Esc

Try “tilt”, “reveal”, “drag”, or “reduced motion”.

Background

How it works

What Kinesis writes, how it finds elements, and why an idle page costs nothing.

What Kinesis writes

Kinesis writes a fixed set of inline styles, and only on elements it animates:

PropertyWhen
translate, rotate, scalewhile the element is moved, turned, or scaled
opacity, filter, color, background-coloronly with the matching reaction output
touch-actionon draggable elements
--k-progress, --k-pointer-x, --k-pointer-yon reacting or tracking elements

It never writes transform or transform-origin. Browsers apply translate, rotate, and scale before transform, so centering with transform: translate(-50%, -50%) and your hover transforms keep working. If you set translate, rotate, or scale yourself, Kinesis adds its motion to your value.

For 3D, it may set perspective on a tilted element's parent if the parent has none, and transform-style: preserve-3d on the elements between a depth layer and its tilted ancestor.

At rest, these inline values are removed. When an element stops being animated, or its scope is destroyed, the original inline values come back exactly.

How elements are found

Each [data-kinesis] scope scans its descendants once. It reads twelve trigger properties per element, such as --k-parallax and --k-when, and reads the rest only for elements that use Kinesis. By then the browser has resolved units, calc(), variables, and colors.

After that, Kinesis re-reads only what can have changed:

  • added elements, and elements whose class, style, or hidden attribute changed, with their descendants
  • the elements under the pointer as it moves between elements, for :hover rules
  • the focused element and its ancestors
  • the whole scope after a window resize, a theme change on <html> or <body>, or a new stylesheet

It never polls computed styles in the animation loop. On a test page of 1,600 elements, the first scan takes about 2 ms on a desktop computer. A class change on one element re-reads that element and its children.

Geometry

Kinesis measures positions without its own transforms, so elements react from where you placed them. Positions are stored relative to the document, so scrolling needs no re-measuring. Kinesis re-measures after a resize, a DOM change, a scroll inside a nested container, or the pointer entering a scope.

The frame loop

One loop runs for the whole page. Each frame has three phases:

  1. Read. Apply pending configuration and measurements, if anything changed.
  2. Compute. Math only, with pointer and scroll input sampled once per frame.
  3. Write. Update only the styles whose value changed.

Reads never follow writes within a frame, so Kinesis causes no layout thrashing. When every spring has settled and no input is changing, the loop stops and the page runs no frames at all. Offscreen elements and scopes are skipped, and hidden tabs pause.

Springs

Springs advance with the exact solution of a damped oscillator. They behave the same at any frame rate and stay stable after long pauses. They snap to rest within a hundredth of a pixel or degree.

Size

The core is 14 KB minified and gzipped, with no dependencies. Text splitting and audio are separate imports.

Browser support

Kinesis needs registered custom properties, the individual transform properties, and color-mix(). That means Chrome and Edge 111+, Safari 16.4+, and Firefox 128+. Older browsers don't resolve values written with calc(), variables, or relative units, so they get less motion or none. The page still works.

Edit this page on GitHub