commit 9e3323f1342ea8c2fc5e2116d4a4b949a40ed270
parent e85bdcd0cc37a32978a1a5da2ee0721b9af3e846
Author: Hunter
Date:   Mon,  2 Feb 2026 18:40:22 -0500

add page flip buffering

Diffstat:
Mresources/page-flip-animation.js | 56+++++++++++++++++++++++++++++++++++++++++++++++++++-----
Mresources/spread-layout.js | 7+++----
Mresources/zine-styles.js | 2++
3 files changed, 56 insertions(+), 9 deletions(-)

diff --git a/resources/page-flip-animation.js b/resources/page-flip-animation.js @@ -1,6 +1,7 @@ // Page flip animation state let isAnimating = false; let currentSpreadIndex = 0; +let pendingFlipQueue = []; // Get animation state export function getIsAnimating() { @@ -94,10 +95,20 @@ function updateLeafStates(spreadIndex, doc) { } -// Animate page flip -export function animatePageFlip(fromSpread, toSpread, container, doc, onComplete) { - if (isAnimating) return; +// Process next queued flip if any +function processNextFlip() { + if (pendingFlipQueue.length === 0) { + isAnimating = false; + return; + } + + const nextFlip = pendingFlipQueue.shift(); + // Use current spread index as fromSpread since we're now at a different position + executePageFlip(currentSpreadIndex, nextFlip.toSpread, nextFlip.container, nextFlip.doc, nextFlip.onComplete); +} +// Execute a single page flip (internal function) +function executePageFlip(fromSpread, toSpread, container, doc, onComplete) { isAnimating = true; currentSpreadIndex = toSpread; @@ -120,16 +131,25 @@ export function animatePageFlip(fromSpread, toSpread, container, doc, onComplete }); // Clean up after animation + let cleanupCalled = false; const cleanup = () => { + if (cleanupCalled) return; + cleanupCalled = true; + leafToFlip.dataset.state = 'open'; leafToFlip.style.transition = ''; // Set z-index for open state (lower than closed leaves) leafToFlip.style.zIndex = String(fromSpread + 1); - isAnimating = false; if (onComplete) onComplete(); leafToFlip.removeEventListener('transitionend', cleanup); + clearTimeout(timeoutId); + // Process next flip in queue + processNextFlip(); }; leafToFlip.addEventListener('transitionend', cleanup); + + // Fallback timeout in case transitionend doesn't fire + const timeoutId = setTimeout(cleanup, 800); } } else { // Flipping backward (left to right) @@ -147,20 +167,46 @@ export function animatePageFlip(fromSpread, toSpread, container, doc, onComplete }); // Clean up after animation + let cleanupCalled = false; const cleanup = () => { + if (cleanupCalled) return; + cleanupCalled = true; + leafToFlip.dataset.state = 'closed'; leafToFlip.style.transition = ''; // Set z-index for closed state (higher than open leaves) leafToFlip.style.zIndex = String(20 - toSpread); - isAnimating = false; if (onComplete) onComplete(); leafToFlip.removeEventListener('transitionend', cleanup); + clearTimeout(timeoutId); + // Process next flip in queue + processNextFlip(); }; leafToFlip.addEventListener('transitionend', cleanup); + + // Fallback timeout in case transitionend doesn't fire + const timeoutId = setTimeout(cleanup, 800); } } } +// Animate page flip with input buffering +export function animatePageFlip(fromSpread, toSpread, container, doc, onComplete) { + // If already animating, queue this flip request + if (isAnimating) { + // Check if there's already a queued flip to the same target - if so, skip this one + // This prevents the queue from growing unnecessarily when users spam the same direction + const lastQueued = pendingFlipQueue[pendingFlipQueue.length - 1]; + if (!lastQueued || lastQueued.toSpread !== toSpread) { + pendingFlipQueue.push({ fromSpread, toSpread, container, doc, onComplete }); + } + return; + } + + // Start the flip immediately + executePageFlip(fromSpread, toSpread, container, doc, onComplete); +} + // Set current spread without animation (for initial load) export function setSpreadImmediate(spreadIndex, doc) { currentSpreadIndex = spreadIndex; diff --git a/resources/spread-layout.js b/resources/spread-layout.js @@ -47,10 +47,9 @@ export function setupSpreadLayout(container, spread, doc, useFlipAnimation = fal // Navigate to a spread with optional flip animation export function navigateToSpread(container, spreadIndex, doc, useFlipAnimation = false, onComplete) { if (useFlipAnimation && container.querySelector('.zine-book')) { - if (!getIsAnimating()) { - animatePageFlip(currentFlipSpread, spreadIndex, container, doc, onComplete); - currentFlipSpread = spreadIndex; - } + // Always call animatePageFlip - it now handles queueing internally + animatePageFlip(currentFlipSpread, spreadIndex, container, doc, onComplete); + currentFlipSpread = spreadIndex; } else { // Original non-animated behavior const spread = SPREADS[spreadIndex]; diff --git a/resources/zine-styles.js b/resources/zine-styles.js @@ -61,6 +61,7 @@ export const ZINE_FLIP_CSS = ` transform-origin: left center; transform-style: preserve-3d; transition: none; + will-change: transform; } /* When flipping, use high z-index so it's on top during animation */ @@ -76,6 +77,7 @@ export const ZINE_FLIP_CSS = ` height: 100%; backface-visibility: hidden; -webkit-backface-visibility: hidden; + transform: translateZ(0); } .zine-leaf-front {