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.

  • 41 lines of CSS
  • 5 lines of HTML
  • No JavaScript
  • No dependencies
  • MIT licensed

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.

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 41 lines

.scene {
  perspective: 800px;
}

.sign {
  position: relative;
  width: 220px;
  height: 190px;
  transform-origin: top center;
  animation: swing 2.2s ease-in-out infinite alternate;
}

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

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

@keyframes swing {
  from { transform: rotateY(-18deg) rotateX(-32deg); }
  to   { transform: rotateY(-18deg) rotateX(32deg); }
}

Sass source 46 lines

.d-sign {
  position: relative;
  width: 170px;
  height: 150px;
  transform-style: preserve-3d;
  transform-origin: top center;
  animation: d-sign-swing 2.2s ease-in-out infinite alternate;

  // the rail it hangs from + two cords
  &::before {
    content: '';
    position: absolute;
    inset: 0 12px auto;
    height: 62px;
    border: solid var(--muted);
    border-width: 4px 2px 0;
  }

  &__board {
    position: absolute;
    inset: 62px 0 0;
    display: grid;
    place-items: center;
    border: 3px solid var(--warm);
    border-radius: 12px;
    background: #1b1408;
    color: var(--warm);
    font-size: 36px;
    font-weight: 900;
    letter-spacing: 0.12em;
    text-shadow: 0 0 14px var(--warm);
    box-shadow:
      0 0 24px color-mix(in srgb, var(--warm) 45%, transparent),
      inset 0 0 18px color-mix(in srgb, var(--warm) 25%, transparent);
  }
}

@keyframes d-sign-swing {
  from {
    transform: translateY(-20px) rotateY(-18deg) rotateX(-32deg);
  }

  to {
    transform: translateY(-20px) rotateY(-18deg) rotateX(32deg);
  }
}
Standalone snippet · plain CSS, no build step, no dependencies.