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
GitHub

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

.d-sphere {
  display: grid;
  grid-template-rows: 1fr auto;
  width: 100%;
  height: 100%;
  padding: 8px 14px 10px;

  &__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%);
    }
  }

  label {
    display: flex;
    align-items: center;
    gap: 8px;
    color: var(--muted);
    font-family: var(--mono);
    font-size: 11px;
  }

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

  input {
    flex: 1;
    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.