Pure CSS Scenes & objects #loop

Hanging sign in pure CSS

A pendulum is just rotateX around the top edge with an ease-in-out alternate.

  • 46 lines of CSS
  • 5 lines of HTML
  • No JavaScript
  • No dependencies
  • Snippet under MIT
GitHub

How it works

  1. A pendulum is a rotation around the point it hangs from: transform-origin: top center.
  2. Animate rotateX from −32° to +32° with animation-direction: alternate.
  3. ease-in-out is what makes it physical — slow at the ends of the swing, fast through the middle.
  4. A constant rotateY in both keyframes turns the sign slightly so you can see the swing.
  5. A delay of minus half a swing (-1.1s) starts the loop with the sign hanging straight down, so a paused sign hangs at rest in the middle of the frame instead of held up at one end of its swing.
  6. Every length is a multiple of one base unit, --u, tied to the canvas: the sign is 220 × 190 units, so it is the same share of a gallery card, the editor and a recording canvas, and its swing toward you still stays inside the frame.

Key techniques

  • transform-origin: top
  • animation-direction: alternate
  • ease-in-out = pendulum

Copy-paste code

HTML 5 lines

<div class="scene">
  <div class="sign">
    <div class="board">OPEN</div>
  </div>
</div>

CSS 46 lines

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

.sign {
  position: relative;
  width: calc(220 * var(--u));
  height: calc(190 * var(--u));
  transform-origin: top center;
  animation: swing 2.2s ease-in-out -1.1s infinite alternate;
}

/* rail + two cords */
.sign::before {
  content: '';
  position: absolute;
  inset: 0 calc(16 * var(--u)) auto;
  height: calc(80 * var(--u));
  border: solid #949bc0;
  border-width: calc(4 * var(--u)) calc(2 * var(--u)) 0;
}

.board {
  position: absolute;
  inset: calc(80 * var(--u)) 0 0;
  display: grid;
  place-items: center;
  border: calc(3 * var(--u)) solid #ffb547;
  border-radius: calc(12 * var(--u));
  background: #1b1408;
  color: #ffb547;
  font: 900 calc(45 * var(--u)) Inter, system-ui;
  letter-spacing: 0.12em;
  text-shadow: 0 0 calc(14 * var(--u)) #ffb547;
  box-shadow: 0 0 calc(24 * var(--u)) rgb(255 181 71 / 0.45);
}

@keyframes swing {
  from { transform: rotateY(-18deg) rotateX(-32deg); }
  to   { transform: rotateY(-18deg) rotateX(32deg); }
}
Standalone snippet · plain CSS, no build step, no dependencies.