Pure CSS Shapes & solids #loop #shape

Spinning coin in pure CSS

Two faces and a band of 24 slats round the edge between them, so it is solid even edge-on.

  • 59 lines of CSS
  • 30 lines of HTML
  • No JavaScript
  • No dependencies
  • Snippet under MIT
GitHub

How it works

  1. Every length is a multiple of one base unit, --u, so the coin is the same share of a gallery card, the editor and a recording canvas. The numbers below are those units.
  2. Front and back are two discs pushed apart with translateZ(±5); the back one is pre-flipped with rotateY(180deg).
  3. backface-visibility: hidden stops you seeing the front face through the back.
  4. A flat disc has no edge, and seen exactly edge-on it draws nothing at all, so a stack of discs between the faces vanishes with them. The rim is a real band instead: 24 slats, each rotateZ(i × 15deg) translateY(-74.5) rotateX(90deg), stood on the circle's edge and facing outwards. Edge-on, the slats facing you are a solid, reeded edge.
  5. Spin the parent. That is all.

Key techniques

  • a rim of rotateZ + translateY slats
  • backface-visibility
  • rotateY spin

Copy-paste code

HTML 30 lines

<div class="scene">
  <div class="coin">
    <b>$</b>
    <i style="--i:0"></i>
    <i style="--i:1"></i>
    <i style="--i:2"></i>
    <i style="--i:3"></i>
    <i style="--i:4"></i>
    <i style="--i:5"></i>
    <i style="--i:6"></i>
    <i style="--i:7"></i>
    <i style="--i:8"></i>
    <i style="--i:9"></i>
    <i style="--i:10"></i>
    <i style="--i:11"></i>
    <i style="--i:12"></i>
    <i style="--i:13"></i>
    <i style="--i:14"></i>
    <i style="--i:15"></i>
    <i style="--i:16"></i>
    <i style="--i:17"></i>
    <i style="--i:18"></i>
    <i style="--i:19"></i>
    <i style="--i:20"></i>
    <i style="--i:21"></i>
    <i style="--i:22"></i>
    <i style="--i:23"></i>
    <b><span class="star"></span></b>
  </div>
</div>

CSS 59 lines

.scene {
  /* one base unit: every length below is a multiple of it, so the coin is the same share of a
     card, the editor, a full screen and a recording canvas */
  --u: 0.39vmin;
  perspective: calc(800 * var(--u));
}

.coin {
  position: relative;
  width: calc(150 * var(--u));
  height: calc(150 * var(--u));
  transform-style: preserve-3d;
  animation: coin-spin 4s linear infinite;
}

/* the rim: 24 slats round the edge, each 20 long (just over the 19.6 a 24th of the circle
   needs, so there are no gaps) and 10 deep, the distance between the faces */
.coin i {
  position: absolute;
  top: 50%;
  left: 50%;
  width: calc(20 * var(--u));
  height: calc(10 * var(--u));
  margin: calc(-5 * var(--u)) 0 0 calc(-10 * var(--u));
  background: repeating-linear-gradient(90deg, #b8801f 0 calc(2 * var(--u)), #8a5a0c calc(2 * var(--u)) calc(3 * var(--u)));
  transform: rotateZ(calc(var(--i) * 15deg)) translateY(calc(-74.5 * var(--u))) rotateX(90deg);
}

.coin b {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  display: grid;
  place-items: center;
  border: calc(6 * var(--u)) solid #e2a93b;
  background: radial-gradient(circle at 35% 30%, #fff1b8, #f1b93d 55%, #c98a1b);
  color: #8a5a0c;
  font: 900 calc(72 * var(--u)) Inter, system-ui;
  backface-visibility: hidden;
  transform: translateZ(calc(5 * var(--u)));
}

.coin b:last-child {
  transform: rotateY(180deg) translateZ(calc(5 * var(--u)));
}

/* the star on the back is drawn, not typed: ★ is in neither font an export carries, so every
   device, and the export service, drew a star of its own */
.coin .star {
  width: calc(56 * var(--u));
  height: calc(54 * var(--u));
  background: currentColor;
  clip-path: polygon(50% 0%, 61.2% 34.5%, 97.6% 34.5%, 68.2% 55.9%, 79.4% 90.5%, 50% 69.1%, 20.6% 90.5%, 31.8% 55.9%, 2.4% 34.5%, 38.8% 34.5%);
}

@keyframes coin-spin {
  from { transform: rotateX(12deg) rotateY(0deg); }
  to   { transform: rotateX(12deg) rotateY(360deg); }
}
Standalone snippet · plain CSS, no build step, no dependencies.