Sine

No dependencies
Code format
React + Tailwind
const MID = 20; // centre line in the 120 x 40 box

// A sine wave as cubic curves, one per half wave. Control points at 0.4244 of
// the half (4 / 3pi) with a height of 4/3 the amplitude match the real curve's
// slope at the crossings and its peak.
function wave(length: number, amp: number, from: number, to: number) {
  const half = length / 2;
  const k = half * 0.4244;
  const h = (amp * 4) / 3;
  let d = `M${from} ${MID}`;
  let side = -1;
  for (let x = from; x < to; x += half) {
    const y = (MID + side * h).toFixed(2);
    d += ` C${(x + k).toFixed(2)} ${y} ${(x + half - k).toFixed(2)} ${y} ${x + half} ${MID}`;
    side = -side;
  }
  return d;
}

const css = `
  .loader-sine { --ink: light-dark(#373a40, #f1f2f3); --echo: light-dark(#6d7179, #7d8087); display: inline-block; font-size: 100px; width: 1.4em; height: 0.4667em; }
  .loader-sine svg { display: block; width: 100%; height: 100%; fill: none; stroke-linecap: round; mask-image: linear-gradient(90deg, transparent, #000 24%, #000 76%, transparent); }
  .loader-sine path { vector-effect: non-scaling-stroke; }
  /* The waves roll one wavelength and start over; the group breathes their height on a slower beat. */
  .loader-sine__swell { transform-box: view-box; transform-origin: 60px ${MID}px; animation: loader-sine-swell 3.6s ease-in-out infinite; }
  .loader-sine__wave { stroke: var(--ink); stroke-width: 3; animation: loader-sine-flow 1.2s linear infinite; }
  .loader-sine__echo { stroke: var(--echo); stroke-width: 2; animation: loader-sine-ebb 2.1s linear infinite; }
  @keyframes loader-sine-flow { to { transform: translateX(-40px); } }
  @keyframes loader-sine-ebb { from { transform: translateX(-64px); } to { transform: translateX(0); } }
  @keyframes loader-sine-swell { 0%, 100% { transform: scaleY(1); } 50% { transform: scaleY(0.42); } }
  @media (prefers-reduced-motion: reduce) {
    .loader-sine__swell, .loader-sine__wave, .loader-sine__echo { animation: none; }
  }
`;

export function Preview() {
  return (
    <>
      <style href="kit-loaders-sine" precedence="kit">
        {css}
      </style>
      <div className="loader-sine" role="status" aria-label="Loading">
        <svg viewBox="0 0 120 40" aria-hidden="true">
          <g className="loader-sine__swell">
            <path className="loader-sine__echo" d={wave(64, 6, -64, 192)} />
            <path className="loader-sine__wave" d={wave(40, 11, -40, 200)} />
          </g>
        </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-sine
Source on GitHub

About this component

Two sine waves flow past each other in opposite directions while their height swells and settles.

Dependencies
None
Difficulty
Beginner
Triggers
continuous, loop, ambient
Techniques
svg, keyframes, transform only
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 both waves rest still.

More loaders

See all
Snow Globe
Spirograph
Stairs
Stamp