Hamster

No dependencies
Code format
React + Tailwind
const css = `
  @keyframes loader-hamster-wheel { to { transform: rotate(360deg); } }
  @keyframes loader-hamster-bob { 0%, 100% { transform: translateY(0) rotate(0deg); } 50% { transform: translateY(-1.6px) rotate(-2deg); } }
  @keyframes loader-hamster-leg { 0%, 100% { transform: rotate(32deg); } 50% { transform: rotate(-32deg); } }
  .loader-hamster { position: relative; width: 120px; height: 120px; }
  .loader-hamster svg { width: 100%; height: 100%; overflow: visible; }
  .loader-hamster__stand { fill: none; stroke: light-dark(#cbced3, #393c41); stroke-width: 3.5; stroke-linecap: round; }
  .loader-hamster__base { stroke: light-dark(#6d7179, #7d8087); stroke-width: 3; stroke-linecap: round; }
  .loader-hamster__wheel { transform-origin: 50px 44px; animation: loader-hamster-wheel 1.4s linear infinite; }
  .loader-hamster__rim { fill: none; stroke: light-dark(#6d7179, #7d8087); stroke-width: 2.5; }
  .loader-hamster__inner { fill: none; stroke: light-dark(#cbced3, #393c41); stroke-width: 1.2; }
  .loader-hamster__rungs { fill: none; stroke: light-dark(#6d7179, #7d8087); stroke-width: 4; stroke-dasharray: 1.2 5.9; }
  .loader-hamster__spoke { stroke: light-dark(#cbced3, #393c41); stroke-width: 1.6; }
  .loader-hamster__hub { fill: light-dark(#5f626a, #a7abb2); }
  .loader-hamster__run { animation: loader-hamster-bob 0.13s ease-in-out infinite; }
  .loader-hamster__fur { fill: light-dark(#50535b, #c6c8cd); }
  .loader-hamster__belly { fill: light-dark(#cbced3, #e5e6e9); }
  .loader-hamster__ear { fill: light-dark(#5f626a, #a7abb2); }
  .loader-hamster__eye { fill: light-dark(#fbfcfd, #121417); }
  .loader-hamster__nose { fill: light-dark(#6d7179, #7d8087); }
  .loader-hamster__leg { stroke: light-dark(#50535b, #c6c8cd); stroke-width: 2.6; stroke-linecap: round; animation: loader-hamster-leg 0.26s ease-in-out infinite; }
  .loader-hamster__leg--far { stroke: light-dark(#6d7179, #7d8087); animation-delay: -0.13s; }
  .loader-hamster__leg--front { transform-origin: 6px -4px; }
  .loader-hamster__leg--back { transform-origin: -6px -4px; animation-direction: reverse; }
  @media (prefers-reduced-motion: reduce) {
    .loader-hamster__wheel, .loader-hamster__run, .loader-hamster__leg { animation: none; }
  }
`;

const SPOKES = [0, 60, 120, 180, 240, 300];

export function HamsterLoader() {
  return (
    <>
      <style>
        {css}
      </style>
      <div className="loader-hamster" role="status" aria-label="Loading">
        <svg viewBox="0 0 100 100" aria-hidden="true">
          <path className="loader-hamster__stand" d="M50 44L32 94M50 44L68 94" />
          <path className="loader-hamster__base" d="M24 94H76" />
          <g className="loader-hamster__wheel">
            {SPOKES.map((a) => (
              <line key={a} className="loader-hamster__spoke" x1="50" y1="44" x2={50 + 32 * Math.cos((a * Math.PI) / 180)} y2={44 + 32 * Math.sin((a * Math.PI) / 180)} />
            ))}
            <circle className="loader-hamster__rungs" cx="50" cy="44" r="34" />
            <circle className="loader-hamster__inner" cx="50" cy="44" r="32" />
            <circle className="loader-hamster__rim" cx="50" cy="44" r="36" />
            <circle className="loader-hamster__hub" cx="50" cy="44" r="3.5" />
          </g>
          {/* Running uphill just past the bottom of the wheel. */}
          <g transform="translate(55 74) rotate(-14)">
            <g className="loader-hamster__run">
              <line className="loader-hamster__leg loader-hamster__leg--far loader-hamster__leg--front" x1="6" y1="-4" x2="7" y2="1.5" />
              <line className="loader-hamster__leg loader-hamster__leg--far loader-hamster__leg--back" x1="-6" y1="-4" x2="-7" y2="1.5" />
              <ellipse className="loader-hamster__fur" cx="-1" cy="-9" rx="12" ry="8.5" />
              <circle className="loader-hamster__fur" cx="8" cy="-11" r="6.8" />
              <ellipse className="loader-hamster__belly" cx="2" cy="-4.5" rx="8" ry="3.4" />
              <circle className="loader-hamster__ear" cx="5" cy="-17" r="2.8" />
              <circle className="loader-hamster__fur" cx="-13" cy="-8" r="1.6" />
              <circle className="loader-hamster__eye" cx="10.5" cy="-12.5" r="1.2" />
              <circle className="loader-hamster__nose" cx="14.6" cy="-10" r="1.1" />
              <line className="loader-hamster__leg loader-hamster__leg--front" x1="6" y1="-4" x2="7" y2="1.5" />
              <line className="loader-hamster__leg loader-hamster__leg--back" x1="-6" y1="-4" x2="-7" y2="1.5" />
            </g>
          </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-hamster
Source on GitHub

About this component

A hamster running flat out in its wheel: legs scrambling, body bobbing, the wheel spinning round under it.

Dependencies
None
Difficulty
Beginner
Triggers
continuous, loop
Techniques
keyframes, svg, 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 is decorative. Under prefers-reduced-motion the hamster and wheel are still.

More loaders

See all
Jellyfish
Juggle
Spirit level
Locomotive