ScrollerVideo
A video-driven scrollytelling component with frame-by-frame scrubbing. The video playback position maps to scroll position, allowing the user to scrub through a video by scrolling while foreground step content overlays the video.
This headless component uses a <div> element with an aria-label and a muted video element whose playback is driven entirely by scroll position.
Example
Show demo markup
<div class="scroller-video" aria-label="a video-driven scrollytelling component with frame-by-frame scrubbing">ScrollerVideo</div>
Usage example
<ScrollerVideo
src="/videos/climate-story.mp4"
label="Rising sea levels"
alt="Animation showing coastal flooding progression from 2020 to 2050"
bind:progress
>
<div class="step"><p>Current sea levels</p></div>
<div class="step"><p>Projected 2030 levels</p></div>
<div class="step"><p>Projected 2050 levels</p></div>
</ScrollerVideo>
Show Svelte source
// In your Svelte component:
import ScrollerVideo from "lily-design-system-svelte-headless/components/ScrollerVideo/ScrollerVideo.svelte";
<ScrollerVideo>
<!-- content -->
</ScrollerVideo>
Usage
<ScrollerVideo
src="/videos/climate-story.mp4"
label="Rising sea levels"
alt="Animation showing coastal flooding progression from 2020 to 2050"
bind:progress
>
<div class="step"><p>Current sea levels</p></div>
<div class="step"><p>Projected 2030 levels</p></div>
<div class="step"><p>Projected 2050 levels</p></div>
</ScrollerVideo>
<ScrollerVideo
src="/videos/cell-division.mp4"
label="Cell division process"
alt="Microscope footage showing the stages of mitosis"
bind:index
bind:progress
>
<div class="step"><p>Prophase: chromosomes condense.</p></div>
<div class="step"><p>Metaphase: chromosomes align.</p></div>
<div class="step"><p>Anaphase: chromosomes separate.</p></div>
<div class="step"><p>Telophase: cell divides.</p></div>
</ScrollerVideo>
Props
src: string (required) -- video source URL
children: slot (required) -- foreground step content overlaid on video
offset: number (default: 0.5) -- viewport offset where step triggers
index: number (bindable, default: 0) -- current active step index
progress: number (bindable, default: 0) -- overall scroll progress (0-1)
label: string (required) -- accessible label describing the video story
alt: string (required) -- text alternative describing the video content
...restProps: Any additional HTML attributes
Keyboard Interactions
- Standard scroll behavior drives video position
ARIA
aria-label on container describing the video story
role="img" with aria-roledescription="scrollable video" on video area
alt text provides a text alternative for non-visual access
When to Use
- Use for scroll-driven video narratives where the video illustrates a progression
- Use when a video should advance frame-by-frame in sync with the reader's scroll position
- Use for data journalism, educational content, or storytelling with video evidence
When Not to Use
- Do not use for standard video playback -- use a video player instead
- Do not use when video is supplementary rather than central to the narrative
- Do not use for short clips that are better served by autoplay or click-to-play
Headless
This component provides a <div> element with scroll-driven video playback and foreground step composition, with zero visual styling. The consumer is responsible for all CSS including video sizing, foreground overlay positioning, step spacing, and responsive layout.
Styles
The consumer provides all CSS styling. The component renders with a .scroller-video class for targeting. No default styles are included -- this is a fully headless component.
Testing
- Verify the component renders a
<div> element with class scroller-video
- Verify
aria-label is applied
- Verify
alt text is provided for non-visual access
- Verify video is muted
- Verify video playback position maps to scroll position
- Verify index updates as steps scroll into view
- Verify progress updates as the user scrolls
- Verify
role="img" and aria-roledescription="scrollable video" are on the video area
- Verify pass-through attributes are applied
Advice
- Designers: Choose videos that communicate a clear progression. Ensure foreground text is readable over the video at every frame. Consider providing a static fallback image for users who prefer reduced motion.
- Developers: Always provide both
label and alt props for accessibility. The alt text should describe the overall video content, not individual frames. Test with WebCodecs API support and the currentTime fallback. Ensure the video file is optimized for scrubbing (short GOP length).
Implementation Notes
- Uses
<div> element with class scroller-video
- Video playback position maps to scroll position
- Video scrubs frame-by-frame as user scrolls
- Can use WebCodecs API for smooth frame extraction where supported
- Falls back to standard video currentTime seeking
- Video is muted (scroll-driven, not time-driven)
Related components
scroller — see components.tsv
scroller-base — see components.tsv
video-player — see components.tsv
References
- Reuters Graphics ScrollerVideo
- WebCodecs API: https://developer.mozilla.org/en-US/docs/Web/API/WebCodecs_API
- ScrollerBase component: ../scroller-base/index.md
Canonical contract
Generated from this component's canonical metadata — the machine-checked source the implementations are held to.
Metadata
- Component: scroller-video
- PascalCase: ScrollerVideo
- Description: a video-driven scrollytelling component with frame-by-frame scrubbing
- Status: beta — implemented and unit-tested in all seven frameworks with an axe-clean demo page; not yet exercised in composed flows
- HTML tag: <div>
- CSS class: .scroller-video
- Interactive: yes
ARIA
aria-label on container role="img" with aria-roledescription="scrollable video" on video area alt text for non-visual access
Keyboard
- Standard scroll behavior drives video position
Props
src: string (required) -- video source URL children: slot (required) -- foreground step content overlaid on video offset: number (default: 0.5) -- viewport offset where step triggers index: number (bindable, default: 0) -- current active step index progress: number (bindable, default: 0) -- overall scroll progress (0-1) label: string (required) -- accessible label describing the video story alt: string (required) -- text alternative describing the video content ...restProps: Any additional HTML attributes