25 CSS Spinners 05 / 25

Heartbeat Pulse Loader

A vivid red core pulses with a cardiac double-beat rhythm while three concentric ripple rings expand outward and fade, mimicking a real heartbeat waveform.

Pure CSS MIT licensed
Live Demo Open in tab

This is a full-page demo — interact inside the frame above, or open it in the playground for the full-screen experience.

Open in playground

The code

<div class="sp-05">
  <div class="sp-05__stack">
    <div class="sp-05__ring"></div>
    <div class="sp-05__ring"></div>
    <div class="sp-05__ring"></div>
    <div class="sp-05__core"></div>
  </div>
</div>
.sp-05,.sp-05 *,.sp-05 *::before,.sp-05 *::after{box-sizing:border-box;margin:0;padding:0}
.sp-05{
  --bg:#0f0a0a;
  --c:#ff1744;
  --glow:rgba(255,23,68,0.5);
  display:flex;
  align-items:center;
  justify-content:center;
  min-height:100vh;
  background:var(--bg);
}
.sp-05__stack{
  position:relative;
  width:72px;
  height:72px;
}
.sp-05__ring{
  position:absolute;
  border-radius:50%;
  border:2px solid var(--c);
  opacity:0;
  animation:sp-05-ripple 1.8s ease-out infinite;
}
.sp-05__ring:nth-child(1){
  inset:0;
  animation-delay:0s;
}
.sp-05__ring:nth-child(2){
  inset:0;
  animation-delay:0.6s;
}
.sp-05__ring:nth-child(3){
  inset:0;
  animation-delay:1.2s;
}
.sp-05__core{
  position:absolute;
  inset:22px;
  border-radius:50%;
  background:var(--c);
  box-shadow:0 0 16px var(--glow),0 0 32px var(--glow);
  animation:sp-05-beat 1.8s ease-in-out infinite;
}
@keyframes sp-05-ripple{
  0%{transform:scale(0.3);opacity:0.9}
  100%{transform:scale(1.6);opacity:0}
}
@keyframes sp-05-beat{
  0%,100%{transform:scale(1)}
  15%{transform:scale(1.25)}
  30%{transform:scale(0.95)}
  45%{transform:scale(1.1)}
}
@media (prefers-reduced-motion: reduce){
  .sp-05__ring,.sp-05__core{animation:none;opacity:1}
}

How this works

The core element uses a multi-step sp-05-beat keyframe that replicates a cardiac double-peak: a first strong beat at 15% (scale 1.25), a rebound at 30% (scale 0.95), a secondary beat at 45% (scale 1.1), then return to baseline — matching the QRS complex shape of an electrocardiogram.

Three staggered ripple rings are all position:absolute;inset:0, starting at scale(0.3) with full opacity and animating to scale(1.6) at zero opacity. Each ring starts 0.6s later than the previous, so at any moment one ring is expanding from centre, one is mid-ripple, and one is fading at the periphery.

Customize

  • Change the heart rate by adjusting the animation duration from 1.8s — shorter durations (e.g. 0.9s) simulate elevated heart rate.
  • Edit --c to any colour for brand adaptation; the glow and rings inherit the variable automatically.
  • Add a fourth ripple ring at animation-delay:1.8s to fill the gap at the end of the cycle.
  • Scale the core element size via inset:22px — decrease to make the dot smaller relative to the ripple area.
  • Reduce scale values in sp-05-beat (e.g. max 1.1 instead of 1.25) for a subtler, restful pulse more suitable as a passive loading indicator.

Watch out for

  • The transform:scale on the core alongside box-shadow can cause sub-pixel blur on retina displays at the compressed 0.95 scale step — acceptable visually but worth noting.
  • Three simultaneous infinite ring animations on the same stacking layer can create compositing overhead; use will-change:transform,opacity on the rings if profiling shows jank.
  • The ripple rings use inset:0 so they match the wrapper size exactly — if the wrapper has padding, the rings will appear smaller than intended.

Browser support

ChromeSafariFirefoxEdge
60+ 12+ 60+ 60+

Fully compositor-based; no vendor prefixes required in modern browsers.

Search CodeFronts

Loading…