chromium/third_party/blink/renderer/core/animation/test_data/transform-animation-on-svg.html

<!DOCTYPE html>
<html>
  <button id="dotsbutton" style="border: 0; background: transparent; padding: 0;">
    <svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 9 9" id="dots">
      <rect x="4" y="4" width="1" height="1" rx=".5" ry=".5" fill="#4285f4" style="transform-origin: 50%; will-change: transform;"></rect>
      <rect x="4" y="4" width="1" height="1" rx=".5" ry=".5" fill="#ea4335" style="transform-origin: 50%; will-change: transform;"></rect>
      <rect x="4" y="4" width="1" height="1" rx=".5" ry=".5" fill="#faBB05" style="transform-origin: 50%; will-change: transform;"></rect>
      <rect x="4" y="4" width="1" height="1" rx=".5" ry=".5" fill="#34a853" style="transform-origin: 50%; will-change: transform;"></rect>
    </svg>
  </button>
<script>
var dots = Array.prototype.slice.call(document.getElementsByTagName('rect'));

function getLineOffsetX(dotIndex) {
    return -3 + dotIndex * 2;
}

function transitionTo(dotIndex, transform, duration = 150, delay = 0) {
    const dot = dots[dotIndex];
    const originalTransform = getComputedStyle(dot).getPropertyValue('transform');
    const animation = dot.animate([
        { transform: originalTransform },
        { transform },
    ], {
        easing: 'cubic-bezier(.0,.0,.2,1)',
        fill: 'both',
        delay,
        duration,
    });
}

function showListening() {
    const LATENCY_PER_DOT_MS = 200;
    for (let i = 0; i < dots.length; i++) {
        const offsetX = getLineOffsetX(i);
        transitionTo(i, `translateX(${offsetX}px)`, 150 + i * LATENCY_PER_DOT_MS);
    }
}

showListening();
</script>
</html>