fixed firefox marquee rollover flicker
This commit is contained in:
parent
fe3d5f7039
commit
0f0acb642f
2 changed files with 37 additions and 10 deletions
2
PLAN.md
2
PLAN.md
|
|
@ -665,6 +665,8 @@ Before public launch:
|
|||
- **Fix: `filter: blur(0px)` on Firefox.** A no-op CSS filter forces Firefox through a different compositing path that enables sub-pixel rendering during transform animations. Applied via `.browser-firefox .tab-row { filter: blur(0px); }` using the existing browser detection class on `<body>`.
|
||||
- **`image-rendering: pixelated` conflicts with sub-pixel transforms.** Once the filter hack enables sub-pixel rendering, `pixelated` (nearest-neighbor sampling) causes icons to flicker at sub-pixel positions. Fix: `.browser-firefox .tab-icon { image-rendering: auto; }` — bilinear filtering interpolates smoothly. Icons are very slightly softer on Firefox but don't flicker. Chrome/Safari keep `pixelated`.
|
||||
- **Integer-pixel rAF animation is not a good alternative.** Attempted `requestAnimationFrame` with `Math.round()`/`Math.floor()` to control pixel snapping — eliminates flicker but introduces visible stepping at slow speeds. Text jumps are more distracting than the original jitter. The `filter` hack is strictly better.
|
||||
- **Web Animations API flickers at loop boundary on Firefox.** Even with pixel-precise endpoints (not percentage), Firefox's compositor flickers when an `iterations: Infinity` animation restarts. Fix: use `requestAnimationFrame` with continuous sub-pixel positions and modular wrapping (`if (pos >= halfWidth) pos -= halfWidth`) — no iteration boundary means no flicker. Combined with `filter: blur(0px)` for sub-pixel smoothness. Chrome/Safari use the Web Animations API without issue.
|
||||
- **Marquee speed should be px/sec, not fixed duration.** Original code used 90-150s fixed duration, but wider screens have wider rows, so the same duration = faster movement. Fix: target 15-25 px/sec and calculate duration from measured row width after DOM insertion.
|
||||
|
||||
## Phase 10: Parallelization (if needed)
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ function detectBrowser() {
|
|||
}
|
||||
const browserName = detectBrowser();
|
||||
document.body.classList.add(`browser-${browserName}`);
|
||||
const isFirefox = browserName === "firefox";
|
||||
|
||||
// How many tabs fit in one row?
|
||||
function tabsPerRow() {
|
||||
|
|
@ -109,14 +110,12 @@ function createRow(entries, rowIndex) {
|
|||
const row = document.createElement("div");
|
||||
row.className = "tab-row";
|
||||
|
||||
const pxPerSec = 20 + (rng() * 15); // 20-35 px/sec, consistent across screen sizes
|
||||
const pxPerSec = 15 + (rng() * 10); // 15-25 px/sec, consistent across screen sizes
|
||||
const goLeft = rng() > 0.5;
|
||||
const stagger = rng();
|
||||
|
||||
row.classList.add(goLeft ? "scroll-left" : "scroll-right");
|
||||
|
||||
// Store config — duration calculated after DOM insertion (needs measured width)
|
||||
row._animConfig = { pxPerSec, stagger };
|
||||
// Store config — animation starts after DOM insertion (needs measured width)
|
||||
row._animConfig = { pxPerSec, goLeft, stagger };
|
||||
|
||||
// Add tabs twice so the marquee loops seamlessly (translate -50% = one full set)
|
||||
for (let copy = 0; copy < 2; copy++) {
|
||||
|
|
@ -128,13 +127,39 @@ function createRow(entries, rowIndex) {
|
|||
return row;
|
||||
}
|
||||
|
||||
// Set animation speed based on actual row width (must be called after DOM insertion)
|
||||
// Start animation (must be called after DOM insertion)
|
||||
function startRowAnimation(row) {
|
||||
const { pxPerSec, stagger } = row._animConfig;
|
||||
const { pxPerSec, goLeft, stagger } = row._animConfig;
|
||||
const halfWidth = row.scrollWidth / 2;
|
||||
const duration = halfWidth / pxPerSec;
|
||||
row.style.setProperty("--speed", `${duration}s`);
|
||||
row.style.animationDelay = `${-stagger * duration}s`;
|
||||
|
||||
if (isFirefox) {
|
||||
// Firefox: rAF with continuous sub-pixel positions — no loop boundary,
|
||||
// no compositor iteration restart flicker. filter: blur(0px) in CSS
|
||||
// prevents Firefox's device-pixel snapping.
|
||||
let pos = stagger * halfWidth;
|
||||
let lastTime = null;
|
||||
|
||||
function tick(now) {
|
||||
if (lastTime === null) { lastTime = now; requestAnimationFrame(tick); return; }
|
||||
pos += ((now - lastTime) / 1000) * pxPerSec;
|
||||
lastTime = now;
|
||||
if (pos >= halfWidth) pos -= halfWidth;
|
||||
row.style.transform = `translateX(${goLeft ? -pos : -(halfWidth - pos)}px)`;
|
||||
requestAnimationFrame(tick);
|
||||
}
|
||||
|
||||
requestAnimationFrame(tick);
|
||||
} else {
|
||||
// Chrome/Safari: Web Animations API on the compositor
|
||||
const duration = halfWidth / pxPerSec * 1000;
|
||||
const from = goLeft ? "translateX(0px)" : `translateX(-${halfWidth}px)`;
|
||||
const to = goLeft ? `translateX(-${halfWidth}px)` : "translateX(0px)";
|
||||
|
||||
row.animate(
|
||||
[{ transform: from }, { transform: to }],
|
||||
{ duration, iterations: Infinity, easing: "linear", iterationStart: stagger }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue