Firefox jumpiness involving CSS scale and canvas

30 Views Asked by At

In Firefox (121.0b9), when scaling canvas elements, I notice slight jitteriness. I don't experience this in Safari or Chromium. Is there a way to work around this in Firefox?

Here's an example (JSFiddle). (If you're on macOS, activate "Use scroll gesture with modifier keys to zoom" to zoom in on the bottom right corner. On JSFiddle, when you're zoomed in, use "Command + Enter" to rerun.)

<!DOCTYPE html>

<style>
  canvas {
    margin: 100px;
  }
</style>

<canvas width="400" height="400" />

<script>
  window.onload = function () {
    // make canvas
    const elem = document.querySelector("canvas");
    const ctx = elem.getContext("2d");
    ctx.fillStyle = "yellow";
    ctx.fillRect(0, 0, elem.width, elem.height);
    ctx.fillStyle = "black";
    ctx.fillRect(50, 50, 300, 300);

    // scale loop
    const start = performance.now();
    const end = start + 2000;

    const tick = () => {
      const now = performance.now();
      const t = Math.min(1, (now - start) / (end - start));
      const t2 = 1 - Math.pow(2, -10 * t); // easeOutExpo
      elem.style.transform = `scale(${1 - 0.5 * t2})`;
      if (now < end) {
        requestAnimationFrame(tick);
      }
    };

    tick();
  };
</script>

Possibly related: https://bugzilla.mozilla.org/show_bug.cgi?id=663776

1

There are 1 best solutions below

2
X3R0 On

here is a different approach, you could use delta time instead

const tick = (timestamp) => {
        if (!start) {
            start = timestamp || performance.now();
        }
        const elapsed = (timestamp || performance.now()) - start;
        const t = Math.min(1, elapsed / (end - start));
        const t2 = parseFloat(1 - Math.pow(2, -10 * t).toFixed(3)) + 0.0002;
        elem.style.transform = `scale(${1 - (0.5 * parseFloat(t2.toFixed(3)))})`;
        if (elapsed < end - start) {
            requestAnimationFrame(tick);
        }
    };
    requestAnimationFrame(tick);

Demo

jsfiddle link

Snippet

<!DOCTYPE html>

<style>
  canvas {
    margin: 100px;
  }
</style>

<canvas width="400" height="400" />

<script>
  window.onload = function () {
    // make canvas
    const elem = document.querySelector("canvas");
    const ctx = elem.getContext("2d");
    ctx.fillStyle = "yellow";
    ctx.fillRect(0, 0, elem.width, elem.height);
    ctx.fillStyle = "black";
    ctx.fillRect(50, 50, 300, 300);

    // scale loop
    const start = parseFloat(performance.now().toFixed(3));
    const end = start + 2000;

    const tick = (timestamp) => {
        if (!start) {
            start = timestamp || performance.now();
        }
        const elapsed = (timestamp || performance.now()) - start;
        const t = Math.min(1, elapsed / (end - start));
        const t2 = parseFloat(1 - Math.pow(2, -10 * t).toFixed(3)) + 0.0002;
        elem.style.transform = `scale(${1 - (0.5 * parseFloat(t2.toFixed(3)))})`;
        if (elapsed < end - start) {
            requestAnimationFrame(tick);
        }
    };
    requestAnimationFrame(tick);
  };
</script>