Pure CSS Shapes & solids #loop #shape

Twisting puzzle cube in pure CSS

A 3×3×3 cube built as three layers that take turns twisting a quarter turn. Each layer is one flat box with the stickers painted on by gradients.

  • 72 lines of CSS
  • 7 lines of HTML
  • No JavaScript
  • No dependencies
  • Snippet under MIT
GitHub

How it works

  1. 27 cubelets would be 162 faces. But a horizontal layer only ever turns as one piece, so each layer is one flat box (6 faces), and the 3 × 3 stickers are painted on with two repeating gradients.
  2. All three layers run the same 12-second timeline: a quarter turn at 0%, 25%, 50% and 75%, holding in between. Negative delays of -11s and -10s shift layers 2 and 3 one and two seconds later, so they take turns.
  3. After four quarter turns every layer is back at 360°, the same as 0°, so the loop is seamless and the cube solves itself each cycle.
  4. The black faces between layers use backface-visibility: hidden: the two touching faces point in opposite directions, so only one is ever drawn and they never flicker.
  5. Every length is a multiple of one base unit, --u, tied to the canvas, so the cube and the black lines between its stickers are the same share of a gallery card, the editor and a recording canvas.

Key techniques

  • one box per layer, not 27 cubelets
  • gradient sticker grid
  • shared keyframes + negative delays take turns
  • backface-visibility on inner faces

Copy-paste code

HTML 7 lines

<div class="scene">
  <div class="rubik">
    <div class="layer"><i></i><i></i><i></i><i></i><b class="top"></b><b></b></div>
    <div class="layer"><i></i><i></i><i></i><i></i><b></b><b></b></div>
    <div class="layer"><i></i><i></i><i></i><i></i><b></b><b class="bottom"></b></div>
  </div>
</div>

CSS 72 lines

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

.rubik {
  position: relative;
  width: calc(120 * var(--u));
  height: calc(120 * var(--u));
  transform-style: preserve-3d;
  transform: rotateX(-28deg) rotateY(-38deg);
}

/* one layer = one flat box, 120 x 40 x 120 */
.layer {
  position: absolute;
  left: 0;
  width: calc(120 * var(--u));
  height: calc(40 * var(--u));
  transform-style: preserve-3d;
  animation: twist 12s cubic-bezier(0.65, 0, 0.35, 1) infinite;
}

.layer:nth-child(1) { top: 0; }
.layer:nth-child(2) { top: calc(40 * var(--u)); animation-delay: -11s; } /* one second later */
.layer:nth-child(3) { top: calc(80 * var(--u)); animation-delay: -10s; } /* two seconds later */

/* stickers: black lines on the tile edges, 3 across and --row down */
.layer > * {
  position: absolute;
  left: 0;
  background-color: var(--c, #10121f);
  background-image:
    linear-gradient(90deg, #10121f calc(3 * var(--u)), transparent calc(3 * var(--u)) calc(100% - 3 * var(--u)), #10121f calc(100% - 3 * var(--u))),
    linear-gradient(#10121f calc(3 * var(--u)), transparent calc(3 * var(--u)) calc(100% - 3 * var(--u)), #10121f calc(100% - 3 * var(--u)));
  background-size: 33.334% 100%, 100% var(--row, 100%);
  backface-visibility: hidden;
}

/* four sides: strips of three stickers */
.layer i { top: 0; width: calc(120 * var(--u)); height: calc(40 * var(--u)); }
.layer i:nth-child(1) { --c: #8b6cff; transform: translateZ(calc(60 * var(--u))); }
.layer i:nth-child(2) { --c: #2ee6d6; transform: rotateY(90deg) translateZ(calc(60 * var(--u))); }
.layer i:nth-child(3) { --c: #ff4d9d; transform: rotateY(180deg) translateZ(calc(60 * var(--u))); }
.layer i:nth-child(4) { --c: #ffb547; transform: rotateY(-90deg) translateZ(calc(60 * var(--u))); }

/* top and bottom of a layer: black inside the cube, stickers only on the outside */
.layer b {
  --row: 33.334%;
  top: calc(-40 * var(--u));
  width: calc(120 * var(--u));
  height: calc(120 * var(--u));
  transform: rotateX(90deg) translateZ(calc(20 * var(--u)));
}

.layer b + b {
  transform: rotateX(-90deg) translateZ(calc(20 * var(--u)));
}

.layer .top    { --c: #f2f3ff; }
.layer .bottom { --c: #4d8dff; }

/* a quarter turn in the first 6% of each quarter, then hold */
@keyframes twist {
  0%       { transform: rotateY(0deg); }
  6%, 25%  { transform: rotateY(90deg); }
  31%, 50% { transform: rotateY(180deg); }
  56%, 75% { transform: rotateY(270deg); }
  81%, 100% { transform: rotateY(360deg); }
}
Standalone snippet · plain CSS, no build step, no dependencies.