CSS + JS Shapes & solids #generated #controls #loop

Point sphere in CSS + JavaScript

JS places the dots on a Fibonacci sphere once; the spin itself is a plain CSS animation.

  • 27 lines of CSS
  • 3 lines of HTML
  • 13 lines of JS
  • No dependencies
  • MIT licensed

How it works

  1. Writing hundreds of dots by hand is not realistic — JS generates them once from the Fibonacci-sphere formula.
  2. Each dot gets rotateY(longitude) rotateX(latitude) translateZ(radius): aim, then push outward. Same idea as the carousel, in two axes.
  3. After that JS is done. The rotation is an ordinary CSS keyframe animation on the container.
  4. The container is 0×0 so every dot rotates around the exact centre.

Key techniques

  • DOM generated from a formula
  • rotateY · rotateX · translateZ per dot
  • CSS keyframe spin

Copy-paste code

HTML 3 lines

<div class="scene">
  <div class="ball"></div>
</div>

CSS 27 lines

.scene {
  perspective: 600px;
}

.ball {
  --r: 130px;
  position: relative;
  width: 0;
  height: 0;
  transform-style: preserve-3d;
  animation: ball-spin 16s linear infinite;
}

.ball i {
  position: absolute;
  top: -4px;
  left: -4px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: radial-gradient(circle, #fff 15%, #ffb547 60%);
}

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

JS 13 lines

const ball = document.querySelector('.ball');
const COUNT = 120;
const GOLDEN_ANGLE = Math.PI * (3 - Math.sqrt(5));

for (let i = 0; i < COUNT; i++) {
  const lat = Math.asin(1 - (i / (COUNT - 1)) * 2);  // -90° … 90°
  const lon = i * GOLDEN_ANGLE;

  const dot = document.createElement('i');
  dot.style.transform =
    'rotateY(' + lon + 'rad) rotateX(' + lat + 'rad) translateZ(var(--r))';
  ball.append(dot);
}

Sass source 67 lines

.d-sphere {
  display: grid;
  grid-template-rows: 1fr auto;
  width: 100%;
  height: 100%;
  padding: 10px 14px 14px; // the control dock: the same place in every demo with controls

  &__view {
    display: grid;
    place-items: center;
    perspective: 600px;
  }

  &__ball {
    --r: 80px;
    position: relative;
    width: 0;
    height: 0;
    transform-style: preserve-3d;
    animation: d-sphere-spin 16s linear infinite;

    // Each <i> gets its own rotateY · rotateX · translateZ(var(--r)) inline from JS.
    i {
      position: absolute;
      top: -3px;
      left: -3px;
      width: 6px;
      height: 6px;
      border-radius: 50%;
      background: radial-gradient(circle, #fff 15%, var(--warm) 60%);
    }
  }

  // the site's control pattern: the text centred on top, the slider under it
  label {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 4px 6px;
    width: min(100%, 260px);
    margin-inline: auto;
    color: var(--muted);
    font-family: var(--mono);
    font-size: 11px;
  }

  output {
    min-width: 3ch;
    color: var(--text);
  }

  input {
    flex: 1 1 100%;
    accent-color: var(--warm);
  }
}

@keyframes d-sphere-spin {
  from {
    transform: rotateX(-18deg) rotateY(0deg);
  }

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