Pure CSS Shapes & solids #loop #shape

Cone in pure CSS

Sixteen thin triangles on a circle, each leaning in by atan(radius / height) so all the tips meet on the axis, over a flat disc base.

  • 69 lines of CSS
  • 23 lines of HTML
  • No JavaScript
  • No dependencies
  • Snippet under MIT
GitHub

How it works

  1. Place 16 thin triangles on a circle like carousel panels: rotateY(i × 22.5deg) translateZ(r). Each is 2r × tan(180° / 16) wide, so their bottom edges close the ring.
  2. Hinge them on the bottom edge and lean them back by atan(r / h). That is exactly the angle at which every tip lands on the axis, h above the base.
  3. The triangle element must be as tall as the slant, √(h² + r²), not h, because leaning shortens it.
  4. One element cannot run two transform animations, so the rocking lives on a wrapper and the spin on its child.
  5. Every length is a multiple of one base unit, --u, so the cone is the same share of a gallery card, the editor and a recording canvas. The numbers above are those units.
  6. The cone is centred on what it draws, not on its layout box: the base is a circle lying flat at the bottom, so tipping it back swings half that circle below the base while the tip only comes down. The rock carries a translateY that answers it, the same in both poses.

Key techniques

  • triangle width = 2r × tan(180° / n)
  • lean = atan(r / h)
  • rocking wrapper + spinning child
  • rotateY(i × 22.5deg) per face

Copy-paste code

HTML 23 lines

<div class="scene">
  <div class="cone">
    <div class="cone-body">
      <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>
      <b></b>
    </div>
  </div>
</div>

CSS 69 lines

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

/* wrapper: rocks back and forth */
.cone {
  transform-style: preserve-3d;
  animation: rock 7s ease-in-out infinite alternate;
}

/* child: spins */
.cone-body {
  position: relative;
  width: calc(19.89 * var(--u)); /* triangle base = 2 × 50 × tan(180deg / 16) */
  height: calc(110 * var(--u));  /* cone height */
  transform-style: preserve-3d;
  animation: spin 10s linear infinite;
}

.cone-body i {
  --hue: calc(293 + 40 * cos(var(--i) * 22.5deg)); /* pink ... violet ... pink */
  --edge: hsl(var(--hue) 90% 68% / 0.8);
  position: absolute;
  left: 0;
  bottom: 0;
  width: 100%;
  height: calc(120.83 * var(--u)); /* slant = √(110² + 50²) */
  transform-origin: 50% 100%;
  /* stand on the base circle, then lean in by atan(50 / 110) */
  transform: rotateY(calc(var(--i) * 22.5deg)) translateZ(calc(50 * var(--u))) rotateX(24.44deg);
  clip-path: polygon(50% 0, 0 100%, 100% 100%);
  /* the edge lines fade in over 1.5 units instead of starting hard: a hard line on a long,
     thin, slanted triangle is sampled unevenly and breaks into dashes */
  background:
    linear-gradient(to top left, transparent calc(50% - 2.5 * var(--u)), var(--edge) calc(50% - 1 * var(--u)) 50%, transparent 50%) left / 50% 100% no-repeat,
    linear-gradient(to top right, transparent calc(50% - 2.5 * var(--u)), var(--edge) calc(50% - 1 * var(--u)) 50%, transparent 50%) right / 50% 100% no-repeat,
    linear-gradient(to top, hsl(var(--hue) 90% 68% / 0.45), hsl(var(--hue) 90% 68% / 0.14));
}

/* base disc through the triangles' corners: 2 × 50 / cos(180deg / 16) */
.cone-body b {
  position: absolute;
  left: calc(50% - 50.98 * var(--u));
  top: calc(100% - 50.98 * var(--u));
  /* its border is inside that diameter, so the disc stays centred on the cone's axis */
  box-sizing: border-box;
  width: calc(101.96 * var(--u));
  height: calc(101.96 * var(--u));
  border-radius: 50%;
  background: rgb(46 230 214 / 0.26);
  border: calc(1 * var(--u)) solid rgb(46 230 214 / 0.75);
  box-shadow: inset 0 0 calc(24 * var(--u)) rgb(46 230 214 / 0.3);
  transform: rotateX(90deg);
}

/* The cone is centred on what it draws, not on its layout box: the base disc is a circle lying
   flat at the bottom, so tipping it back swings half the disc BELOW the base, while the tip only
   comes down. The lift answers that, and it is the same in both poses so the rock stays a rock. */
@keyframes rock {
  from { transform: translateY(calc(-12 * var(--u))) rotateX(-30deg) rotateZ(-6deg); }
  to   { transform: translateY(calc(-12 * var(--u))) rotateX(24deg) rotateZ(6deg); }
}

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