Skip to content

InnerWorks-me/innerworks-loader

Repository files navigation

innerworks-loader

A draggable 3D loading animation. The logo is extruded into a thin slab, then several slabs are stacked a small distance apart and spun at whole-revolution multiples of a shared cycle, so they drift apart and snap back into perfect alignment once per cycle. Rear slabs fade toward your background colour for depth.

No dependencies beyond React. Single component, fully prop-driven. Ships TypeScript source - no separate @types package needed.

Demo

https://innerworks-me.github.io/innerworks-loader/

npm run dev    # build + watch + dev server at http://localhost:8080
npm run build  # one-off build of demo/demo.js

Install

npm install @innerworks-me/innerworks-loader

Usage

import InnerworksLoader from "@innerworks-me/innerworks-loader";

export default function Loading() {
  return (
    <InnerworksLoader
      size={240}
      logoColor="#111111"
      fadeColor="#ffffff"
      speed={1.2}
      realignRevs={6}
      draggable
      onAlign={() => console.log("aligned!")}
    />
  );
}

Reacting to alignment

onAlign fires every time the slabs line up — once per realign cycle. It's wired to the actual animation (the rear slab completes exactly one revolution per cycle), so it stays in sync regardless of speed or tab throttling. Handy for pulsing a glow, advancing a step, or triggering a sound on the "click" moment.

const [pulse, setPulse] = useState(false);

<InnerworksLoader
  onAlign={() => {
    setPulse(true);
    setTimeout(() => setPulse(false), 150);
  }}
  style={{ filter: pulse ? "brightness(1.4)" : "none", transition: "filter .15s" }}
/>

Static (non-draggable) at a fixed angle

<InnerworksLoader draggable={false} initialRotation={{ x: 0, y: -24 }} />

Lock dragging to one axis

<InnerworksLoader tiltAxis="y" />   // left/right only

Props

Prop Type Default Description
size number 300 Square render size in px. All geometry scales with it.
logoColor string "#111111" Logo fill colour.
fadeColor string "#ffffff" Colour the rear slabs fade toward (set to your background).
fadeMin number 32 % of logoColor used by the rear-most slab. Lower = fainter.
slabs number 5 Number of stacked slabs.
sheets number 14 Copies stacked per slab to fake the extruded thickness.
thicknessMm number 2 Extrusion depth per slab, in mm.
spacingMm number 3 Gap between slabs, in mm.
pxPerMm number 7 Pixels per mm at size=300.
perspective number 1500 CSS perspective at size=300, in px.
speed number 1 Speed multiplier.
realignRevs number 5 Revolutions the fastest slab makes before everything realigns.
cycleSeconds number 14 Seconds for one full realign cycle at speed=1.
draggable boolean true Allow click/touch drag (and arrow keys) to rotate.
dragSensitivity number 0.55 Degrees of rotation per pixel dragged.
initialRotation { x: number; y: number } { x: 0, y: -20 } Starting angle; the fixed angle when draggable is false.
tiltAxis "both" | "x" | "y" "both" Restrict drag/keys to one axis.
paused boolean false Freeze the spin (keeps current phase).
respectReducedMotion boolean true Freeze when the user prefers reduced motion.
fireAlignOnMount boolean false Also fire onAlign once for the initial aligned state.
onAlign () => void Called every time the slabs line up.

Any other props (className, style, id, data attributes, …) are spread onto the root element.

Notes

  • Realign math: each slab does a distinct whole number of revolutions per cycle (rear = 1, front = realignRevs), so they only all coincide at the cycle boundary. That's the single alignment moment onAlign reports.
  • Theming: the depth fade blends logoColor toward fadeColor with CSS color-mix. Set fadeColor to match wherever you mount it so the rear slabs recede correctly. For a transparent backdrop, set fadeColor to your page colour rather than transparent.
  • Accessibility: when draggable, the root is a focusable slider and responds to arrow keys (hold Shift for larger steps). Reduced-motion users see a static, aligned logo by default.

Releases

No releases published

Packages

 
 
 

Contributors