Skip to content
animorio
IT
Back to the library

The dithered sphere

WebGL, 3DGSAP, WebGL, CanvasMediumCompressed weight: Demo 6.5 KBGSAP 28 KBFreeDownload the zip

A minimal-style WebGL animation: a single sphere drawn in one bit, with square dots of ink like a newspaper halftone, lit by a light that follows the pointer. Parallels and meridians turn slowly, the shadow slides away from the light and only the highlight turns blue.

The code

A folder that runs on its own: index.html, style.css, script.js and the libraries from a CDN. The React and Vue tabs hold the same demo as a ready component, which switches its animations off when it leaves the page.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Nadir</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/jost@5.3.0/index.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <!-- one sphere drawn in one bit: a WebGL shader lights it like a ball and an 8x8 Bayer matrix turns the light into
       big square dots of ink; the light follows the pointer, and when nobody moves it, it walks around the sphere -->
  <main class="pagina" data-pagina>
    <canvas class="tela" aria-hidden="true" data-tela></canvas>

    <header class="testa">
      <p class="marchio" data-entra>Nadir</p>
      <p class="testa__nota" data-entra>A study of light</p>
    </header>

    <p class="nota" data-entra data-nota>Move the light</p>
  </main>

  <script src="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/gsap.min.js"></script>
  <script src="script.js"></script>
</body>
</html>

Playground

Change values, colours and text with the controls and watch the animation respond live. Or write in the code yourself. Then download the folder with your changes.

16 values to change

Or ask Claude Code for it

With the Animorio connector, Claude Code takes it from here, guide included, and fits it into your project. Paste this request:

Take “The dithered sphere” from Animorio and fit it into my site

What it does

On a warm white sheet, in the middle, there is a single sphere made of square dots of ink, all the same, like the halftone of a photo printed in a newspaper: where the sphere is lit the dots thin out, where it is in shadow they crowd together up to solid black. On opening the sphere develops out of the paper like a print, the greys come down from the white and the radius settles with a hair too much; then the name, Nadir, and two small lines appear. The light sits in front of the sphere and the pointer leads it: moving it, the bright side turns, the dark side widens on the other side and the highlight, the only blue dot on the page, slides over the surface. On the sphere the halftone draws thin parallels and meridians, dark where there is light and light where there is shadow, which turn slowly like a globe; below, on the floor, the sphere's oval shadow moves away from the light. The sphere breathes a little, in shape, not in light. When nobody moves the pointer, the light goes back to circling the sphere on its own, in a fourteen-second round that starts again the same.

How it works

A square and a shader

The whole page is a WebGL canvas with a single square covering it (two triangles in a TRIANGLE_STRIP) and a fragment shader that decides the colour of each pixel. The pixel is not worked out for itself: it is rounded to its halftone cell, floor(gl_FragCoord.xy / u_dot), and the sum is done at the centre of the cell. So every pixel in a cell comes out the same and the halftone dots are squares DOT CSS pixels wide, even with devicePixelRatio at 2.

The light of a ball

In the cell we look at the distance from the centre of the sphere in radii: if it is less than one, the surface normal is (x, y, the square root of 1 minus x² minus y²). The light is a direction: the pointer gives x and y in sphere radii and LIGHT_HEIGHT gives z. Lambert light is the dot product of normal and light, the highlight is Blinn-Phong, the normal against the halfway vector between light and eye raised to SHINE. The grey is a number from 0 (black) to 1 (paper).

One bit with the Bayer matrix

The grey becomes a dot or no dot by comparing it with a threshold that changes from cell to cell according to the 8x8 Bayer matrix, worked out in the shader with a recursive formula: bayer2 on a.x and a.y, then bayer4 and bayer8 that add the same formula at half scale. With 64 different thresholds a mid grey becomes a checkerboard, a light one a dot here and there. The strongest highlight, above its own threshold, turns blue instead of paper.

The globe and the shadow

The normal is turned around the vertical axis by an angle that grows with the round, and its longitude and latitude are taken: where either falls near a whole number there is a line. The line darkens the grey where the sphere is lit and lightens it where it is in shadow, so it shows on both sides. Outside the sphere there is a flattened ellipse beneath it, moved to the side away from the light, which darkens the paper on a soft curve: the shadow goes through the halftone too.

The direction

An object of numbers, fase and nasce, is animated by GSAP: fase goes from 0 to 1 in LIGHT_SPEED seconds in a repeating timeline, and from it come the light's round when nobody moves the pointer, the turning of the meridians and the breathing of the radius, all with periods that come back whole. The real light chases its point with a measured spring on GSAP's ticker; after 2.4 seconds without a pointer it goes back to the round. An IntersectionObserver switches everything off when the page is not visible. Without WebGL the same sum is done in JavaScript on a 2D canvas.

Values to change

NameFileDefaultWhat it changes
DOTscript.js4The side of a halftone dot, in px: bigger shows more grain.
BAYER_SIZEscript.js8The Bayer matrix, 2, 4 or 8: bigger gives more shades of grey.
RADIUSscript.js0.3The radius of the sphere, as a share of the page's shorter side.
LIGHT_SPEEDscript.js14How many seconds the light takes for a full round on its own.
LIGHT_HEIGHTscript.js0.9How far in front of the sphere the light sits: higher, less shadow.
SHINEscript.js26How tight the blue highlight is: higher, smaller.
LINESscript.js12How many meridians (the parallels are half as many).
BREATHscript.js0.012How much the sphere's radius breathes.
SHADOWscript.js0.5How dark the shadow under the sphere is.

Try them in the Playground

When to use it

As the opening of an observatory, a lab, a photography or print studio, a product about light, energy or space; as the landing page of a data app or a developer tool when you want a strong image made with very little; as the background of a page for a conference on graphics.

On phones

The same animation: the sphere is sized on the shorter side (a little bigger on narrow screens), a finger that touches and drags moves the light, and as soon as it lifts the light goes back to its round.

With reduced motion

The sphere is already developed and still, with the light at the top left: no round, no breathing, no turning meridians. The name and the small lines fade in.

Pitfalls

  • The halftone is worked out per cell and not per pixel: rounding gl_FragCoord to the cell and doing the sum at its centre makes every dot a solid square. Doing it pixel by pixel frays the edges of the dots and the halftone looks like noise.
  • The Bayer matrix needs no texture: the recursive formula of bayer2, bayer4 and bayer8 fits in three lines of GLSL and works in WebGL 1 too, where arrays with variable indices are awkward.
  • Dark meridians on the shadow side did not show: the halftone there is already almost black. Now the line darkens where there is light and lightens where there is shadow, with a blend in between.
  • A shadow with a hard edge looked like a rug: now it falls off on a soft curve (smoothstep) and the halftone turns it into a gradient of dots.
  • The entrance uses back.out, which takes the entrance number a little past 1: the radius gets the overshoot we want, but the grey must be held at 1 with min(), or for a moment the sphere gets darker.
  • The canvas is resized on every resize, even mid-round, with devicePixelRatio capped at 2: the halftone cell is multiplied by the same number, so the dots stay the same size in CSS pixels.

Libraries

GSAP 3.15.0, Jost (font variabile) 5.3.0.

Compressed with gzip, the three demo files weigh 6.5 KB; the libraries: GSAP 28 KB.

Testing

Tested on 21 October 2026

  • Desktop: Done
  • Phone: Done
  • Reduced motion: Done
  • Safari on iPhone: To do

License

Use it in your own projects and in your clients’ projects. Do not resell it or redistribute it as is. GSAP stays under its own Standard license. Full license.

All animations