Stretch

No dependencies
Code format
React + Tailwind
const T = 1.4;
const R = 20;
const C = 2 * Math.PI * R;
// Both ends of the arc go round exactly once a cycle, but their speeds rise and
// fall half a cycle apart: the head leads while the tail slows, then the tail
// races while the head slows. The arc stretches and shrinks with no seam.
const A = 0.3;
const MIN = 0.05;
const N = 40;
const frames = Array.from({ length: N + 1 }, (_, i) => {
  const u = i / N;
  const g = (1 - Math.cos(2 * Math.PI * u)) / 2;
  const tail = u - A * g;
  const len = (MIN + 2 * A * g) * C;
  return `${((u * 100).toFixed(2))}% { stroke-dasharray: ${len.toFixed(2)} ${(C - len).toFixed(2)}; stroke-dashoffset: ${(-tail * C).toFixed(2)}; }`;
}).join('\n    ');

const css = `
  .loader-stretch { --ink: light-dark(#373a40, #f1f2f3); --track: light-dark(#dbdde1, #2e3035); display: inline-block; font-size: 88px; width: 1em; height: 1em; }
  .loader-stretch svg { display: block; width: 100%; height: 100%; rotate: -90deg; fill: none; stroke-width: 4; stroke-linecap: round; }
  .loader-stretch__track { stroke: var(--track); }
  .loader-stretch__arc { stroke: var(--ink); animation: loader-stretch-arc ${T}s linear infinite; }
  @keyframes loader-stretch-arc {
    ${frames}
  }
  @media (prefers-reduced-motion: reduce) {
    .loader-stretch__arc { animation: none; stroke-dasharray: ${(0.3 * C).toFixed(2)} ${(0.7 * C).toFixed(2)}; }
  }
`;

export function Preview() {
  return (
    <>
      <style href="kit-loaders-stretch" precedence="kit">
        {css}
      </style>
      <div className="loader-stretch" role="status" aria-label="Loading">
        <svg viewBox="0 0 50 50" aria-hidden="true">
          <circle className="loader-stretch__track" cx="25" cy="25" r={R} />
          <circle className="loader-stretch__arc" cx="25" cy="25" r={R} />
        </svg>
      </div>
    </>
  );
}

Free to use in any project, commercial included. MIT-0, no attribution required. Licence

Install with shadcn

npx shadcn@latest add @movin/loader-stretch
Source on GitHub

About this component

A single arc runs round a faint ring, stretching while its head pulls ahead and shrinking while its tail catches up.

Dependencies
None
Difficulty
Intermediate
Triggers
continuous, loop, draw
Techniques
svg, stroke dashoffset, keyframes
Reduced motion
Respected
Theme
Light and dark (follows your page's color-scheme)

The root has role="status" and aria-label="Loading"; everything inside it is aria-hidden. Under prefers-reduced-motion the arc rests at a third of the ring.

More loaders

See all
Swap
Tape Measure
Triad
Twist