A comprehensive collection of React hooks and utilities for smart scroll handling, animations, and interactions.
- 10+ Smart Hooks - Complete scroll state management
- Smooth Scrolling - 20+ easing functions for perfect animations
- Scroll Spy - Track visible elements and active sections
- Infinite Scroll - Easy infinite scrolling implementation
- Scroll Lock - Prevent body scrolling (modals, overlays)
- Parallax Effects - Smooth parallax scrolling
- Scroll Animations - Trigger animations on scroll
- Mobile Optimized - Touch-friendly and performant
- Highly Configurable - Extensive customization options
- TypeScript Support - Full type definitions included
- Lightweight - Tree-shakeable, minimal bundle impact
- Performance First - RAF, throttling, and debouncing built-in
npm install use-smart-scroll
# or
bun add use-smart-scroll
# or
yarn add use-smart-scroll
# or
pnpm add use-smart-scroll
import React from 'react';
import { useScroll, useSmoothScroll } from 'use-smart-scroll';
function App() {
const { show, scrolled, direction, isAtTop } = useScroll();
const { scrollToTop, scrollToElement } = useSmoothScroll();
return (
<div>
{/* Smart Navigation */}
<nav className={`navbar ${show ? 'visible' : 'hidden'} ${scrolled ? 'scrolled' : ''}`}>
<button onClick={() => scrollToElement('#about', { duration: 800 })}>
About
</button>
<button onClick={() => scrollToTop()} disabled={isAtTop}>
Back to Top
</button>
</nav>
{/* Discount Banner - Only at top */}
{isAtTop && (
<div className="discount-banner">
🎉 Special Offer: 50% OFF! Use code: SAVE50
</div>
)}
<main>
<section id="hero">Hero Section</section>
<section id="about">About Section</section>
</main>
</div>
);
}
Hook | Purpose | Returns |
---|---|---|
useScroll |
Complete scroll state | { show, scrolled, direction, position, velocity, isAtTop, isAtBottom } |
useSmoothScroll |
Smooth scrolling utilities | { scrollTo, scrollToTop, scrollToElement, cancel } |
useScrollSpy |
Track visible elements | { activeElement, visibleElements } |
useScrollProgress |
Scroll progress percentage |
progress (0-100) |
Hook | Purpose | Use Case |
---|---|---|
useInfiniteScroll |
Infinite loading | Social feeds, product lists |
useScrollLock |
Prevent scrolling | Modals, overlays |
useParallax |
Parallax effects | Hero sections, backgrounds |
useScrollAnimation |
Scroll-triggered animations | Fade-ins, reveals |
The main smart hook that provides comprehensive scroll state information.
const {
show, // boolean - show/hide based on scroll direction
scrolled, // boolean - scrolled past threshold
scrollY, // number - current Y position
scrollX, // number - current X position
direction, // { vertical: 'up'|'down'|'none', horizontal: 'left'|'right'|'none' }
velocity, // number - scroll velocity
isScrolling, // boolean - currently scrolling
isAtTop, // boolean - at top of page
isAtBottom, // boolean - at bottom of page
isAtLeft, // boolean - at left edge
isAtRight, // boolean - at right edge
} = useScroll({
directionThreshold: 0, // threshold for direction detection
scrolledThreshold: 100, // threshold for scrolled state
throttle: 100, // throttle scroll events (ms)
debounce: 0, // debounce scroll events (ms)
onScroll: (state) => {}, // callback on scroll
});
Example: Smart Navigation with Discount Banner
function SmartNavigation() {
const { show, scrolled, direction, isAtTop } = useScroll({
scrolledThreshold: 50,
throttle: 16, // 60fps
});
return (
<>
{/* Discount Banner - Only at top */}
<div className={`
fixed top-0 left-0 right-0 z-50 bg-red-500 text-white text-center py-2
transition-all duration-300 transform
${isAtTop ? 'translate-y-0 opacity-100' : '-translate-y-full opacity-0'}
`}>
🎉 Special Offer: 50% OFF! Use code: SAVE50
</div>
{/* Smart Navigation */}
<nav className={`
fixed left-0 right-0 z-40 transition-all duration-300
${isAtTop ? 'top-10' : 'top-0'}
${show ? 'translate-y-0' : '-translate-y-full'}
${scrolled ? 'bg-white shadow-lg' : 'bg-transparent'}
`}>
<div className="flex justify-between items-center p-4">
<div>Logo</div>
<div className="text-sm">
Direction: {direction.vertical}
</div>
</div>
</nav>
</>
);
}
Provides smart smooth scrolling utilities with customizable easing functions.
const {
scrollTo, // (options) => Promise<void>
scrollToTop, // (duration?) => Promise<void>
scrollToBottom, // (duration?) => Promise<void>
scrollToElement, // (element, options?) => Promise<void>
scrollIntoView, // (element, options?) => Promise<void>
cancel, // () => void
isScrolling, // () => boolean
} = useSmoothScroll();
Example: Smart Navigation Menu
function SmartNavigationMenu() {
const { scrollToElement } = useSmoothScroll();
const { activeElement } = useScrollSpy(['#home', '#about', '#contact']);
const menuItems = [
{ id: '#home', label: 'Home' },
{ id: '#about', label: 'About' },
{ id: '#contact', label: 'Contact' },
];
return (
<nav className="flex space-x-4">
{menuItems.map(({ id, label }) => (
<button
key={id}
onClick={() => scrollToElement(id, {
duration: 800,
easing: 'easeInOutCubic',
offset: -100,
onComplete: () => console.log('Scrolled to', label),
})}
className={`px-4 py-2 rounded transition-colors ${
activeElement === id
? 'bg-blue-500 text-white'
: 'text-gray-600 hover:text-blue-500'
}`}
>
{label}
</button>
))}
</nav>
);
}
Smart Scroll Options:
await scrollTo({
target: 1000, // scroll position or element
duration: 800, // animation duration
easing: 'easeInOutCubic', // easing function
offset: -100, // offset from target
onStart: () => console.log('Started'),
onComplete: () => console.log('Completed'),
onCancel: () => console.log('Cancelled'),
});
Smart tracking of which elements are currently visible in the viewport.
const { activeElement, visibleElements } = useScrollSpy(
['#section1', '#section2', '#section3'],
{
offset: 100, // offset from top
threshold: 0.5, // visibility threshold (0-1)
rootMargin: '0px', // intersection observer margin
}
);
Get the current scroll progress as a percentage with smart calculation.
const progress = useScrollProgress(); // 0-100
Example: Smart Reading Progress Bar
function SmartReadingProgress() {
const progress = useScrollProgress();
return (
<div className="fixed top-0 left-0 w-full h-1 bg-gray-200 z-50">
<div
className="h-full bg-gradient-to-r from-blue-500 to-purple-600 transition-all duration-150"
style={{ width: `${progress}%` }}
/>
</div>
);
}
Smart infinite scrolling with loading states and error handling.
const { setTarget, isLoading } = useInfiniteScroll({
hasMore: true, // has more items to load
onLoadMore: async () => { // load more function
await fetchMoreItems();
},
threshold: 100, // distance from bottom to trigger
rootMargin: '100px', // trigger area
});
Example: Smart Product List
function SmartProductList() {
const [products, setProducts] = useState([]);
const [hasMore, setHasMore] = useState(true);
const [loading, setLoading] = useState(false);
const { setTarget } = useInfiniteScroll({
hasMore,
onLoadMore: async () => {
setLoading(true);
try {
const newProducts = await fetchProducts();
setProducts(prev => [...prev, ...newProducts]);
if (newProducts.length === 0) setHasMore(false);
} catch (error) {
console.error('Failed to load products:', error);
} finally {
setLoading(false);
}
},
});
return (
<div className="space-y-4">
{products.map(product => (
<ProductCard key={product.id} product={product} />
))}
{hasMore && (
<div ref={setTarget} className="p-8 text-center">
{loading ? (
<>
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500 mx-auto"></div>
<p className="mt-2 text-gray-600">Loading more products...</p>
</>
) : (
<p className="text-gray-500">Scroll to load more</p>
)}
</div>
)}
{!hasMore && (
<div className="text-center p-8">
<p className="text-gray-600">🎉 You've seen all products!</p>
</div>
)}
</div>
);
}
Smart scroll locking for modals and overlays with mobile support.
const { lock, unlock, toggle, isLocked } = useScrollLock({
reserveScrollBarGap: true, // maintain scrollbar space
allowTouchMove: (el) => false, // allow touch move on specific elements
});
Example: Smart Modal with Scroll Lock
function SmartModal({ isOpen, onClose, children }) {
const { lock, unlock, isLocked } = useScrollLock();
useEffect(() => {
if (isOpen) {
lock();
} else {
unlock();
}
return () => unlock(); // cleanup
}, [isOpen, lock, unlock]);
if (!isOpen) return null;
return (
<div className="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50 p-4">
<div className="bg-white rounded-2xl p-6 max-w-md w-full transform animate-fade-in-up">
<div className="flex justify-between items-center mb-4">
<h3 className="text-xl font-bold">Modal Title</h3>
<button
onClick={onClose}
className="text-gray-500 hover:text-gray-700 text-2xl"
>
×
</button>
</div>
{children}
<div className="mt-4 text-sm text-gray-500">
Scroll Lock: {isLocked ? 'Active' : 'Inactive'}
</div>
</div>
</div>
);
}
Create smart parallax scrolling effects with performance optimization.
const parallaxRef = useParallax({
speed: 0.5, // parallax speed (0-1)
offset: 0, // initial offset
min: -100, // minimum transform value
max: 100, // maximum transform value
});
Example: Smart Hero Section
function SmartHeroSection() {
const parallaxRef = useParallax({ speed: 0.5 });
const { scrollY } = useScroll();
return (
<section className="relative h-screen overflow-hidden">
<div
ref={parallaxRef}
className="absolute inset-0 bg-cover bg-center"
style={{
backgroundImage: 'url("/hero-bg.jpg")',
}}
/>
<div className="relative z-10 flex items-center justify-center h-full bg-black/30">
<div className="text-center text-white">
<h1 className="text-6xl font-bold mb-4">Smart Scrolling</h1>
<p className="text-xl mb-8">Experience the power of use-smart-scroll</p>
<div className="text-sm opacity-75">
Scroll Position: {scrollY}px
</div>
</div>
</div>
</section>
);
}
Smart scroll-triggered animations with intersection observer.
const { elementRef, reset } = useScrollAnimation({
threshold: 0.1, // intersection threshold
triggerOnce: true, // trigger only once
animationClass: 'fade-in', // CSS class to add
animationDelay: 200, // delay before animation
});
Example: Smart Animated Cards
function SmartAnimatedCard({ children, delay = 0 }) {
const { elementRef } = useScrollAnimation({
animationClass: 'animate-fade-in-up',
threshold: 0.2,
animationDelay: delay,
});
return (
<div ref={elementRef} className="opacity-0 transform translate-y-8">
{children}
</div>
);
}
function SmartFeatureSection() {
return (
<section className="py-20 bg-gray-50">
<div className="max-w-6xl mx-auto px-4">
<h2 className="text-4xl font-bold text-center mb-16">Smart Features</h2>
<div className="grid md:grid-cols-3 gap-8">
<SmartAnimatedCard delay={0}>
<div className="bg-white p-6 rounded-xl shadow-lg">
<h3 className="text-xl font-bold mb-4">🚀 Performance</h3>
<p>Optimized with RAF and throttling</p>
</div>
</SmartAnimatedCard>
<SmartAnimatedCard delay={200}>
<div className="bg-white p-6 rounded-xl shadow-lg">
<h3 className="text-xl font-bold mb-4">📱 Mobile First</h3>
<p>Touch-friendly and responsive</p>
</div>
</SmartAnimatedCard>
<SmartAnimatedCard delay={400}>
<div className="bg-white p-6 rounded-xl shadow-lg">
<h3 className="text-xl font-bold mb-4">🎯 TypeScript</h3>
<p>Full type safety included</p>
</div>
</SmartAnimatedCard>
</div>
</div>
</section>
);
}
// CSS
.animate-fade-in-up {
animation: fadeInUp 0.6s ease-out forwards;
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
Available easing functions for smooth scrolling:
linear
-
easeInQuad
,easeOutQuad
,easeInOutQuad
-
easeInCubic
,easeOutCubic
,easeInOutCubic
-
easeInQuart
,easeOutQuart
,easeInOutQuart
-
easeInQuint
,easeOutQuint
,easeInOutQuint
-
easeInSine
,easeOutSine
,easeInOutSine
-
easeInExpo
,easeOutExpo
,easeInOutExpo
-
easeInCirc
,easeOutCirc
,easeInOutCirc
-
easeInBack
,easeOutBack
,easeInOutBack
-
easeInElastic
,easeOutElastic
,easeInOutElastic
-
easeInBounce
,easeOutBounce
,easeInOutBounce
import {
// Scroll Control
scrollTo,
scrollToTop,
scrollToBottom,
scrollToElement,
scrollIntoView,
cancelScroll,
// Position & Measurement
getScrollPosition,
getDocumentDimensions,
getViewportDimensions,
getElementOffset,
getScrollProgress,
// Detection & Validation
isElementInViewport,
isScrollable,
getScrollableParent,
// Performance
throttle,
debounce,
rafThrottle,
calculateVelocity,
preventScroll,
getScrollbarWidth,
} from 'use-smart-scroll';
import { useScroll, useInfiniteScroll, useScrollSpy, useSmoothScroll } from 'use-smart-scroll';
function EcommerceApp() {
const { show, scrolled, isAtTop } = useScroll();
const { scrollToTop } = useSmoothScroll();
return (
<div>
{/* Discount Banner */}
{isAtTop && (
<div className="bg-red-500 text-white text-center py-2">
🔥 Flash Sale: 70% OFF Everything!
</div>
)}
{/* Smart Navigation */}
<nav className={`sticky top-0 z-40 transition-all ${
show ? 'translate-y-0' : '-translate-y-full'
} ${scrolled ? 'bg-white shadow-lg' : 'bg-transparent'}`}>
<div className="flex justify-between items-center p-4">
<div>🛍️ Smart Store</div>
<div className="flex space-x-4">
<a href="#products">Products</a>
<a href="#about">About</a>
<button onClick={() => scrollToTop()}>🔝</button>
</div>
</div>
</nav>
{/* Product Grid with Infinite Scroll */}
<ProductGrid />
</div>
);
}
function BlogApp() {
const progress = useScrollProgress();
const { activeElement } = useScrollSpy(['#intro', '#content', '#conclusion']);
return (
<div>
{/* Reading Progress */}
<div className="fixed top-0 w-full h-1 bg-gray-200 z-50">
<div
className="h-full bg-blue-500 transition-all"
style={{ width: `${progress}%` }}
/>
</div>
{/* Table of Contents */}
<nav className="fixed left-4 top-1/2 transform -translate-y-1/2">
{['intro', 'content', 'conclusion'].map(section => (
<div key={section} className={`w-2 h-8 mb-2 rounded ${
activeElement === `#${section}` ? 'bg-blue-500' : 'bg-gray-300'
}`} />
))}
</nav>
<article>
<section id="intro">Introduction</section>
<section id="content">Main Content</section>
<section id="conclusion">Conclusion</section>
</article>
</div>
);
}
function LandingPage() {
const { show, direction } = useScroll();
const { scrollToElement } = useSmoothScroll();
const parallaxRef = useParallax({ speed: 0.3 });
return (
<div>
{/* Hero with Parallax */}
<section className="relative h-screen overflow-hidden">
<div ref={parallaxRef} className="absolute inset-0 bg-gradient-to-r from-blue-500 to-purple-600" />
<div className="relative z-10 flex items-center justify-center h-full text-white">
<div className="text-center">
<h1 className="text-6xl font-bold mb-8">use-smart-scroll</h1>
<p className="text-xl mb-8">Smart scrolling for modern React apps</p>
<button
onClick={() => scrollToElement('#features', { duration: 1000 })}
className="bg-white text-blue-500 px-8 py-3 rounded-full font-semibold hover:bg-gray-100"
>
Explore Features
</button>
</div>
</div>
</section>
{/* Smart Navigation */}
<nav className={`fixed top-0 w-full z-40 transition-all duration-300 ${
show ? 'translate-y-0' : '-translate-y-full'
} bg-white/90 backdrop-blur-md shadow-lg`}>
<div className="flex justify-center space-x-8 p-4">
<button onClick={() => scrollToElement('#hero')}>Home</button>
<button onClick={() => scrollToElement('#features')}>Features</button>
<button onClick={() => scrollToElement('#examples')}>Examples</button>
<button onClick={() => scrollToElement('#contact')}>Contact</button>
</div>
</nav>
{/* Animated Features */}
<SmartFeatureSection />
</div>
);
}
// Mobile-specific configurations
const { show, scrolled } = useScroll({
throttle: 16, // 60fps for smooth mobile
directionThreshold: 10, // Larger threshold for touch
});
// Touch-friendly scroll lock
const { lock, unlock } = useScrollLock({
allowTouchMove: (el) => {
// Allow scrolling inside specific elements
return el.closest('.scrollable-content') !== null;
},
});
// Mobile-optimized infinite scroll
const { setTarget } = useInfiniteScroll({
threshold: 200, // Larger threshold for mobile
rootMargin: '200px', // Preload earlier on mobile
});
// For 60fps smooth performance
const { show } = useScroll({ throttle: 16 });
// For battery saving
const { show } = useScroll({ throttle: 100 });
const { scrollY } = useScroll({
debounce: 300,
onScroll: (state) => {
// Heavy operations here
updateAnalytics(state);
},
});
// Use larger thresholds for better performance
const { activeElement } = useScrollSpy(elements, {
threshold: 0.5,
rootMargin: '100px',
});
// Create a custom hook with your defaults
function useAppScroll(options = {}) {
return useScroll({
throttle: 16,
scrolledThreshold: 100,
...options,
});
}
import type {
ScrollState,
ScrollToOptions,
UseScrollOptions
} from 'use-smart-scroll';
// Custom scroll handler
const handleScroll = (state: ScrollState) => {
console.log('Scroll state:', state);
};
// Custom scroll options
const scrollOptions: ScrollToOptions = {
duration: 800,
easing: 'easeInOutCubic',
offset: -100,
};
import { renderHook, act } from '@testing-library/react';
import { useScroll } from 'use-smart-scroll';
test('should detect scroll direction', () => {
const { result } = renderHook(() => useScroll());
act(() => {
// Simulate scroll
Object.defineProperty(window, 'scrollY', { value: 100 });
window.dispatchEvent(new Event('scroll'));
});
expect(result.current.direction.vertical).toBe('down');
});
MIT © Eyachir Arafat
- Inspired by modern scroll libraries
- Built with performance and accessibility in mind
- Community feedback
- 📧 Email: me.eyachirarafat@gmail.com
- 🐛 Issues: GitHub Issues
- 💬 Discussions: GitHub Discussions
Made with ❤️ for the React community