mirror of
https://github.com/moku-project/Moku.git
synced 2026-06-13 09:19:56 -05:00
Feat: Middle-Click for Browser-Auto-Scroll (#70)
This commit is contained in:
@@ -214,6 +214,30 @@
|
|||||||
let autoScrollPaused = false;
|
let autoScrollPaused = false;
|
||||||
let autoScrollPauseTimer: ReturnType<typeof setTimeout> | null = null;
|
let autoScrollPauseTimer: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
|
||||||
|
let midScrollActive = $state(false);
|
||||||
|
let midScrollOriginY = 0;
|
||||||
|
let midScrollRaf: number | null = null;
|
||||||
|
|
||||||
|
function startMidScroll(originY: number) {
|
||||||
|
midScrollActive = true;
|
||||||
|
midScrollOriginY = originY;
|
||||||
|
if (midScrollRaf) cancelAnimationFrame(midScrollRaf);
|
||||||
|
const tick = () => {
|
||||||
|
if (!midScrollActive || !containerEl) return;
|
||||||
|
const dy = (window as any)._midScrollCurrentY - midScrollOriginY;
|
||||||
|
const deadZone = 24;
|
||||||
|
const speed = Math.sign(dy) * Math.max(0, Math.abs(dy) - deadZone) * 0.12;
|
||||||
|
containerEl.scrollTop += speed;
|
||||||
|
midScrollRaf = requestAnimationFrame(tick);
|
||||||
|
};
|
||||||
|
midScrollRaf = requestAnimationFrame(tick);
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopMidScroll() {
|
||||||
|
midScrollActive = false;
|
||||||
|
if (midScrollRaf) { cancelAnimationFrame(midScrollRaf); midScrollRaf = null; }
|
||||||
|
}
|
||||||
|
|
||||||
function pauseAutoScroll() {
|
function pauseAutoScroll() {
|
||||||
autoScrollPaused = true;
|
autoScrollPaused = true;
|
||||||
if (autoScrollPauseTimer) clearTimeout(autoScrollPauseTimer);
|
if (autoScrollPauseTimer) clearTimeout(autoScrollPauseTimer);
|
||||||
@@ -250,6 +274,11 @@
|
|||||||
|
|
||||||
export function onInspectMouseDown(e: MouseEvent) {
|
export function onInspectMouseDown(e: MouseEvent) {
|
||||||
if ((e.target as Element).closest(".bar")) return;
|
if ((e.target as Element).closest(".bar")) return;
|
||||||
|
if (e.button === 1 && style === "longstrip") {
|
||||||
|
e.preventDefault();
|
||||||
|
if (midScrollActive) { stopMidScroll(); } else { startMidScroll(e.clientY); }
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (style === "longstrip") {
|
if (style === "longstrip") {
|
||||||
stripDragging = true;
|
stripDragging = true;
|
||||||
stripDragMoved = false;
|
stripDragMoved = false;
|
||||||
@@ -270,6 +299,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function onInspectMouseMove(e: MouseEvent) {
|
export function onInspectMouseMove(e: MouseEvent) {
|
||||||
|
(window as any)._midScrollCurrentY = e.clientY;
|
||||||
if (stripDragging) {
|
if (stripDragging) {
|
||||||
const dy = e.clientY - stripDragStartY;
|
const dy = e.clientY - stripDragStartY;
|
||||||
if (!stripDragMoved && Math.abs(dy) > 4) stripDragMoved = true;
|
if (!stripDragMoved && Math.abs(dy) > 4) stripDragMoved = true;
|
||||||
@@ -371,8 +401,13 @@
|
|||||||
} else if (style !== "longstrip") {
|
} else if (style !== "longstrip") {
|
||||||
observer?.disconnect();
|
observer?.disconnect();
|
||||||
observer = null;
|
observer = null;
|
||||||
|
stopMidScroll();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
(window as any)._midScrollCurrentY = 0;
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@@ -380,17 +415,22 @@
|
|||||||
class="viewer"
|
class="viewer"
|
||||||
class:strip={style === "longstrip"}
|
class:strip={style === "longstrip"}
|
||||||
class:inspect-active={readerState.inspectScale > 1}
|
class:inspect-active={readerState.inspectScale > 1}
|
||||||
|
class:midscroll-active={midScrollActive}
|
||||||
style={effectiveWidth != null ? `--effective-width:${effectiveWidth}px` : ""}
|
style={effectiveWidth != null ? `--effective-width:${effectiveWidth}px` : ""}
|
||||||
role="presentation"
|
role="presentation"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
onclick={handleTap}
|
onclick={handleTap}
|
||||||
|
onauxclick={(e) => { if (e.button === 1 && style === "longstrip") e.preventDefault(); }}
|
||||||
ondblclick={() => { if (tapToToggleBar) onToggleUi(); }}
|
ondblclick={() => { if (tapToToggleBar) onToggleUi(); }}
|
||||||
onmousedown={onInspectMouseDown}
|
onmousedown={onInspectMouseDown}
|
||||||
onpointerdown={pinchZoomEnabled ? onPointerDown : undefined}
|
onpointerdown={pinchZoomEnabled ? onPointerDown : undefined}
|
||||||
onwheel={(e) => { if (e.ctrlKey || style !== "longstrip") e.preventDefault(); }}
|
onwheel={(e) => { if (e.ctrlKey || style !== "longstrip") e.preventDefault(); }}
|
||||||
style:cursor={style === "longstrip" ? (stripDragging ? "grabbing" : "grab") : undefined}
|
style:cursor={midScrollActive ? "none" : style === "longstrip" ? (stripDragging ? "grabbing" : "grab") : undefined}
|
||||||
onkeydown={(e) => { if (e.key === " " && style === "longstrip") { e.preventDefault(); store.settings.autoScroll = !store.settings.autoScroll; } }}
|
onkeydown={(e) => { if (e.key === " " && style === "longstrip") { e.preventDefault(); store.settings.autoScroll = !store.settings.autoScroll; } }}
|
||||||
>
|
>
|
||||||
|
{#if midScrollActive}
|
||||||
|
<div class="midscroll-cursor" style="top:{midScrollOriginY}px"></div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if loading}
|
{#if loading}
|
||||||
<div class="center-overlay"><CircleNotch size={20} weight="light" class="anim-spin" style="color:var(--text-faint)" /></div>
|
<div class="center-overlay"><CircleNotch size={20} weight="light" class="anim-spin" style="color:var(--text-faint)" /></div>
|
||||||
@@ -514,4 +554,35 @@
|
|||||||
|
|
||||||
.center-overlay { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; }
|
.center-overlay { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; }
|
||||||
.error-msg { color: var(--color-error); font-size: var(--text-base); }
|
.error-msg { color: var(--color-error); font-size: var(--text-base); }
|
||||||
|
|
||||||
|
.midscroll-cursor {
|
||||||
|
position: fixed;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 2px solid var(--accent-fg);
|
||||||
|
background: transparent;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 100;
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
.midscroll-cursor::before,
|
||||||
|
.midscroll-cursor::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
border-left: 5px solid transparent;
|
||||||
|
border-right: 5px solid transparent;
|
||||||
|
}
|
||||||
|
.midscroll-cursor::before {
|
||||||
|
top: -10px;
|
||||||
|
border-bottom: 6px solid var(--accent-fg);
|
||||||
|
}
|
||||||
|
.midscroll-cursor::after {
|
||||||
|
bottom: -10px;
|
||||||
|
border-top: 6px solid var(--accent-fg);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
Reference in New Issue
Block a user