Pancake

No dependencies
Code format
React + Tailwind
const css = `
  /* Two flips per cycle, so the pancake comes back the same side up. */
  @keyframes loader-pancake-flip {
    0% { transform: translate(0, 0) rotate(0deg) scale(1, 1); }
    7% { transform: translate(0, 1px) rotate(0deg) scale(1.04, 0.85); }
    12% { transform: translate(0, -6px) rotate(-10deg) scale(0.98, 1.08); animation-timing-function: cubic-bezier(0.33, 0.67, 0.67, 1); }
    22% { transform: translate(3px, -40px) rotate(-95deg) scale(1, 1); animation-timing-function: cubic-bezier(0.33, 0, 0.67, 0.33); }
    32% { transform: translate(0, 0) rotate(-180deg) scale(1, 1); }
    34.5% { transform: translate(0, 1px) rotate(-180deg) scale(1.1, 0.72); }
    38%, 50% { transform: translate(0, 0) rotate(-180deg) scale(1, 1); }
    57% { transform: translate(0, 1px) rotate(-180deg) scale(1.04, 0.85); }
    62% { transform: translate(0, -6px) rotate(-190deg) scale(0.98, 1.08); animation-timing-function: cubic-bezier(0.33, 0.67, 0.67, 1); }
    72% { transform: translate(3px, -40px) rotate(-275deg) scale(1, 1); animation-timing-function: cubic-bezier(0.33, 0, 0.67, 0.33); }
    82% { transform: translate(0, 0) rotate(-360deg) scale(1, 1); }
    84.5% { transform: translate(0, 1px) rotate(-360deg) scale(1.1, 0.72); }
    88%, 100% { transform: translate(0, 0) rotate(-360deg) scale(1, 1); }
  }
  /* The pan dips, flicks up to throw, then gives a little to catch. */
  @keyframes loader-pancake-pan {
    0% { transform: rotate(0deg); }
    14% { transform: rotate(-5deg); }
    24% { transform: rotate(8deg); animation-timing-function: ease-out; }
    38%, 58% { transform: rotate(0deg); }
    66% { transform: rotate(-3deg); }
    76% { transform: rotate(0.6deg); }
    84%, 100% { transform: rotate(0deg); }
  }
  @keyframes loader-pancake-steam { 0% { stroke-dashoffset: 1; opacity: 0; } 30% { opacity: 0.7; } 100% { stroke-dashoffset: -1; opacity: 0; } }
  .loader-pancake { position: relative; width: 134px; height: 100px; }
  .loader-pancake svg { width: 100%; height: 100%; overflow: visible; }
  .loader-pancake__pan { transform-origin: 102px 60px; animation: loader-pancake-pan 1.6s ease-in-out infinite; }
  .loader-pancake__cake { transform-origin: 42px 54px; animation: loader-pancake-flip 3.2s ease-in-out infinite; }
  .loader-pancake__top { fill: light-dark(#50535b, #c6c8cd); }
  .loader-pancake__under { fill: light-dark(#cbced3, #7d8087); }
  .loader-pancake__body { fill: light-dark(#cbced3, #393c41); stroke: light-dark(#6d7179, #7d8087); stroke-width: 1.2; stroke-linejoin: round; }
  .loader-pancake__rim { stroke: light-dark(#5f626a, #a7abb2); stroke-width: 2.5; stroke-linecap: round; }
  .loader-pancake__handle { fill: none; stroke: light-dark(#6d7179, #7d8087); stroke-width: 4.5; stroke-linecap: round; }
  .loader-pancake__grip { fill: none; stroke: light-dark(#dbdde1, #2e3035); stroke-width: 6.5; stroke-linecap: round; }
  .loader-pancake__steam { fill: none; stroke: light-dark(#6d7179, #7d8087); stroke-width: 1.6; stroke-linecap: round; stroke-dasharray: 1; animation: loader-pancake-steam 2.4s ease-in-out infinite; }
  .loader-pancake__steam + .loader-pancake__steam { animation-delay: -1.2s; }
  @media (prefers-reduced-motion: reduce) {
    .loader-pancake__pan, .loader-pancake__cake, .loader-pancake__steam { animation: none; }
  }
`;

export function PancakeLoader() {
  return (
    <>
      <style>
        {css}
      </style>
      <div className="loader-pancake" role="status" aria-label="Loading">
        <svg viewBox="0 0 112 84" aria-hidden="true">
          <path className="loader-pancake__steam" pathLength={1} d="M32 44Q28 36 32 30Q36 24 32 16" />
          <path className="loader-pancake__steam" pathLength={1} d="M52 44Q48 36 52 30Q56 24 52 16" />
          <g className="loader-pancake__pan">
            <g className="loader-pancake__cake">
              <rect className="loader-pancake__under" x="22" y="51" width="40" height="6" rx="3" />
              <rect className="loader-pancake__top" x="22" y="51" width="40" height="3.2" rx="1.6" />
            </g>
            <path className="loader-pancake__body" d="M14 54H70L66 64Q65 67 61 67H23Q19 67 18 64Z" />
            <path className="loader-pancake__rim" d="M13 54H71" />
            <path className="loader-pancake__handle" d="M70 57L86 59" />
            <path className="loader-pancake__grip" d="M86 59L104 61" />
          </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-pancake
Source on GitHub

About this component

A frying pan dips and flicks, the pancake flips over in the air and lands back with a squash, again and again.

Dependencies
None
Difficulty
Intermediate
Triggers
continuous, loop, spring
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 pancake rests in the pan.

More loaders

See all
Popcorn
Receipt
Snail
Toaster