scroll- craft.
The motion layer used in the client builds above, extracted into a zero-dependency scroll-animation library and published to npm: reveal, parallax, counters, text effects.
- Role
- Author
- Timeline
- 2026-05-03 to 2026-05-05, 31 commits
- Live URL
- scroll-craft.bilalstudio.io ↗
01 / THE REQUEST
The motion layer,extracted.
The same reveal-on-scroll and parallax patterns kept getting rebuilt per client site. The fix was to write it once, correctly, and publish it, so every future build starts from a tested library instead of a copy-pasted IntersectionObserver.
02 / THE PROBLEM
Split text breaksscreen readers.
Character and word-level text reveal effects work by wrapping every letter in its own <span>. Done naively, that means a screen reader encounters a wall of single-letter nodes and spells the word out letter by letter instead of reading it. A library that does this for a living has to solve that, not ship it as a known issue.
03 / THE CHALLENGE
Observer gates,rAF animates.
The library's rule is IntersectionObserver decides whether to animate, requestAnimationFrame runs the animation, and ScrollTimeline is never used, since browser support for it still isn't reliable enough to build on. Parallax and scene effects split reading layout from writing it: a measure() pass and a write() pass are kept separate and coalesced into one rAF tick (src/effects.ts:1040-1124), so the browser never has to interleave a forced layout read with a style write and thrash.
Every effect feature-detects the APIs it needs and falls through to the final visual state if they're missing, so a browser without some capability shows the finished layout instead of invisible content stuck mid-animation.
The accessibility fix: the text splitter sets a synthetic aria-label carrying the original full string, marks every generated span aria-hidden="true", keeps whitespace as real text nodes rather than spans, and removes the synthetic label again on revert so the DOM returns to its original state (src/effects.ts:384-473). A screen reader reads the label, not the exploded spans.
04 / THE SOLUTION
What gotbuilt.
Observer-gated, rAF-driven
IntersectionObserver decides when to run; requestAnimationFrame runs it. No ScrollTimeline dependency.
Measure/write split
Layout reads and style writes are batched separately and coalesced into one rAF tick to avoid thrash.
Screen-reader-safe text splitting
A synthetic aria-label plus aria-hidden spans keep split-text effects legible to assistive tech.
Graceful feature detection
Every effect falls through to its final state when a required API is missing, rather than leaving content invisible.
05 / THE RESULT
On npm,in three days.
- Livescroll-craft.bilalstudio.io (demo)
- Package size32 KB minified, 9.3 KB gzipped, measured from the committed build output
- Build window2026-05-03 to 2026-05-05, 31 commits
- Owns the codeBilal Studio, MIT-licensed on npm
BUILT WITH
Want the same motion system?
Next project
Licheanu