AI CSS Animations

Foucault Pendulum Loader

Seen from above, a pendulum swings while its plane slowly turns, knocking over the ring of pins one by one before they pop back up. Pin delays are timed to the exact swing.

Also known as: foucault pendulum animation, pendulum knocking pins, rotating pendulum loader.

HTML

<div class="ldr-foucault-pendulum" role="status" aria-label="Loading">
  <span class="ldr-foucault-pendulum-pin" style="--k: 0; --t: 3s"></span>
  <span class="ldr-foucault-pendulum-pin" style="--k: 1; --t: 0.5s"></span>
  <span class="ldr-foucault-pendulum-pin" style="--k: 2; --t: 4s"></span>
  <span class="ldr-foucault-pendulum-pin" style="--k: 3; --t: 1.5s"></span>
  <span class="ldr-foucault-pendulum-pin" style="--k: 4; --t: 5s"></span>
  <span class="ldr-foucault-pendulum-pin" style="--k: 5; --t: 2.5s"></span>
  <span class="ldr-foucault-pendulum-pin" style="--k: 6; --t: 0s"></span>
  <span class="ldr-foucault-pendulum-pin" style="--k: 7; --t: 3.5s"></span>
  <span class="ldr-foucault-pendulum-pin" style="--k: 8; --t: 1s"></span>
  <span class="ldr-foucault-pendulum-pin" style="--k: 9; --t: 4.5s"></span>
  <span class="ldr-foucault-pendulum-pin" style="--k: 10; --t: 2s"></span>
  <span class="ldr-foucault-pendulum-pin" style="--k: 11; --t: 5.5s"></span>
  <span class="ldr-foucault-pendulum-plane"><span class="ldr-foucault-pendulum-bob"></span></span>
</div>

CSS

.ldr-foucault-pendulum {
  --s: var(--ldr-size, 48px);
  --c: var(--ldr-color, #3b82f6);
  position: relative;
  width: var(--s);
  height: var(--s);
}
.ldr-foucault-pendulum::before {
  content: "";
  position: absolute;
  inset: 0;
  box-sizing: border-box;
  border: 1px solid color-mix(in srgb, var(--ldr-color, #3b82f6) 30%, transparent);
  border-radius: 50%;
}
.ldr-foucault-pendulum-pin {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: rotate(calc(var(--k) * 30deg)) translateX(calc(var(--s) * 0.42));
}
.ldr-foucault-pendulum-pin::before {
  content: "";
  position: absolute;
  top: calc(var(--s) / -24);
  left: calc(var(--s) / -24);
  width: calc(var(--s) / 12);
  height: calc(var(--s) / 12);
  border-radius: 50%;
  background: color-mix(in srgb, var(--ldr-color, #3b82f6) 70%, transparent);
  animation: ldr-foucault-pendulum-knock 6s infinite;
  animation-delay: var(--t);
}
/* The swing plane turns once every 6s; the bob reaches a pin every half swing. */
.ldr-foucault-pendulum-plane {
  position: absolute;
  inset: 0;
  animation: ldr-foucault-pendulum-precess 6s linear infinite;
}
.ldr-foucault-pendulum-plane::before {
  content: "";
  position: absolute;
  top: 50%;
  left: 12%;
  right: 12%;
  height: 1px;
  background: color-mix(in srgb, var(--ldr-color, #3b82f6) 25%, transparent);
}
.ldr-foucault-pendulum-bob {
  position: absolute;
  top: 50%;
  left: 50%;
  width: calc(var(--s) / 6);
  height: calc(var(--s) / 6);
  margin: calc(var(--s) / -12);
  border-radius: 50%;
  background: var(--c);
  animation: ldr-foucault-pendulum-swing 0.5s cubic-bezier(0.37, 0, 0.63, 1) infinite alternate;
}
@keyframes ldr-foucault-pendulum-precess {
  to { transform: rotate(360deg); }
}
@keyframes ldr-foucault-pendulum-swing {
  from { transform: translateX(calc(var(--s) * -0.295)); }
  to   { transform: translateX(calc(var(--s) * 0.295)); }
}
@keyframes ldr-foucault-pendulum-knock {
  0%       { transform: translateX(0) scale(1); opacity: 1; animation-timing-function: ease-out; }
  4%, 78%  { transform: translateX(calc(var(--s) / 20)) scale(0.5); opacity: 0.3; animation-timing-function: cubic-bezier(0.3, 1.6, 0.6, 1); }
  88%, 100% { transform: translateX(0) scale(1); opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .ldr-foucault-pendulum, .ldr-foucault-pendulum *, .ldr-foucault-pendulum::before, .ldr-foucault-pendulum::after, .ldr-foucault-pendulum *::before, .ldr-foucault-pendulum *::after {
    animation-duration: 3s;
  }
}

Customizing this loader

  • Color: set --ldr-color on the element or a parent. Currently previewing #3b82f6.
  • Size: set --ldr-size. Every dimension in the snippet is derived from it. Currently previewing 64px.
  • Speed: change the duration on the animation shorthand. Delays on the children are relative, so they keep their rhythm.
  • Accessibility: the root carries role="status" and an aria-label so screen readers announce the wait. The reduced-motion rule at the end slows the animation for users who asked for less motion.

Similar loaders