Getting Started
Installation
Pure HTML (IIFE build)
A single script tag gives you tosijs, tosijs-product, and the tosijs-ui components — everything a product page needs, with its custom elements registered:
<script src="https://cdn.jsdelivr.net/npm/tosijs-product@0.8.0/dist/index.js"></script>
Not included since 0.8.0: the tosijs-ui doc-authoring elements —
<tosi-code>,<tosi-example>,<tosi-doc-system>,<tosi-doc-system-prefs>,<tosi-css-var-editor>,<tosi-line-offset-probe>,<tosi-test-results>,<tosi-tests-done>. They are doc-site tooling, not product-page components, and they were most of the bundle: dropping them is what took it from 578kB to 145kB gzip. If you need them, load them from tosijs-ui's own subpaths (tosijs-ui/doc-browser,tosijs-ui/live-example) rather than from this script tag.
ESM (bundled apps)
bun install tosijs tosijs-ui tosijs-product
import {
tosiProduct,
tosiProductSection,
tosiInterpolator,
tosiWaypoint,
tosiFilmstrip,
tosiScrollMap,
tosiScrollCamera,
} from "tosijs-product";
Quick Start
Every tosijs-product page follows the same pattern:
- Wrap everything in
<tosi-product> - Create
<tosi-product-section>elements — each one pins its content and converts scroll distance to 0-1 progress - Inside sections, use
data-scroll-animateon children to drive them with scroll progress
Minimal example
<tosi-product>
<tosi-product-section scroll="200">
<tosi-interpolator data-scroll-animate easing="ease-in-out">
<tosi-waypoint
progress="0.0"
style="opacity: 0; transform: scale(0.9)"
></tosi-waypoint>
<tosi-waypoint
progress="0.3"
style="opacity: 1; transform: scale(1)"
></tosi-waypoint>
<tosi-waypoint
progress="0.7"
style="opacity: 1; transform: scale(1)"
></tosi-waypoint>
<tosi-waypoint
progress="1.0"
style="opacity: 0; transform: scale(0.9)"
></tosi-waypoint>
<h1>Hello, World</h1>
</tosi-interpolator>
</tosi-product-section>
</tosi-product>
The scroll="200" means the section creates 2x the viewport height of scroll distance. As the user scrolls through it, progress goes from 0 to 1, and the interpolator fades the heading in and out.
The scroll attribute
The scroll attribute is a viewport-relative percentage, not pixels:
scroll="100"— 1x the container dimension (default)scroll="300"— 3x, giving more scroll distance for slower animationsscroll="500"— 5x, for long sequences like map flights
Scroll ranges
Use data-scroll-range to constrain a child's animation to part of the section:
<tosi-product-section scroll="300">
<!-- First half -->
<tosi-interpolator data-scroll-range="0,0.5" easing="ease-in-out">
<tosi-waypoint progress="0" style="opacity: 0"></tosi-waypoint>
<tosi-waypoint progress="0.5" style="opacity: 1"></tosi-waypoint>
<tosi-waypoint progress="1" style="opacity: 0"></tosi-waypoint>
<h2>Part One</h2>
</tosi-interpolator>
<!-- Second half -->
<tosi-interpolator data-scroll-range="0.5,1" easing="ease-in-out">
<tosi-waypoint progress="0" style="opacity: 0"></tosi-waypoint>
<tosi-waypoint progress="0.5" style="opacity: 1"></tosi-waypoint>
<tosi-waypoint progress="1" style="opacity: 0"></tosi-waypoint>
<h2>Part Two</h2>
</tosi-interpolator>
</tosi-product-section>
Video scrubbing
Add data-scroll-animate="currentTime" to any <video> element:
<tosi-product-section scroll="300">
<video
src="clip.mp4"
data-scroll-animate="currentTime"
muted
playsinline
preload="auto"
></video>
</tosi-product-section>
For smoother results, convert the video to a filmstrip mosaic (see <tosi-filmstrip>).
Lottie animations
<tosi-product-section scroll="200">
<tosi-lottie src="animation.json" data-scroll-animate="lottie"></tosi-lottie>
</tosi-product-section>
Embedding, nesting and horizontal scrolling
The engine detects the scroll container it lives in, so a <tosi-product> works inside any
scrollable element, not just the page:
<div style="height: 60vh; overflow-y: auto;">
<tosi-product>
<tosi-product-section scroll="200">...</tosi-product-section>
</tosi-product>
</div>
Put a <tosi-product> inside a section and it becomes a follower: instead of listening to
scroll itself, it is driven by the parent section's pin progress and sizes to fill it. That's how
you nest a whole sub-story inside one scene of a bigger one.
For horizontal scrolling, set direction="horizontal" on the <tosi-product> — it's an engine
attribute, not a section one, because the engine is what lays the runway out:
<tosi-product direction="horizontal">
<tosi-product-section scroll="200">...</tosi-product-section>
</tosi-product>
Accessibility
When prefers-reduced-motion: reduce is active, all scroll-driven child animations are skipped. Only scrollCallback continues to fire, allowing you to provide alternative experiences.