fixed firefox marquee rollover flicker

This commit is contained in:
Joe Lothan 2026-05-21 00:56:50 -04:00
parent fe3d5f7039
commit 0f0acb642f
2 changed files with 37 additions and 10 deletions

View file

@ -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 }
);
}
}