fix judder without blur
Hunter
Wednesday, February 4th, 2026 at 4:47 PM
70 updateBookPosition(0, false);
71 }
72
-// 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;
- }
-
73 +// Calculate the shift amount for a given spread
74 +function getShiftAmountForSpread(spreadIndex) {
75 // Spread 0 (front cover): single right page, shift left to center
76 // Spread 4 (back cover): single left page, shift right to center
- // Half page width = 1.375in
- let shiftAmount = '0in';
77 + // margin-left is doubled to compensate for flex re-centering, so the net visual
78 + // shift is half the margin value (net visual shift = half a page width = 1.375in)
79 if (spreadIndex === 0) {
- shiftAmount = '-1.375in';
80 + return '-2.75in';
81 } else if (spreadIndex === 4) {
- shiftAmount = '1.375in';
82 + return '2.75in';
83 }
84 + return '0in';
85 +}
86
- 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;
87 +// Update the book container horizontal position based on spread
88 +// Spreads 0 and 4 (single pages) should be centered, others at normal position
89 +// Uses margin-left instead of translateX to avoid conflicts with preserve-3d and zoom
90 +function updateBookPosition(spreadIndex, animated = true) {
91 + if (!bookContainer) return;
92
- // 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`);
93 + const shiftAmount = getShiftAmountForSpread(spreadIndex);
94
- // 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})`;
- });
95 + if (animated) {
96 + bookContainer.style.transition = 'margin-left 0.6s cubic-bezier(0.645, 0.045, 0.355, 1.000)';
97 + bookContainer.style.marginLeft = shiftAmount;
98
99 // Remove transition after animation completes
100 setTimeout(() => {
101 bookContainer.style.transition = '';
- }, 600);
102 + }, 650);
103 } else {
104 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;
105 + bookContainer.style.marginLeft = shiftAmount;
106 + bookContainer.offsetHeight;
107 + bookContainer.style.transition = '';
108 }
109 }
110
79 // Store the current zoom on the book container for use by updateBookPosition
80 bookContainer.dataset.currentZoom = scale;
81
- // 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 || '';
-
82 // Notify parent of spread position and grid size for background grid
83 if (doc.defaultView?.parent && doc.defaultView.parent !== doc.defaultView) {
84 const rect = bookContainer.getBoundingClientRect();
88 const gridHeight = rect.height / 11;
89 const gridWidth = gridHeight * (30.25 / 29.75);
90
- // 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;
91 + // Remove the net visual shift so grid always anchors to the un-shifted book position
92 + // margin-left is doubled to compensate for flex re-centering, so the net visual
93 + // shift is half the margin value; getBoundingClientRect already reflects this
94 + const marginLeft = bookContainer.style.marginLeft || '0in';
95 + const marginMatch = marginLeft.match(/([-.0-9]+)in/);
96 + const netShiftPx = marginMatch ? (parseFloat(marginMatch[1]) / 2) * 96 * scale : 0;
97
98 doc.defaultView.parent.postMessage({
99 type: 'scaleChange',
100 gridWidth,
101 gridHeight,
- spreadLeft: rect.left - translatePx,
102 + spreadLeft: rect.left - netShiftPx,
103 spreadTop: rect.top
104 }, '*');
105 }
114
115 // Spread 0 (front cover): single right page, shift left to center
116 // Spread 4 (back cover): single left page, shift right to center
- // Half page width = 1.375in
117 + // Doubled to compensate for flex re-centering (net visual shift = half)
118 let shiftAmount = '0in';
119 if (spreadIndex === 0) {
- shiftAmount = '-1.375in';
120 + shiftAmount = '-2.75in';
121 } else if (spreadIndex === 4) {
- shiftAmount = '1.375in';
122 + shiftAmount = '2.75in';
123 }
124
125 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 + ')';
- });
126 + bookContainer.style.transition = 'margin-left 0.6s cubic-bezier(0.645, 0.045, 0.355, 1.000)';
127 + bookContainer.style.marginLeft = shiftAmount;
128
129 // Remove transition after animation completes
130 setTimeout(function() {
131 bookContainer.style.transition = '';
- }, 600);
132 + }, 650);
133 } else {
134 bookContainer.style.transition = 'none';
- // For non-animated, use explicit translateX
- bookContainer.style.transform = shiftAmount !== '0in' ? 'translateX(' + shiftAmount + ')' : 'translateX(0in)';
135 + bookContainer.style.marginLeft = shiftAmount;
136 + bookContainer.offsetHeight;
137 + bookContainer.style.transition = '';
138 }
139 }
140