Pure CSS Shapes & solids #loop #shape

Cylinder in pure CSS

A glass tube with rings of light rising inside. CSS has no curves in 3D, so the tube is 24 flat strips, each 2r × tan(180° / 24) wide.

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

How it works

  1. There are no curved surfaces in CSS 3D, so approximate: 24 flat strips arranged in a circle.
  2. Every length is a multiple of one base unit, --u, so the tube is the same share of a gallery card, the editor and a recording canvas. The numbers below are those units.
  3. Strip width for a closed ring is 2 × r × tan(180° / n). For r = 80 and n = 24 that is ≈ 21; add 1 so no seams show.
  4. Placement is the carousel trick: rotateY(i × 15deg) translateZ(r). Semi-transparent strips let the far side show through, which is what makes it read as glass.
  5. The rings of light are circles laid flat with rotateX(90deg). They rise by animating translate, which is applied before transform, so "up" is the tube's own vertical.

Key techniques

  • strip width = 2r × tan(180° / n)
  • rotateY + translateZ
  • per-strip shading

Copy-paste code

HTML 30 lines

<div class="scene">
  <div class="cylinder">
    <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></b><s></s>
    <u></u><u></u><u></u>
  </div>
</div>

CSS 78 lines

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

.cylinder {
  --r: calc(80 * var(--u));
  --h: calc(180 * var(--u));
  position: relative;
  width: calc(22 * var(--u));  /* 2 × 80 × tan(7.5deg) + 1 */
  height: var(--h);
  transform-style: preserve-3d;
  animation: cylinder-spin 14s linear infinite;
}

/* see-through strips: the far side of the tube shows through the near one */
.cylinder i {
  position: absolute;
  inset: 0;
  background:
    linear-gradient(rgb(255 255 255 / 0.55) 0 calc(2 * var(--u)), transparent calc(2 * var(--u)) calc(100% - 2 * var(--u)), rgb(255 255 255 / 0.4) calc(100% - 2 * var(--u))),
    linear-gradient(hsl(calc(170 + var(--i) * 6) 85% 62% / 0.5), hsl(calc(250 + var(--i) * 3) 80% 58% / 0.14));
  transform: rotateY(calc(var(--i) * 15deg)) translateZ(var(--r));
}

/* lid (b) and floor (s): glass discs laid flat */
.cylinder b,
.cylinder s {
  position: absolute;
  left: calc(50% - var(--r));
  width: calc(var(--r) * 2);
  height: calc(var(--r) * 2);
  box-sizing: border-box;
  border: calc(1 * var(--u)) solid rgb(255 255 255 / 0.4);
  border-radius: 50%;
  background: radial-gradient(circle, rgb(255 255 255 / 0.16), rgb(139 108 255 / 0.22) 70%);
  transform: rotateX(90deg);
}

.cylinder b { top: calc(var(--r) * -1); }

.cylinder s {
  top: calc(var(--h) - var(--r));
  box-shadow: 0 0 calc(40 * var(--u)) rgb(46 230 214 / 0.55);
}

/* rings of light rising through the tube. `translate` moves them up the tube's own vertical,
   before the rotateX that lays them flat. */
.cylinder u {
  position: absolute;
  top: calc(var(--h) - var(--r) + 8 * var(--u));
  left: calc(50% - var(--r) + 8 * var(--u));
  width: calc(var(--r) * 2 - 16 * var(--u));
  height: calc(var(--r) * 2 - 16 * var(--u));
  box-sizing: border-box;
  border: calc(2 * var(--u)) solid #2ee6d6;
  border-radius: 50%;
  box-shadow: 0 0 calc(14 * var(--u)) #2ee6d6, inset 0 0 calc(14 * var(--u)) #2ee6d6;
  opacity: 0;
  transform: rotateX(90deg);
  animation: rise 3.6s linear infinite;
}

.cylinder u:nth-of-type(2) { animation-delay: -1.2s; }
.cylinder u:nth-of-type(3) { animation-delay: -2.4s; }

@keyframes cylinder-spin {
  from { transform: rotateX(-20deg) rotateY(0deg); }
  to   { transform: rotateX(-20deg) rotateY(360deg); }
}

@keyframes rise {
  0%       { opacity: 0; translate: 0 calc(-4 * var(--u)); }
  15%, 80% { opacity: 1; }
  100%     { opacity: 0; translate: 0 calc(var(--h) * -1 + 12 * var(--u)); }
}
Standalone snippet · plain CSS, no build step, no dependencies.