Skip to content

farazfarid/scroll-snap-kit

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

11 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

scroll-snap-kit 🎯

Smooth scroll utilities + React hooks for modern web apps.

Zero dependencies. Tree-shakeable. Works with or without React.

npm version license bundle size


Install

npm install scroll-snap-kit

What's included

Utility Description
scrollTo Smooth scroll to an element or pixel value
scrollToTop / scrollToBottom Page-level scroll helpers
getScrollPosition Current scroll x/y + percentages
onScroll Throttled scroll listener with cleanup
isInViewport Check if an element is visible
lockScroll / unlockScroll Freeze body scroll, restore position
scrollSpy Highlight nav links based on active section
onScrollEnd Fire a callback when scrolling stops
scrollIntoViewIfNeeded Only scroll if element is off-screen
easeScroll + Easings Custom easing curves for scroll animation
scrollSequence Chain multiple scroll animations in order
parallax Attach parallax speed multipliers to elements
scrollProgress Track how far through an element the user has scrolled
snapToSection Auto-snap to the nearest section after scrolling stops
Hook Description
useScrollPosition Live scroll position + percentage
useInViewport Whether a ref'd element is visible
useScrollTo Scroll function scoped to a container ref
useScrolledPast Boolean — has user scrolled past a threshold
useScrollDirection 'up' | 'down' | null

Vanilla JS Utilities

import {
  scrollTo, scrollToTop, scrollToBottom,
  getScrollPosition, onScroll, isInViewport,
  lockScroll, unlockScroll,
  scrollSpy, onScrollEnd, scrollIntoViewIfNeeded,
  easeScroll, Easings,
  scrollSequence, parallax, scrollProgress, snapToSection
} from 'scroll-snap-kit';

scrollTo(target, options?)

Smoothly scroll to a DOM element or a Y pixel value.

scrollTo(document.querySelector('#section'));
scrollTo(500);                                               // scroll to y=500px
scrollTo(document.querySelector('#hero'), { offset: -80 }); // offset for sticky headers
Option Type Default Description
behavior 'smooth' | 'instant' 'smooth' Scroll behavior
block ScrollLogicalPosition 'start' Vertical alignment
offset number 0 Pixel offset (e.g. -80 for a sticky nav)

scrollToTop(options?) / scrollToBottom(options?)

scrollToTop();
scrollToBottom({ behavior: 'instant' });

getScrollPosition(container?)

Returns the current scroll position and scroll percentage for the page or any scrollable container.

const { x, y, percentX, percentY } = getScrollPosition();
// percentY → how far down the page (0–100)

const pos = getScrollPosition(document.querySelector('.sidebar'));

onScroll(callback, options?)

Throttled scroll listener. Returns a cleanup function.

const stop = onScroll(({ x, y, percentX, percentY }) => {
  console.log(`Scrolled ${percentY}% down`);
}, { throttle: 100 });

stop(); // removes the listener
Option Type Default Description
throttle number 100 Minimum ms between callbacks
container Element window Scrollable container to listen on

isInViewport(element, options?)

if (isInViewport(document.querySelector('.card'), { threshold: 0.5 })) {
  // At least 50% of the card is visible
}
Option Type Default Description
threshold number 0 0–1 portion of element that must be visible

lockScroll() / unlockScroll()

lockScroll();   // body stops scrolling, position saved
unlockScroll(); // position restored precisely — no layout jump

scrollSpy(sectionsSelector, linksSelector, options?)

Watches scroll position and automatically adds an active class to the nav link matching the current section.

const stop = scrollSpy(
  'section[id]',
  'nav a',
  { offset: 80, activeClass: 'scroll-spy-active' }
);

stop(); // remove listener
nav a.scroll-spy-active {
  color: #00ffaa;
  border-bottom: 1px solid currentColor;
}
Option Type Default Description
offset number 0 px from top to trigger section change
activeClass string 'scroll-spy-active' Class added to the active link

onScrollEnd(callback, options?)

Fires once the user has stopped scrolling for a configurable delay.

const stop = onScrollEnd(() => {
  console.log('User stopped scrolling!');
  saveScrollPosition();
}, { delay: 200 });

stop();
Option Type Default Description
delay number 150 ms of idle scrolling before callback fires
container Element window Scrollable container to watch

scrollIntoViewIfNeeded(element, options?)

Scrolls to an element only if it is outside the visible viewport. If it's already visible enough, nothing happens.

scrollIntoViewIfNeeded(document.querySelector('.card'));
scrollIntoViewIfNeeded(element, { threshold: 0.5, offset: -80 });
Option Type Default Description
threshold number 1 0–1 visibility ratio required to skip scrolling
offset number 0 Pixel offset applied when scrolling
behavior 'smooth' | 'instant' 'smooth' Scroll behavior

easeScroll(target, options?) + Easings

Scroll to a position with a fully custom easing curve. Returns a Promise that resolves when animation completes.

await easeScroll('#contact', { duration: 800, easing: Easings.easeOutElastic });

// Chain animations
await easeScroll('#hero',     { duration: 600, easing: Easings.easeInOutCubic });
await easeScroll('#features', { duration: 400, easing: Easings.easeOutQuart });

// BYO easing function — any (t: 0→1) => (0→1)
easeScroll(element, { easing: t => t * t * t });
Option Type Default Description
duration number 600 Animation duration in ms
easing (t: number) => number Easings.easeInOutCubic Easing function
offset number 0 Pixel offset applied to target

Built-in easings: linear, easeInQuad, easeOutQuad, easeInOutQuad, easeInCubic, easeOutCubic, easeInOutCubic, easeInQuart, easeOutQuart, easeOutElastic, easeOutBounce


scrollSequence(steps)

Run multiple easeScroll animations one after another as a choreographed sequence. Supports pauses between steps. Returns { promise, cancel }.

const { promise, cancel } = scrollSequence([
  { target: '#intro',    duration: 600 },
  { target: '#features', duration: 800, pause: 400 },  // pause 400ms before next step
  { target: '#pricing',  duration: 600, easing: Easings.easeOutElastic },
]);

await promise;       // resolves when all steps complete
cancel();            // abort at any point mid-sequence
Step option Type Default Description
target Element | string | number Scroll destination (required)
duration number 600 Duration of this step in ms
easing Function easeInOutCubic Easing for this step
offset number 0 Pixel offset
pause number 0 ms to wait after this step before the next

parallax(targets, options?)

Attach a parallax scroll effect to one or more elements. Each element moves relative to its original position at the given speed multiplier.

// speed < 1 = moves slower than scroll (background feel)
// speed > 1 = moves faster than scroll (foreground feel)
// speed < 0 = moves in the opposite direction to scroll

const destroy = parallax('.hero-bg', { speed: 0.4 });
const destroy = parallax('.clouds',  { speed: -0.2, axis: 'x' });
const destroy = parallax([el1, el2], { speed: 0.6, axis: 'both' });

destroy(); // removes the effect and resets all transforms
Option Type Default Description
speed number 0.5 Movement multiplier
axis 'y' | 'x' | 'both' 'y' Axis to apply parallax on
container Element window Scrollable container

scrollProgress(element, callback, options?)

Track how far the user has scrolled through a specific element, independent of overall page progress.

  • 0 = element top just entered the bottom of the viewport
  • 1 = element bottom just exited the top of the viewport
const stop = scrollProgress('#article', (progress) => {
  progressBar.style.width = `${progress * 100}%`;
  if (progress === 1) console.log('Article fully read!');
});

stop(); // cleanup
Option Type Default Description
offset number 0 Pixel adjustment to progress calculation

snapToSection(sections, options?)

After the user stops scrolling, automatically snap to the nearest section. Returns a destroy function.

const destroy = snapToSection('section[id]', {
  delay: 150,                        // ms to wait after scroll stops (default: 150)
  offset: -70,                       // account for sticky nav (default: 0)
  duration: 500,                     // snap animation duration (default: 500)
  easing: Easings.easeInOutCubic     // snap animation easing
});

destroy(); // remove snap behaviour
Option Type Default Description
delay number 150 ms after scrolling stops before snap fires
offset number 0 Pixel offset applied to snap target
duration number 500 Snap animation duration in ms
easing Function Easings.easeInOutCubic Snap animation easing

Works on both window scroll and scrollable containers. Pass an array of elements instead of a selector for more control.


React Hooks

import {
  useScrollPosition, useInViewport, useScrollTo,
  useScrolledPast, useScrollDirection
} from 'scroll-snap-kit/hooks';

Requires React 16.8+. React is a peer dependency — install it separately.


useScrollPosition(options?)

function ProgressBar() {
  const { percentY } = useScrollPosition({ throttle: 50 });
  return <div style={{ width: `${percentY}%` }} className="progress-bar" />;
}

useInViewport(options?)

function FadeInCard() {
  const [ref, inView] = useInViewport({ threshold: 0.2, once: true });
  return (
    <div ref={ref} style={{ opacity: inView ? 1 : 0, transition: 'opacity 0.5s' }}>
      Fades in when visible!
    </div>
  );
}

useScrollTo()

function Page() {
  const [containerRef, scrollToTarget] = useScrollTo();
  const sectionRef = useRef(null);
  return (
    <div ref={containerRef} style={{ overflowY: 'scroll', height: '400px' }}>
      <button onClick={() => scrollToTarget(sectionRef.current)}>Jump</button>
      <div ref={sectionRef}>Target</div>
    </div>
  );
}

useScrolledPast(threshold?)

function BackToTopButton() {
  const scrolledPast = useScrolledPast(300);
  return scrolledPast ? <button onClick={scrollToTop}>↑ Top</button> : null;
}

useScrollDirection()

function HideOnScrollNav() {
  const direction = useScrollDirection();
  return (
    <nav style={{ transform: direction === 'down' ? 'translateY(-100%)' : 'translateY(0)' }}>
      My Navbar
    </nav>
  );
}

Tree-shaking

All exports are named and side-effect free:

import { scrollToTop } from 'scroll-snap-kit';             // ~400 bytes
import { onScroll, scrollSpy } from 'scroll-snap-kit/utils';
import { useScrollPosition } from 'scroll-snap-kit/hooks';

Browser support

All modern browsers. easeScroll and scrollSequence use requestAnimationFrame. useInViewport / isInViewport use IntersectionObserver — polyfill if you need IE11.


Changelog

v2.0.0

  • scrollSequence() — chain multiple scroll animations with pauses and cancel support
  • parallax() — multi-element parallax with configurable speed, axis, and cleanup
  • scrollProgress() — per-element scroll progress tracking (0→1)
  • snapToSection() — auto-snap to nearest section after scrolling stops

v1.1.0

  • scrollSpy() — highlight nav links by active section
  • onScrollEnd() — callback when scrolling stops
  • scrollIntoViewIfNeeded() — scroll only when off-screen
  • easeScroll() + Easings — custom easing engine with 11 built-in curves

v1.0.0

  • 🎉 Initial release — 8 core utilities and 5 React hooks

License

MIT © Fabian Faraz Farid

Contributors