commit f2f3e4dd26daabc3eb5db165f9e63832d4dffb63
parent 98ec6608fb25192a41230207b485b4ea17ee5744
Author: Hunter
Date:   Wed,  4 Feb 2026 16:47:58 -0500

fix judder without blur

Diffstat:
Mresources/page-flip-animation.js | 59+++++++++++++++++++++--------------------------------------
Mresources/spread-layout.js | 19+++++++------------
Mresources/viewer-generator.js | 28+++++++++-------------------
3 files changed, 37 insertions(+), 69 deletions(-)

diff --git a/resources/page-flip-animation.js b/resources/page-flip-animation.js @@ -70,58 +70,41 @@ export function initPageFlip(container, doc) { updateBookPosition(0, false); } -// Update the book container horizontal position based on spread -// Spreads 0 and 4 (single pages) should be centered, others at normal position -function updateBookPosition(spreadIndex, animated = true) { - if (!bookContainer) { - console.error('updateBookPosition: bookContainer is null!'); - return; - } - +// Calculate the shift amount for a given spread +function getShiftAmountForSpread(spreadIndex) { // Spread 0 (front cover): single right page, shift left to center // Spread 4 (back cover): single left page, shift right to center - // Half page width = 1.375in - let shiftAmount = '0in'; + // margin-left is doubled to compensate for flex re-centering, so the net visual + // shift is half the margin value (net visual shift = half a page width = 1.375in) if (spreadIndex === 0) { - shiftAmount = '-1.375in'; + return '-2.75in'; } else if (spreadIndex === 4) { - shiftAmount = '1.375in'; + return '2.75in'; } + return '0in'; +} - const currentTransform = bookContainer.style.transform; - const currentTransition = bookContainer.style.transition; - console.log(`updateBookPosition(spread=${spreadIndex}, animated=${animated})`); - console.log(` Current transform: "${currentTransform}"`); - console.log(` Current transition: "${currentTransition}"`); - console.log(` Target: translateX(${shiftAmount})`); - - if (animated) { - // Clear any existing transition first to ensure clean state - bookContainer.style.transition = ''; - // Force a reflow - bookContainer.offsetHeight; +// Update the book container horizontal position based on spread +// Spreads 0 and 4 (single pages) should be centered, others at normal position +// Uses margin-left instead of translateX to avoid conflicts with preserve-3d and zoom +function updateBookPosition(spreadIndex, animated = true) { + if (!bookContainer) return; - // Now set the transition for this animation - bookContainer.style.transition = 'transform 0.6s cubic-bezier(0.645, 0.045, 0.355, 1.000)'; - console.log(` Set transition, will animate in rAF`); + const shiftAmount = getShiftAmountForSpread(spreadIndex); - // Use requestAnimationFrame to set transform (same as leaf flip animation) - // This ensures both animations start in the same frame - requestAnimationFrame(() => { - console.log(` rAF: Setting transform to translateX(${shiftAmount})`); - bookContainer.style.transform = `translateX(${shiftAmount})`; - }); + if (animated) { + bookContainer.style.transition = 'margin-left 0.6s cubic-bezier(0.645, 0.045, 0.355, 1.000)'; + bookContainer.style.marginLeft = shiftAmount; // Remove transition after animation completes setTimeout(() => { bookContainer.style.transition = ''; - }, 600); + }, 650); } else { bookContainer.style.transition = 'none'; - // For non-animated, use explicit translateX or none - const newTransform = shiftAmount !== '0in' ? `translateX(${shiftAmount})` : 'translateX(0in)'; - console.log(` Non-animated: Setting transform to ${newTransform}`); - bookContainer.style.transform = newTransform; + bookContainer.style.marginLeft = shiftAmount; + bookContainer.offsetHeight; + bookContainer.style.transition = ''; } } diff --git a/resources/spread-layout.js b/resources/spread-layout.js @@ -79,13 +79,6 @@ export function scaleSpreadToFit(container, doc, bottomPadding = 0) { // Store the current zoom on the book container for use by updateBookPosition bookContainer.dataset.currentZoom = scale; - // Preserve any existing translateX from book positioning - const currentTransform = bookContainer.style.transform || ''; - const translateMatch = currentTransform.match(/translateX\([^)]+\)/); - const translateX = translateMatch ? translateMatch[0] : ''; - - bookContainer.style.transform = translateX || ''; - // Notify parent of spread position and grid size for background grid if (doc.defaultView?.parent && doc.defaultView.parent !== doc.defaultView) { const rect = bookContainer.getBoundingClientRect(); @@ -95,16 +88,18 @@ export function scaleSpreadToFit(container, doc, bottomPadding = 0) { const gridHeight = rect.height / 11; const gridWidth = gridHeight * (30.25 / 29.75); - // Remove translateX shift so grid always anchors to the un-shifted book position - // With CSS zoom, translateX values are in the zoomed coordinate space - const translateInchMatch = translateX.match(/translateX\(([^)]+)in\)/); - const translatePx = translateInchMatch ? parseFloat(translateInchMatch[1]) * 96 * scale : 0; + // Remove the net visual shift so grid always anchors to the un-shifted book position + // margin-left is doubled to compensate for flex re-centering, so the net visual + // shift is half the margin value; getBoundingClientRect already reflects this + const marginLeft = bookContainer.style.marginLeft || '0in'; + const marginMatch = marginLeft.match(/([-.0-9]+)in/); + const netShiftPx = marginMatch ? (parseFloat(marginMatch[1]) / 2) * 96 * scale : 0; doc.defaultView.parent.postMessage({ type: 'scaleChange', gridWidth, gridHeight, - spreadLeft: rect.left - translatePx, + spreadLeft: rect.left - netShiftPx, spreadTop: rect.top }, '*'); } diff --git a/resources/viewer-generator.js b/resources/viewer-generator.js @@ -114,37 +114,27 @@ export function generateViewerCode() { // Spread 0 (front cover): single right page, shift left to center // Spread 4 (back cover): single left page, shift right to center - // Half page width = 1.375in + // Doubled to compensate for flex re-centering (net visual shift = half) let shiftAmount = '0in'; if (spreadIndex === 0) { - shiftAmount = '-1.375in'; + shiftAmount = '-2.75in'; } else if (spreadIndex === 4) { - shiftAmount = '1.375in'; + shiftAmount = '2.75in'; } if (animated) { - // Clear any existing transition first to ensure clean state - bookContainer.style.transition = ''; - // Force a reflow - bookContainer.offsetHeight; - - // Now set the transition for this animation - bookContainer.style.transition = 'transform 0.6s cubic-bezier(0.645, 0.045, 0.355, 1.000)'; - - // Use requestAnimationFrame to set transform (same as leaf flip animation) - // This ensures both animations start in the same frame - requestAnimationFrame(function() { - bookContainer.style.transform = 'translateX(' + shiftAmount + ')'; - }); + bookContainer.style.transition = 'margin-left 0.6s cubic-bezier(0.645, 0.045, 0.355, 1.000)'; + bookContainer.style.marginLeft = shiftAmount; // Remove transition after animation completes setTimeout(function() { bookContainer.style.transition = ''; - }, 600); + }, 650); } else { bookContainer.style.transition = 'none'; - // For non-animated, use explicit translateX - bookContainer.style.transform = shiftAmount !== '0in' ? 'translateX(' + shiftAmount + ')' : 'translateX(0in)'; + bookContainer.style.marginLeft = shiftAmount; + bookContainer.offsetHeight; + bookContainer.style.transition = ''; } }