IntersectionObserver reveal does.
This module is included in
tailmotion/css and also available on its own as
tailmotion/scroll.css — 12.1 KB raw, 4.0 KB gzipped. Every rule sits inside
@supports (animation-timeline: view()), so in a browser without support not
one declaration applies.The vocabulary
Ranges
A view timeline runs from “this element starts entering the scrollport” to “it has left”. Which slice of that you animate over is the whole design decision.--tm-scroll-range directly for anything more specific:
The default range starts once the subject is properly on screen and finishes
before it reaches the middle. Running a reveal across the full
entry range
means the reader watches text resolve while trying to read it — the animation
finishes at the exact moment their eye arrives, which is the worst possible
timing.Progress bar
scroll(root block), so it tracks the document. It scales from the
inline-start edge and mirrors in right-to-left contexts. You size and color the
bar; TailMotion scales it.
Restraint
Two rules this module follows, and you should too:- No large parallax.
tm-scroll-slide-blocktravels twice--tm-distanceand no further. Long parallax travel on body content makes text arrive somewhere the eye did not expect and measurably hurts reading. - No continuous decorative movement. Every class here settles at a final state and stays there. Nothing loops while the reader is trying to read.
Support and fallback
Everything sits inside
@supports (animation-timeline: view()). Where that is
false, not one declaration applies and the section renders as ordinary,
fully visible content.
Check the current status on caniuse.
Reduced motion
A progress-linked animation ignoresanimation-duration, so the library-wide
1ms reset cannot stop these. They are switched off by name instead:
tm-scroll-progress shows its complete state rather than tracking the scroll. If
a permanently full bar would misinform your readers, hide it as well:
This is the one place TailMotion’s reduced-motion behaviour is a judgement call
rather than a mechanical rule. The library’s convention is “land on the final
state”, and for a progress bar the final state is full. Naming the trade-off
here rather than picking silently is the point.
Verifying it works
Scroll-driven animations are attached as realAnimation objects, so you can
inspect them:
getAnimations() returns an empty array, the browser has no support and the
@supports guard did its job.