From 9c1a007298a26e5c039a93c2753b54410776070a Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Fri, 17 Apr 2026 22:33:18 -0400 Subject: [PATCH] Center on focused frame instead of full spread when zoomed in When a numeric zoom is active and both panes have content, #render() now centers on the frame indicated by #side rather than centering the entire spread. This means on page turns the view starts on the correct frame (left for LTR, right for RTL) instead of the middle of the spread. For fit-page/fit-width and single-pane views, centering behavior is unchanged. --- fixed-layout.js | 60 ++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 54 insertions(+), 6 deletions(-) diff --git a/fixed-layout.js b/fixed-layout.js index 840d8f1..9ff197d 100644 --- a/fixed-layout.js +++ b/fixed-layout.js @@ -204,9 +204,12 @@ export class FixedLayout extends HTMLElement { #updateFrameScales(scale) { const left = this.#left ?? {}; const right = this.#center ?? this.#right ?? {}; - const { width: hostWidth, height: hostHeight } = this.getBoundingClientRect(); + const { width: hostWidth, height: hostHeight } = + this.getBoundingClientRect(); const portrait = - this.spread !== "both" && this.spread !== "portrait" && hostHeight > hostWidth; + this.spread !== "both" && + this.spread !== "portrait" && + hostHeight > hostWidth; const target = this.#side === "left" ? left : right; const blankWidth = left.width ?? right.width ?? 0; const blankHeight = left.height ?? right.height ?? 0; @@ -245,9 +248,12 @@ export class FixedLayout extends HTMLElement { #getContentSize() { const left = this.#left ?? {}; const right = this.#center ?? this.#right ?? {}; - const { width: hostWidth, height: hostHeight } = this.getBoundingClientRect(); + const { width: hostWidth, height: hostHeight } = + this.getBoundingClientRect(); const portrait = - this.spread !== "both" && this.spread !== "portrait" && hostHeight > hostWidth; + this.spread !== "both" && + this.spread !== "portrait" && + hostHeight > hostWidth; const target = this.#side === "left" ? left : right; const blankWidth = left.width ?? right.width ?? 0; const blankHeight = left.height ?? right.height ?? 0; @@ -334,6 +340,14 @@ export class FixedLayout extends HTMLElement { if (!this.#dragState.isDragging) return; this.#dragState.isDragging = false; this.style.cursor = ""; + + // sync #side with the frame the user actually panned to + if (!this.#center && !this.#left?.blank && !this.#right?.blank) { + const leftWidth = (this.#left.width ?? 0) * this.#transform.scale; + const viewportCenterInWrapper = + this.getBoundingClientRect().width / 2 - this.#transform.x; + this.#side = viewportCenterInWrapper < leftWidth ? "left" : "right"; + } } async #createFrame({ index, src: srcOption }, frameId) { @@ -386,7 +400,8 @@ export class FixedLayout extends HTMLElement { const iframeRect = frame.iframe.getBoundingClientRect(); const flRect = this.getBoundingClientRect(); const scaleX = iframeRect.width / (doc.documentElement.clientWidth || 1); - const scaleY = iframeRect.height / (doc.documentElement.clientHeight || 1); + const scaleY = + iframeRect.height / (doc.documentElement.clientHeight || 1); return { clientX: iframeRect.left - flRect.left + e.clientX * scaleX, clientY: iframeRect.top - flRect.top + e.clientY * scaleY, @@ -527,9 +542,24 @@ export class FixedLayout extends HTMLElement { this.#updateFrameScales(scale); const { contentWidth, contentHeight } = this.#getContentSize(); - this.#transform.x = (width - contentWidth) / 2; this.#transform.y = (height - contentHeight) / 2; + const isNumericZoom = typeof this.#zoom === "number" && !isNaN(this.#zoom); + const hasDualFrames = + !this.#center && !this.#left?.blank && !this.#right?.blank; + + if (isNumericZoom && hasDualFrames) { + const leftWidth = (this.#left.width ?? 0) * scale; + const rightWidth = (this.#right.width ?? 0) * scale; + if (side === "right") { + this.#transform.x = (width - rightWidth) / 2 - leftWidth; + } else { + this.#transform.x = (width - leftWidth) / 2; + } + } else { + this.#transform.x = (width - contentWidth) / 2; + } + this.#applyTransform(); } @@ -562,6 +592,15 @@ export class FixedLayout extends HTMLElement { this.#reportLocation("page"); return true; } + + // zoomed-in dual-pane - pan to left frame + if (typeof this.#zoom === "number" && !this.#right?.blank) { + if (this.#side === "right") { + this.#side = "left"; + this.#render(); + return true; + } + } } #goRight() { @@ -572,6 +611,15 @@ export class FixedLayout extends HTMLElement { this.#reportLocation("page"); return true; } + + // zoomed-in dual-pane - pan to right frame + if (typeof this.#zoom === "number" && !this.#left?.blank) { + if (this.#side === "left") { + this.#side = "right"; + this.#render(); + return true; + } + } } open(book) {