Scroll Utilities
Scroll utilities: smooth scroll to target elements, scroll lock during tour steps, and overflow container handling
Utilities for scrolling elements into view and managing scroll behavior during tours.
Why Use These Utilities?
Tour steps often target elements that are:
- Below the fold - Need to scroll into view
- In scrollable containers - Nested scroll areas
- Behind modals - Need scroll locking
scrollIntoView
Scroll an element into the viewport with configurable behavior.
Usage
import { scrollIntoView } from '@tour-kit/core';
// Default behavior (smooth, centered)
const element = document.getElementById('target');
await scrollIntoView(element);
// Custom configuration
await scrollIntoView(element, {
behavior: 'instant',
block: 'start',
offset: 40,
});Parameters
Prop
Type
Configuration
Prop
Type
Return Value
Promise<void> - Resolves when scrolling completes (or immediately if disabled/already visible).
Already Visible Elements
import { scrollIntoView, isElementVisible } from '@tour-kit/core';
// scrollIntoView skips scrolling if element is already visible
const element = document.getElementById('visible-element');
await scrollIntoView(element); // Resolves immediately if visiblescrollTo
Scroll a container to a specific position.
Usage
import { scrollTo } from '@tour-kit/core';
// Scroll window
scrollTo(window, { top: 0 }); // Scroll to top
// Scroll container
const container = document.getElementById('scroll-container');
scrollTo(container, { top: 100, left: 0 }, 'smooth');Parameters
Prop
Type
getScrollPosition
Get the current scroll position of a container.
Usage
import { getScrollPosition } from '@tour-kit/core';
// Window scroll position
const windowPos = getScrollPosition();
console.log(windowPos.x, windowPos.y);
// Container scroll position
const container = document.getElementById('scroll-container');
const containerPos = getScrollPosition(container);Return Value
{ x: number; y: number }lockScroll
Lock page scrolling and return an unlock function. Essential for modal overlays.
Usage
import { lockScroll } from '@tour-kit/core';
function TourModal() {
useEffect(() => {
// Lock scroll when modal opens
const unlock = lockScroll();
// Unlock when modal closes
return () => unlock();
}, []);
return <div className="modal">Tour content</div>;
}Return Value
() => void - Function to restore scroll behavior.
How It Works
- Saves current scroll position
- Fixes body position - Prevents scroll while maintaining visual position
- Restores on unlock - Returns to original scroll position
// Internal implementation:
function lockScroll(): () => void {
const scrollY = window.scrollY;
document.body.style.position = 'fixed';
document.body.style.top = `-${scrollY}px`;
document.body.style.width = '100%';
document.body.style.overflowY = 'scroll'; // Prevent layout shift
return () => {
document.body.style.position = '';
document.body.style.top = '';
document.body.style.width = '';
document.body.style.overflowY = '';
window.scrollTo(0, scrollY); // Restore position
};
}Always call the unlock function when done. Failing to unlock will leave the page in a broken state.
Scroll Behavior Comparison
await scrollIntoView(element, { behavior: 'smooth' });- Animated scrolling
- Better UX for visible transitions
- ~500ms duration
- Respects
prefers-reduced-motion
await scrollIntoView(element, { behavior: 'instant' });- Immediate jump
- Use for programmatic navigation
- Zero delay
- Use for
prefers-reduced-motion
Block Alignment Options
// 'start' - Element at top of viewport
await scrollIntoView(element, { block: 'start' });
// 'center' - Element centered in viewport (default)
await scrollIntoView(element, { block: 'center' });
// 'end' - Element at bottom of viewport
await scrollIntoView(element, { block: 'end' });
// 'nearest' - Minimum scroll needed
await scrollIntoView(element, { block: 'nearest' });Nested Scroll Containers
For elements in nested scrollable containers:
import { scrollIntoView, getScrollParent } from '@tour-kit/core';
async function scrollToNestedElement(selector: string) {
const element = document.querySelector(selector);
if (!element) return;
// Find the scroll parent
const scrollParent = getScrollParent(element);
// Scroll both the container and window if needed
if (scrollParent !== window) {
element.scrollIntoView({ block: 'center', behavior: 'smooth' });
}
// Also ensure container is visible in window
await scrollIntoView(element);
}Reduced Motion Support
import { scrollIntoView, prefersReducedMotion } from '@tour-kit/core';
const element = document.getElementById('target');
await scrollIntoView(element, {
behavior: prefersReducedMotion() ? 'instant' : 'smooth',
});Complete Tour Scroll Example
import { scrollIntoView, lockScroll } from '@tour-kit/core';
import { usePrefersReducedMotion } from '@tour-kit/core';
function TourOverlay({ targetElement, children }) {
const prefersReducedMotion = usePrefersReducedMotion();
useEffect(() => {
// Lock background scroll
const unlock = lockScroll();
// Scroll target into view
scrollIntoView(targetElement, {
behavior: prefersReducedMotion ? 'instant' : 'smooth',
block: 'center',
});
return unlock;
}, [targetElement, prefersReducedMotion]);
return <div className="tour-overlay">{children}</div>;
}Related
- useSpotlight - Uses scroll utilities internally
- DOM Utilities - Element visibility detection
- useMediaQuery - Reduced motion detection