useScrollProgress
The useScrollProgress hook provides an animated value that tracks scroll progress (0 to 1) for any scrollable element or the window. It's perfect for creating scroll-based animations, progress indicators, and scroll-triggered effects.
Why Use useScrollProgress?
useScrollProgress simplifies scroll progress tracking by:
- Animated progress value - Returns a
useValue-like object that animates from 0 to 1 - Automatic calculation - Handles progress calculation based on scroll position
- Smooth interpolation - Works seamlessly with the
tofunction for value mapping - Flexible targets - Works with window, containers, or specific elements
- Offset configuration - Control when progress starts and ends
Basic Syntax
useScrollProgress is actually useScroll called with options (instead of a callback). When you call useScroll with an options object, it returns progress values:
const { scrollYProgress, scrollXProgress } = useScroll(
refs: RefObject<HTMLElement> | Window,
options?: ScrollProgressOptions
)
useScroll has two modes:
- Callback mode:
useScroll(ref, callback)- For scroll event handling - Progress mode:
useScroll(ref, options)- For progress tracking (this page)
See useScroll for callback-based scroll handling.
Parameters
- refs (required): Element or
windowto track scroll progress - options (optional): Configuration object for progress calculation
Return Value
Returns an object with:
- scrollYProgress: Animated value (0-1) for vertical scroll progress
- scrollXProgress: Animated value (0-1) for horizontal scroll progress
Options
type ScrollProgressOptions = {
target?: RefObject<HTMLElement>; // Element to track progress relative to
offset?: [string, string]; // Start and end offsets (e.g., ['start end', 'start start'])
toDescriptor?: (progress: number) => AnimationDescriptor; // Transform progress value
};
Simple Example
Track window scroll progress:
import { useScroll, animate } from 'react-ui-animate';
function ScrollProgressBar() {
const { scrollYProgress } = useScroll(window);
return (
<div style={{ height: 2000 }}>
<animate.div
style={{
position: 'fixed',
top: 0,
left: 0,
width: scrollYProgress.to([0, 1], ['0%', '100%']),
height: 4,
background: 'teal',
zIndex: 1000,
}}
/>
</div>
);
}
Understanding Progress Values
The progress value ranges from 0 to 1:
- 0 = Scroll position at the start (top/left)
- 1 = Scroll position at the end (bottom/right)
- 0.5 = Scroll position at the middle
Real-World Examples
Example 1: Scroll Progress Bar
Create a progress bar at the top of the page:
function ScrollProgressBar() {
const { scrollYProgress } = useScroll(window);
return (
<>
<div style={{ height: 3000 }}>
<h1>Scroll down to see progress</h1>
</div>
<animate.div
style={{
position: 'fixed',
top: 0,
left: 0,
width: scrollYProgress.to([0, 1], ['0%', '100%']),
height: 4,
background: 'linear-gradient(90deg, #667eea, #764ba2)',
zIndex: 1000,
}}
/>
</>
);
}
Example 2: Scale on Scroll
Scale an element based on scroll progress:
function ScaleOnScroll() {
const { scrollYProgress } = useScroll(window);
return (
<div style={{ height: 2000 }}>
<animate.div
style={{
position: 'fixed',
top: '50%',
left: '50%',
translateX: -50,
translateY: -50,
scale: scrollYProgress.to([0, 1], [1, 2]),
width: 100,
height: 100,
background: 'teal',
borderRadius: 8,
}}
/>
</div>
);
}
Example 3: Color Interpolation
Change color based on scroll progress:
function ColorOnScroll() {
const { scrollYProgress } = useScroll(window);
return (
<div style={{ height: 2000 }}>
<animate.div
style={{
position: 'fixed',
top: '50%',
left: '50%',
translateX: -50,
translateY: -50,
width: 200,
height: 200,
backgroundColor: scrollYProgress.to([0, 1], ['#0069d9', '#ff5733']),
borderRadius: 8,
}}
/>
</div>
);
}
Example 4: Element-Specific Progress
Track progress relative to a specific element:
function ElementProgress() {
const containerRef = useRef(null);
const { scrollYProgress } = useScroll(window, {
target: containerRef,
offset: ['start end', 'start start'],
});
return (
<div style={{ height: 2000 }}>
<animate.div
ref={containerRef}
style={{
marginTop: 800,
padding: 40,
background: 'teal',
borderRadius: 8,
opacity: scrollYProgress.to([0, 1], [0, 1]),
translateY: scrollYProgress.to([0, 1], [50, 0]),
}}
>
<h2>This element animates as you scroll</h2>
<p>Progress: {Math.round(scrollYProgress.get() * 100)}%</p>
</animate.div>
</div>
);
}
Example 5: Smooth Progress with Spring
Add spring animation to progress value:
function SmoothProgress() {
const { scrollYProgress } = useScroll(window, {
toDescriptor: (p) => withSpring(p, { damping: 10 }),
});
return (
<div style={{ height: 2000 }}>
<animate.div
style={{
position: 'fixed',
top: 0,
left: 0,
width: scrollYProgress.to([0, 1], ['0%', '100%']),
height: 4,
background: 'teal',
}}
/>
</div>
);
}
Example 6: Horizontal Scroll Progress
Track horizontal scroll progress:
function HorizontalProgress() {
const containerRef = useRef(null);
const { scrollXProgress } = useScroll(containerRef);
return (
<div
ref={containerRef}
style={{
width: 400,
height: 300,
overflowX: 'auto',
border: '1px solid #e1e1e1',
}}
>
<div style={{ width: 2000, height: 200, padding: 20 }}>
<animate.div
style={{
position: 'sticky',
top: 0,
left: 0,
width: scrollXProgress.to([0, 1], ['0%', '100%']),
height: 4,
background: 'teal',
}}
/>
<p>Scroll horizontally to see progress</p>
</div>
</div>
);
}
Offset Configuration
Control when progress starts and ends using offset strings:
const { scrollYProgress } = useScroll(window, {
target: elementRef,
offset: ['start end', 'start start'],
});
Offset Values
'start'- Start of the element/viewport'end'- End of the element/viewport'center'- Center of the element/viewport
Common Patterns
// Progress from when element enters viewport to when it leaves
offset: ['start end', 'end start'];
// Progress from when element enters viewport to when it reaches top
offset: ['start end', 'start start'];
// Progress from when element is 50% visible to when it's fully visible
offset: ['start center', 'start start'];
Interpolation Patterns
Use the to function to map progress (0-1) to any range:
Pattern 1: Linear Mapping
// Map 0-1 to 0-100
width: scrollYProgress.to([0, 1], [0, 100]);
// Map 0-1 to 100-0 (reverse)
opacity: scrollYProgress.to([0, 1], [1, 0]);
Pattern 2: Multi-Stage Animation
// Different values at different progress points
scale: scrollYProgress.to([0, 0.5, 1], [1, 1.5, 1]);
Pattern 3: Color Transitions
backgroundColor: scrollYProgress.to(
[0, 0.5, 1],
['#0069d9', '#ff5733', '#00d9ff']
);
Common Patterns
Pattern 1: Progress Indicator
const { scrollYProgress } = useScroll(window);
<animate.div
style={{
width: scrollYProgress.to([0, 1], ['0%', '100%']),
}}
/>;
Pattern 2: Fade In/Out
const { scrollYProgress } = useScroll(window, {
target: elementRef,
offset: ['start end', 'start start'],
});
<animate.div
style={{
opacity: scrollYProgress.to([0, 1], [0, 1]),
}}
/>;
Pattern 3: Parallax Effect
const { scrollYProgress } = useScroll(window);
<animate.div
style={{
translateY: scrollYProgress.to([0, 1], [0, -200]),
}}
/>;
Best Practices
✅ Do
- Use
scrollYProgressfor vertical scroll effects - Use
scrollXProgressfor horizontal scroll effects - Configure offsets to control when progress starts/ends
- Use the
tofunction for smooth value interpolation - Combine with
withSpringfor smoother progress updates
❌ Don't
- Don't use progress values directly in calculations (use
tofunction) - Don't forget to handle horizontal vs vertical progress
- Don't use progress for elements that don't scroll
- Don't make progress calculations too complex
Performance Tips
- Use transforms -
translateX,translateY,scaleare GPU-accelerated - Limit calculations - Progress is calculated automatically, avoid manual calculations
- Use offsets wisely - Configure offsets to only track relevant scroll ranges
- Smooth with spring - Use
toDescriptorwithwithSpringfor smoother updates
Troubleshooting
Progress doesn't update:
- Make sure the element is scrollable
- Check that the ref is attached correctly
- Verify the element has scrollable content
Progress jumps:
- Use
toDescriptorwithwithSpringfor smoother updates - Check that offsets are configured correctly
- Ensure the target element exists
Progress is always 0 or 1:
- Verify the scroll container has enough content to scroll
- Check offset configuration
- Make sure you're tracking the correct scroll direction
Next Steps
- Learn about useScroll for detailed scroll event handling
- Explore Interpolation for advanced value mapping
- Check out View Animations for scroll-triggered animations