commit 98ec6608fb25192a41230207b485b4ea17ee5744
parent 479562d2c65cbb5edc4ab583985faa7f0a32f34c
Author: Hunter
Date: Wed, 4 Feb 2026 14:56:39 -0500
scaling fixed; judder when opening/closing covers
Diffstat:
4 files changed, 113 insertions(+), 20 deletions(-)
diff --git a/resources/page-flip-animation.js b/resources/page-flip-animation.js
@@ -73,7 +73,10 @@ export function initPageFlip(container, doc) {
// 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) return;
+ if (!bookContainer) {
+ console.error('updateBookPosition: bookContainer is null!');
+ return;
+ }
// Spread 0 (front cover): single right page, shift left to center
// Spread 4 (back cover): single left page, shift right to center
@@ -85,24 +88,40 @@ function updateBookPosition(spreadIndex, animated = true) {
shiftAmount = '1.375in';
}
+ 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) {
- bookContainer.style.transition = 'transform 0.6s cubic-bezier(0.645, 0.045, 0.355, 1.000)';
- } else {
- bookContainer.style.transition = 'none';
- }
+ // Clear any existing transition first to ensure clean state
+ bookContainer.style.transition = '';
+ // Force a reflow
+ bookContainer.offsetHeight;
- // Preserve any existing scale from scaleSpreadToFit
- const currentTransform = bookContainer.style.transform || '';
- const scaleMatch = currentTransform.match(/scale\([^)]+\)/);
- const scale = scaleMatch ? scaleMatch[0] : '';
+ // 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`);
- bookContainer.style.transform = scale ? `${scale} translateX(${shiftAmount})` : `translateX(${shiftAmount})`;
+ // 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})`;
+ });
- // Remove transition after animation completes
- if (animated) {
+ // Remove transition after animation completes
setTimeout(() => {
bookContainer.style.transition = '';
}, 600);
+ } 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;
}
}
@@ -151,10 +170,13 @@ function executePageFlip(fromSpread, toSpread, container, doc, onComplete) {
isAnimating = true;
currentSpreadIndex = toSpread;
+ console.log(`\n=== executePageFlip: ${fromSpread} → ${toSpread} ===`);
+
// Update book position synchronously with the flip animation
updateBookPosition(toSpread, true);
const direction = toSpread > fromSpread ? 'forward' : 'backward';
+ console.log(`Direction: ${direction}`);
const leaves = doc.querySelectorAll('.zine-leaf');
if (direction === 'forward') {
@@ -251,6 +273,7 @@ export function animatePageFlip(fromSpread, toSpread, container, doc, onComplete
// Set current spread without animation (for initial load)
export function setSpreadImmediate(spreadIndex, doc) {
+ console.log(`\n=== setSpreadImmediate(${spreadIndex}) ===`);
currentSpreadIndex = spreadIndex;
updateLeafStates(spreadIndex, doc);
updateBookPosition(spreadIndex, false);
diff --git a/resources/preview-manager.js b/resources/preview-manager.js
@@ -125,10 +125,23 @@ export function updatePreview(editorView, isEditorFocused) {
// Set initial spread state without animation
setTimeout(() => {
+ scaleSpreadToFit(container, doc); // Must call this BEFORE setSpreadImmediate so zoom is set
setSpreadImmediate(currentSpread, doc);
- const scaleToFit = () => scaleSpreadToFit(container, doc);
- scaleToFit();
- doc.defaultView.addEventListener('resize', scaleToFit);
+
+ // On resize, update zoom and reposition book
+ const handleResize = () => {
+ scaleSpreadToFit(container, doc);
+ // Get the actual current spread from the leaf states
+ const leaves = doc.querySelectorAll('.zine-leaf');
+ let activeSpread = 0;
+ leaves.forEach((leaf, index) => {
+ if (leaf.dataset.state === 'open') {
+ activeSpread = index + 1;
+ }
+ });
+ setSpreadImmediate(activeSpread, doc);
+ };
+ doc.defaultView.addEventListener('resize', handleResize);
}, 0);
} else {
// Original non-animated mode
diff --git a/resources/spread-layout.js b/resources/spread-layout.js
@@ -73,12 +73,18 @@ export function scaleSpreadToFit(container, doc, bottomPadding = 0) {
const scaleY = (vh - 40) / spreadHeightPx;
const scale = Math.min(scaleX, scaleY);
+ // Use CSS zoom for high-resolution rendering instead of transform scale
+ bookContainer.style.zoom = scale;
+
+ // 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 ? `scale(${scale}) ${translateX}` : `scale(${scale})`;
+ bookContainer.style.transform = translateX || '';
// Notify parent of spread position and grid size for background grid
if (doc.defaultView?.parent && doc.defaultView.parent !== doc.defaultView) {
@@ -90,7 +96,7 @@ export function scaleSpreadToFit(container, doc, bottomPadding = 0) {
const gridWidth = gridHeight * (30.25 / 29.75);
// Remove translateX shift so grid always anchors to the un-shifted book position
- // The translateX is in inches and gets multiplied by scale
+ // 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;
diff --git a/resources/viewer-generator.js b/resources/viewer-generator.js
@@ -104,7 +104,48 @@ export function generateViewerCode() {
const scaleX = (vw - 40) / spreadWidthPx;
const scaleY = (vh - 40) / spreadHeightPx;
const scale = Math.min(scaleX, scaleY);
- bookContainer.style.transform = 'scale(' + scale + ')';
+ bookContainer.style.zoom = scale;
+ bookContainer.dataset.currentZoom = scale;
+ }
+
+ function updateBookPosition(spreadIndex, animated) {
+ const bookContainer = container.querySelector('.zine-book');
+ if (!bookContainer) return;
+
+ // 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';
+ if (spreadIndex === 0) {
+ shiftAmount = '-1.375in';
+ } else if (spreadIndex === 4) {
+ shiftAmount = '1.375in';
+ }
+
+ 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 + ')';
+ });
+
+ // Remove transition after animation completes
+ setTimeout(function() {
+ bookContainer.style.transition = '';
+ }, 600);
+ } else {
+ bookContainer.style.transition = 'none';
+ // For non-animated, use explicit translateX
+ bookContainer.style.transform = shiftAmount !== '0in' ? 'translateX(' + shiftAmount + ')' : 'translateX(0in)';
+ }
}
function initFlipMode() {
@@ -141,6 +182,9 @@ export function generateViewerCode() {
if (isAnimating) return;
isAnimating = true;
+ // Update book position synchronously with the flip animation
+ updateBookPosition(toSpread, true);
+
const direction = toSpread > fromSpread ? 'forward' : 'backward';
const leaves = document.querySelectorAll('.zine-leaf');
@@ -319,10 +363,11 @@ export function generateViewerCode() {
leaf.style.zIndex = String(index + 1);
}
});
+ scaleToFit();
+ updateBookPosition(currentSpread, false);
} else {
showSpread(currentSpread, false);
}
- scaleToFit();
const blob = new Blob([html], { type: 'text/html' });
const url = URL.createObjectURL(blob);
@@ -334,7 +379,12 @@ export function generateViewerCode() {
}
});
- window.addEventListener('resize', scaleToFit);
+ window.addEventListener('resize', function() {
+ scaleToFit();
+ if (USE_FLIP_ANIMATION) {
+ updateBookPosition(currentSpread, false);
+ }
+ });
// Initialize flip mode
if (USE_FLIP_ANIMATION) {
@@ -343,6 +393,7 @@ export function generateViewerCode() {
document.getElementById('zine-prev').disabled = true;
document.getElementById('zine-next').disabled = false;
scaleToFit();
+ updateBookPosition(0, false);
} else {
showSpread(0, false);
}