Fix: GlobalUIZoom Affecting MangaDisplay (#82)

This commit is contained in:
Youwes09
2026-05-16 23:06:10 -05:00
parent 0e2371096b
commit 01f123f5be
2 changed files with 109 additions and 40 deletions
+107 -39
View File
@@ -20,6 +20,7 @@
tapToToggleBar: boolean; tapToToggleBar: boolean;
pinchZoomEnabled: boolean; pinchZoomEnabled: boolean;
chapterEpoch: number; chapterEpoch: number;
barPosition: "top" | "left" | "right";
onGetZoom: () => number; onGetZoom: () => number;
onSetZoom: (z: number) => void; onSetZoom: (z: number) => void;
resolveUrl: (url: string, priority?: number) => Promise<string>; resolveUrl: (url: string, priority?: number) => Promise<string>;
@@ -32,7 +33,7 @@
const { const {
style, imgCls, effectiveWidth, loading, error, pageReady, style, imgCls, effectiveWidth, loading, error, pageReady,
pageGroups, currentGroup, stripToRender, fadingOut, pageGroups, currentGroup, stripToRender, fadingOut,
tapToToggleBar, pinchZoomEnabled, chapterEpoch, onGetZoom, onSetZoom, tapToToggleBar, pinchZoomEnabled, chapterEpoch, barPosition, onGetZoom, onSetZoom,
resolveUrl, onTap, onWheel, onToggleUi, bindContainer, resolveUrl, onTap, onWheel, onToggleUi, bindContainer,
}: Props = $props(); }: Props = $props();
@@ -215,19 +216,33 @@
let autoScrollPauseTimer: ReturnType<typeof setTimeout> | null = null; let autoScrollPauseTimer: ReturnType<typeof setTimeout> | null = null;
let midScrollActive = $state(false); let midScrollActive = $state(false);
let midScrollOriginY = 0; let midScrollOriginY = $state(0);
let midScrollOriginX = $state(0);
let midScrollCurrentY = 0;
let midScrollRaf: number | null = null; let midScrollRaf: number | null = null;
function startMidScroll(originY: number) { // Speed level 0-5 for the indicator bar
const midScrollSpeedLevel = $derived.by(() => {
if (!midScrollActive) return 0;
// recomputes when midScrollOriginY changes; actual dy read in RAF so this is just for display
return 0; // will be updated imperatively
});
let midScrollDisplayLevel = $state(0);
function startMidScroll(originY: number, originX: number) {
midScrollActive = true; midScrollActive = true;
midScrollOriginY = originY; midScrollOriginY = originY;
midScrollOriginX = originX;
midScrollDisplayLevel = 0;
if (midScrollRaf) cancelAnimationFrame(midScrollRaf); if (midScrollRaf) cancelAnimationFrame(midScrollRaf);
const tick = () => { const tick = () => {
if (!midScrollActive || !containerEl) return; if (!midScrollActive || !containerEl) return;
const dy = (window as any)._midScrollCurrentY - midScrollOriginY; const dy = midScrollCurrentY - midScrollOriginY;
const deadZone = 24; const deadZone = 24;
const speed = Math.sign(dy) * Math.max(0, Math.abs(dy) - deadZone) * 0.12; const excess = Math.max(0, Math.abs(dy) - deadZone);
const speed = Math.sign(dy) * excess * 0.12;
containerEl.scrollTop += speed; containerEl.scrollTop += speed;
midScrollDisplayLevel = Math.sign(dy) * Math.min(5, Math.floor(excess / 30));
midScrollRaf = requestAnimationFrame(tick); midScrollRaf = requestAnimationFrame(tick);
}; };
midScrollRaf = requestAnimationFrame(tick); midScrollRaf = requestAnimationFrame(tick);
@@ -235,6 +250,7 @@
function stopMidScroll() { function stopMidScroll() {
midScrollActive = false; midScrollActive = false;
midScrollDisplayLevel = 0;
if (midScrollRaf) { cancelAnimationFrame(midScrollRaf); midScrollRaf = null; } if (midScrollRaf) { cancelAnimationFrame(midScrollRaf); midScrollRaf = null; }
} }
@@ -276,7 +292,11 @@
if ((e.target as Element).closest(".bar")) return; if ((e.target as Element).closest(".bar")) return;
if (e.button === 1 && style === "longstrip") { if (e.button === 1 && style === "longstrip") {
e.preventDefault(); e.preventDefault();
if (midScrollActive) { stopMidScroll(); } else { startMidScroll(e.clientY); } if (midScrollActive) { stopMidScroll(); } else {
// pause regular auto-scroll while mid-scroll is active
store.settings.autoScroll = false;
startMidScroll(e.clientY, e.clientX);
}
return; return;
} }
if (style === "longstrip") { if (style === "longstrip") {
@@ -299,7 +319,7 @@
} }
export function onInspectMouseMove(e: MouseEvent) { export function onInspectMouseMove(e: MouseEvent) {
(window as any)._midScrollCurrentY = e.clientY; 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;
@@ -404,10 +424,6 @@
stopMidScroll(); stopMidScroll();
} }
}); });
$effect(() => {
(window as any)._midScrollCurrentY = 0;
});
</script> </script>
<div <div
@@ -425,11 +441,24 @@
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={midScrollActive ? "none" : style === "longstrip" ? (stripDragging ? "grabbing" : "grab") : undefined} style:cursor={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} {#if midScrollActive}
<div class="midscroll-cursor" style="top:{midScrollOriginY}px"></div> <div class="midscroll-bar" class:midscroll-bar-right={barPosition !== "right"} class:midscroll-bar-left={barPosition === "right"}>
<div class="midscroll-segments">
{#each [5,4,3,2,1] as n}
<div class="midscroll-seg" class:midscroll-seg-lit={midScrollDisplayLevel < 0 && -midScrollDisplayLevel >= n}></div>
{/each}
<div class="midscroll-origin-dot"></div>
{#each [1,2,3,4,5] as n}
<div class="midscroll-seg" class:midscroll-seg-lit={midScrollDisplayLevel > 0 && midScrollDisplayLevel >= n}></div>
{/each}
</div>
<button class="midscroll-stop" onclick={stopMidScroll} title="Stop (middle click)">
<svg width="8" height="8" viewBox="0 0 8 8"><rect x="0" y="0" width="8" height="8" rx="1" fill="currentColor"/></svg>
</button>
</div>
{/if} {/if}
{#if loading} {#if loading}
@@ -523,7 +552,7 @@
</div> </div>
<style> <style>
.viewer { flex: 1; overflow-y: auto; overflow-x: hidden; display: flex; flex-direction: column; align-items: center; justify-content: center; -webkit-overflow-scrolling: touch; position: relative; touch-action: pan-x pan-y; } .viewer { flex: 1; overflow-y: auto; overflow-x: hidden; display: flex; flex-direction: column; align-items: center; justify-content: center; -webkit-overflow-scrolling: touch; position: relative; touch-action: pan-x pan-y; zoom: calc(1 / var(--ui-zoom, 1)); }
.viewer.strip { justify-content: flex-start; padding: var(--sp-4) 0; } .viewer.strip { justify-content: flex-start; padding: var(--sp-4) 0; }
.viewer:focus { outline: none; } .viewer:focus { outline: none; }
.viewer.inspect-active { cursor: grab; overflow: hidden; } .viewer.inspect-active { cursor: grab; overflow: hidden; }
@@ -579,34 +608,73 @@
.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 { .midscroll-bar {
position: fixed; position: fixed;
left: 50%; top: 50%;
transform: translate(-50%, -50%); transform: translateY(-50%);
z-index: 200;
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
padding: 10px 6px;
background: color-mix(in srgb, var(--bg-raised) 92%, transparent);
border: 1px solid var(--border-base);
border-radius: 10px;
box-shadow: 0 4px 16px rgba(0,0,0,0.45);
pointer-events: auto;
backdrop-filter: blur(6px);
-webkit-backdrop-filter: blur(6px);
}
.midscroll-bar-right { right: 8px; }
.midscroll-bar-left { left: 8px; }
.midscroll-segments {
display: flex;
flex-direction: column;
align-items: center;
gap: 3px;
}
.midscroll-origin-dot {
width: 6px;
height: 6px;
border-radius: 50%;
border: 1.5px solid var(--accent-fg);
opacity: 0.6;
flex-shrink: 0;
margin: 2px 0;
}
.midscroll-seg {
width: 4px;
height: 14px;
border-radius: 2px;
background: var(--border-strong);
transition: background 0.06s ease;
flex-shrink: 0;
}
.midscroll-seg-lit {
background: var(--accent-fg);
}
.midscroll-stop {
width: 20px; width: 20px;
height: 20px; height: 20px;
border-radius: 50%; display: flex;
border: 2px solid var(--accent-fg); align-items: center;
background: transparent; justify-content: center;
pointer-events: none; border-radius: var(--radius-sm);
z-index: 100; border: 1px solid var(--border-dim);
opacity: 0.85; background: none;
color: var(--text-faint);
cursor: pointer;
transition: color var(--t-fast), background var(--t-fast), border-color var(--t-fast);
flex-shrink: 0;
} }
.midscroll-cursor::before, .midscroll-stop:hover {
.midscroll-cursor::after { color: var(--text-primary);
content: ""; background: var(--bg-overlay);
position: absolute; border-color: var(--border-strong);
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>
@@ -593,6 +593,7 @@
fadingOut={readerState.fadingOut} fadingOut={readerState.fadingOut}
{tapToToggleBar} {tapToToggleBar}
{pinchZoomEnabled} {pinchZoomEnabled}
{barPosition}
onGetZoom={() => zoom} onGetZoom={() => zoom}
onSetZoom={(z) => { captureZoomAnchor(containerEl, style, zoomAnchor); applySettings({ readerZoom: z }); restoreZoomAnchor(containerEl, zoomAnchor); }} onSetZoom={(z) => { captureZoomAnchor(containerEl, style, zoomAnchor); applySettings({ readerZoom: z }); restoreZoomAnchor(containerEl, zoomAnchor); }}
resolveUrl={(url, priority) => resolveUrl(url, useBlob, priority)} resolveUrl={(url, priority) => resolveUrl(url, useBlob, priority)}