scroll-driver
CSS Scroll-Driven Animations for all browsers with a type-safe API.
Features
- 🔄 Cross-browser compatibility: Works in all major browsers including Safari (with automatic polyfill)
- 🔒 Type-safe API: Full TypeScript support with intuitive interfaces
- ⚛️ Framework integrations: React hooks, Vue composables, and Svelte stores
- 🛠️ Build tool plugins: Automatic polyfill injection for Vite and Rspack
- 📚 Storybook addon: Test and showcase scroll animations in Storybook
Installation
npm install scroll-driver
yarn add scroll-driver
pnpm add scroll-driver
Usage
Core API
import { createScrollTimeline } from 'scroll-driver';
const { timeline, ref } = createScrollTimeline({
axis: 'y',
range: ['0%', '100%'],
easing: 'ease-out'
});
const element = document.querySelector('.my-element');
if (element) {
ref(element);
}
const style = document.createElement('style');
style.textContent = `
.animated-element {
animation: move 1s linear forwards;
animation-timeline: ${timeline};
}
@keyframes move {
from { transform: translateY(0); }
to { transform: translateY(100px); }
}
`;
document.head.appendChild(style);
React
import { useScrollTimeline } from 'scroll-driver/react';
import React from 'react';
function ReadingProgress() {
const { timeline, ref } = useScrollTimeline({
source: typeof window !== 'undefined' ? document.documentElement : undefined,
axis: 'block',
});
return (
<>
{/* Progress bar fixed at the top */}
<div
style={{
position: 'fixed',
top: 0,
left: 0,
width: '100%', // Full width initially masked
height: '8px',
backgroundColor: 'blue', // Color of the progress bar
transformOrigin: '0 50%', // Animate scale from the left
animationName: 'progress',
animationTimingFunction: 'linear',
animationFillMode: 'forwards',
animationTimeline: timeline, // Link animation to scroll timeline
}}
/>
{/* Keyframes definition (can be in a global CSS file too) */}
<style>{`
@keyframes progress {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}
.content {
height: 200vh;
padding: 20px;
background: linear-gradient(to bottom, #eee, #ccc);
}
`}</style>
{/* The ref should be attached to the element that creates the scroll */}
{/* In this case, we are tracking the whole document scroll, so ref is not directly attached here,
but `useScrollTimeline` handles document scroll when scrollSource is documentElement */}
<div className="content" ref={ref}> {/* Attach ref for polyfill compatibility if needed */}
<h1>Scroll Down to See the Progress Bar</h1>
<p>As you scroll down this page, the blue bar at the top will fill up based on your scroll position.</p>
{/* Add more content here to ensure scrolling */}
<p style={{marginTop: '150vh'}}>End of content.</p>
</div>
</>
);
}
Vue
<script setup>
import { useScrollTimeline } from 'scroll-driver/vue';
const { timeline, elementRef } = useScrollTimeline({
axis: 'y',
range: ['0%', '100%'],
easing: 'ease-out'
});
</script>
<template>
<div ref="elementRef" class="scroll-container">
<div
class="animated-element"
:style="{
animationName: 'move',
animationTimingFunction: 'linear',
animationFillMode: 'forwards',
animationTimeline: timeline
}"
>
Scroll to animate me!
</div>
</div>
</template>
Svelte
<script>
import { createScrollTimeline } from 'scroll-driver/svelte';
const { timeline, action } = createScrollTimeline({
axis: 'y',
range: ['0%', '100%'],
easing: 'ease-out'
});
</script>
<div use:action class="scroll-container">
<div
class="animated-element"
style="
animation-name: move;
animation-duration: 1s;
animation-timing-function: linear;
animation-fill-mode: forwards;
animation-timeline: {timeline};
"
>
Scroll to animate me!
</div>
</div>
Build Tool Plugins
Vite
import { defineConfig } from 'vite';
import { scrollDriverVitePlugin } from 'scroll-driver/plugins/vite';
export default defineConfig({
plugins: [
scrollDriverVitePlugin({
injectPolyfill: true,
polyfillUrl: 'https://flackr.github.io/scroll-timeline/dist/scroll-timeline.js'
})
]
});
Rspack
const { ScrollDriverRspackPlugin } = require('scroll-driver/plugins/rspack');
module.exports = {
plugins: [
new ScrollDriverRspackPlugin({
injectPolyfill: true,
polyfillUrl: 'https://flackr.github.io/scroll-timeline/dist/scroll-timeline.js'
})
]
};
Storybook Addon
import { withScrollDriver, scrollDriverParameters } from 'scroll-driver/storybook';
export const decorators = [withScrollDriver];
export const parameters = {
...scrollDriverParameters
};
Browser Support
- Chrome 115+ (native)
- Firefox 115+ (native)
- Safari (polyfill)
- Edge 115+ (native)
- Other browsers (polyfill)
License
MIT