Skip to content

Motion — spatial & perceived depth

themes specs/themes/motion/spatial.kmd

Pseudo-3D motion on flat surfaces: pointer/gyroscope parallax, depth-on-scroll, and 3D transitions (flip, cube, perspective shared- element). These imply depth without real geometry — they are a PERCEPTUAL projection of the depth axis (`themes/depth.kmd § R4`). Strictly gated by the reduced-motion contract. Material parity: extends M3 motion with the spatial/expressive depth cues M3 leaves implicit.

When this spec applies

Primary triggers

All triggers

Specification body

Spec — Motion: spatial & perceived depth

Facet Visual do Koder Design. Sub-spec of motion.kmd. Material parity: https://m3.material.io/styles/motion + the expressive spatial cues M3 leaves to the implementer.

On a flat screen, depth (depth.kmd) can be implied by motion. This is perceptual, never a real layer — it must not reorder the stacking bands (depth.kmd § R3) and it is always the first thing to switch off under reduced-motion.

Reduced-motion is mandatory — see physics.kmd. Every effect here MUST degrade to a static, non-parallaxed layout.

R1 — Parallax (pointer & gyroscope)

Layers translate at a rate inverse to their depth — near layers move more, far layers move less (distant scenery).

TokenMeaningDefault
--kds-parallax-maxMax layer translation (px) at depth 112
--kds-parallax-falloffMultiplier per depth level deeper0.6
  • A layer at depth d moves --kds-parallax-max × falloff^(d-1).
  • Pointer: track cursor offset from element centre (desktop).
  • Gyroscope: deviceorientation β/γ on mobile (opt-in; needs user permission on iOS — fall back to no parallax if denied).
  • Clamp total travel so text never clips its container.

R2 — Depth-on-scroll

As the viewport scrolls, layers parallax vertically by depth — a hero image recedes slower than its foreground caption.

  • Driven by scroll-timeline / IntersectionObserver, never a scroll-jank requestAnimationFrame storm.
  • Magnitude scales with depth (R1 falloff).
  • MUST NOT trigger layout (transform only — no top/height animation).

R3 — 3D transitions

Perspective transitions between two states of the same surface:

PatternUseNotes
FlipReveal back of a card (front ↔ details)rotateY(180deg); both faces backface-visibility: hidden
CubeStep through a small sequence (≤ 4 faces)rotateY on a transform-style: preserve-3d parent
Perspective shared-elementHand off an element between screens with depthExtends the 2D shared-axis pattern in transitions.kmd
  • perspective between 600–1000px (closer = more dramatic; default 800px).
  • Lighting is consistent: a face rotating away dims toward the shadow side of the rig (lighting.kmd § R1).
  • Duration from easing-duration.kmd; spring option from physics.kmd.

R4 — Performance

  • Animate transform + opacity only (compositor-friendly).
  • Budget: 60 fps; parallax handlers throttled to one update per frame (requestAnimationFrame), never per pointer event.
  • will-change: transform only while active; removed at rest.
  • Disable all effects when the tab is backgrounded.

R5 — Forbidden patterns

  • ❌ Any effect that ignores prefers-reduced-motion (R1 intro).
  • ❌ Parallax that reorders real stacking bands (depth.kmd § R3).
  • ❌ Animating layout properties (top/left/width/height) for depth.
  • ❌ Parallax > --kds-parallax-max that clips or detaches content.
  • ❌ Gyroscope parallax without a no-permission fallback (R1).
  • ❌ Flip/cube on surfaces carrying critical text that becomes unreadable mid-rotation (keep faces legible or fade).

R6 — Accessibility

  • prefers-reduced-motion: reduce → static layout, no parallax, no 3D rotation (cross-fade or instant swap instead).
  • Parallax is decorative: it MUST NOT move focusable targets out from under the pointer/keyboard focus.
  • Motion sickness ceiling: no rotation faster than 30°/s in the user's gaze area (mirrors xr-preview.kmd § R8).

T1-T3 — Tests

T1 — Reduced-motion: with prefers-reduced-motion, parallax travel is 0 px and 3D transitions resolve as a static state change. T2 — Transform-only: scroll/parallax effects animate only transform/ opacity (no layout-triggering property in the animation). T3 — Falloff: a layer at depth 2 moves --kds-parallax-falloff× the travel of a depth-1 layer (within 1 px).

Live demo

  • themes/motion/physics.kmd — reduced-motion contract (mandatory)
  • themes/motion/transitions.kmd — 2D pattern catalog (R3 extends it)
  • themes/depth.kmd — the axis these effects project (§ R4)
  • themes/lighting.kmd — face shading during 3D rotation (R3)

References