add page flip buffering
Hunter
Monday, February 2nd, 2026 at 6:40 PM
1 // Page flip animation state
2 let isAnimating = false;
3 let currentSpreadIndex = 0;
4 +let pendingFlipQueue = [];
5
6 // Get animation state
7 export function getIsAnimating() {
95
96 }
97
-// Animate page flip
-export function animatePageFlip(fromSpread, toSpread, container, doc, onComplete) {
- if (isAnimating) return;
98 +// Process next queued flip if any
99 +function processNextFlip() {
100 + if (pendingFlipQueue.length === 0) {
101 + isAnimating = false;
102 + return;
103 + }
104 +
105 + const nextFlip = pendingFlipQueue.shift();
106 + // Use current spread index as fromSpread since we're now at a different position
107 + executePageFlip(currentSpreadIndex, nextFlip.toSpread, nextFlip.container, nextFlip.doc, nextFlip.onComplete);
108 +}
109
110 +// Execute a single page flip (internal function)
111 +function executePageFlip(fromSpread, toSpread, container, doc, onComplete) {
112 isAnimating = true;
113 currentSpreadIndex = toSpread;
114
131 });
132
133 // Clean up after animation
134 + let cleanupCalled = false;
135 const cleanup = () => {
136 + if (cleanupCalled) return;
137 + cleanupCalled = true;
138 +
139 leafToFlip.dataset.state = 'open';
140 leafToFlip.style.transition = '';
141 // Set z-index for open state (lower than closed leaves)
142 leafToFlip.style.zIndex = String(fromSpread + 1);
- isAnimating = false;
143 if (onComplete) onComplete();
144 leafToFlip.removeEventListener('transitionend', cleanup);
145 + clearTimeout(timeoutId);
146 + // Process next flip in queue
147 + processNextFlip();
148 };
149 leafToFlip.addEventListener('transitionend', cleanup);
150 +
151 + // Fallback timeout in case transitionend doesn't fire
152 + const timeoutId = setTimeout(cleanup, 800);
153 }
154 } else {
155 // Flipping backward (left to right)
167 });
168
169 // Clean up after animation
170 + let cleanupCalled = false;
171 const cleanup = () => {
172 + if (cleanupCalled) return;
173 + cleanupCalled = true;
174 +
175 leafToFlip.dataset.state = 'closed';
176 leafToFlip.style.transition = '';
177 // Set z-index for closed state (higher than open leaves)
178 leafToFlip.style.zIndex = String(20 - toSpread);
- isAnimating = false;
179 if (onComplete) onComplete();
180 leafToFlip.removeEventListener('transitionend', cleanup);
181 + clearTimeout(timeoutId);
182 + // Process next flip in queue
183 + processNextFlip();
184 };
185 leafToFlip.addEventListener('transitionend', cleanup);
186 +
187 + // Fallback timeout in case transitionend doesn't fire
188 + const timeoutId = setTimeout(cleanup, 800);
189 }
190 }
191 }
192
193 +// Animate page flip with input buffering
194 +export function animatePageFlip(fromSpread, toSpread, container, doc, onComplete) {
195 + // If already animating, queue this flip request
196 + if (isAnimating) {
197 + // Check if there's already a queued flip to the same target - if so, skip this one
198 + // This prevents the queue from growing unnecessarily when users spam the same direction
199 + const lastQueued = pendingFlipQueue[pendingFlipQueue.length - 1];
200 + if (!lastQueued || lastQueued.toSpread !== toSpread) {
201 + pendingFlipQueue.push({ fromSpread, toSpread, container, doc, onComplete });
202 + }
203 + return;
204 + }
205 +
206 + // Start the flip immediately
207 + executePageFlip(fromSpread, toSpread, container, doc, onComplete);
208 +}
209 +
210 // Set current spread without animation (for initial load)
211 export function setSpreadImmediate(spreadIndex, doc) {
212 currentSpreadIndex = spreadIndex;
47 // Navigate to a spread with optional flip animation
48 export function navigateToSpread(container, spreadIndex, doc, useFlipAnimation = false, onComplete) {
49 if (useFlipAnimation && container.querySelector('.zine-book')) {
- if (!getIsAnimating()) {
- animatePageFlip(currentFlipSpread, spreadIndex, container, doc, onComplete);
- currentFlipSpread = spreadIndex;
- }
50 + // Always call animatePageFlip - it now handles queueing internally
51 + animatePageFlip(currentFlipSpread, spreadIndex, container, doc, onComplete);
52 + currentFlipSpread = spreadIndex;
53 } else {
54 // Original non-animated behavior
55 const spread = SPREADS[spreadIndex];
61 transform-origin: left center;
62 transform-style: preserve-3d;
63 transition: none;
64 + will-change: transform;
65 }
66
67 /* When flipping, use high z-index so it's on top during animation */
77 height: 100%;
78 backface-visibility: hidden;
79 -webkit-backface-visibility: hidden;
80 + transform: translateZ(0);
81 }
82
83 .zine-leaf-front {