A React component to execute functions when scrolling to specific elements in any scrollable container
npx @tessl/cli install tessl/npm-react-waypoint@10.3.0React Waypoint is a React component that executes callback functions whenever you scroll to an element. It works in all containers that can scroll, including the browser window, and provides callbacks for enter/leave events when elements cross viewport boundaries.
npm install react-waypointimport { Waypoint } from 'react-waypoint';For CommonJS:
const { Waypoint } = require('react-waypoint');import React from 'react';
import { Waypoint } from 'react-waypoint';
function App() {
const handleWaypointEnter = (args) => {
console.log('Waypoint entered!', args.currentPosition);
};
const handleWaypointLeave = (args) => {
console.log('Waypoint left!', args.currentPosition);
};
return (
<div>
<div style={{ height: '1000px' }}>Scroll down...</div>
{/* Basic waypoint */}
<Waypoint
onEnter={handleWaypointEnter}
onLeave={handleWaypointLeave}
/>
{/* Waypoint with child element */}
<Waypoint onEnter={handleWaypointEnter}>
<div style={{ padding: '20px', background: '#f0f0f0' }}>
This content triggers waypoint events
</div>
</Waypoint>
</div>
);
}React Waypoint operates through several key mechanisms:
getBoundingClientRect()onNextTick scheduling to optimize performance and avoid layout thrashingThe main React component that provides scroll-based element visibility detection.
class Waypoint extends React.Component<WaypointProps, {}> {
static above: string;
static below: string;
static inside: string;
static invisible: string;
}
interface WaypointProps {
/** Function called when waypoint enters viewport */
onEnter?: (args: CallbackArgs) => void;
/** Function called when waypoint leaves viewport */
onLeave?: (args: CallbackArgs) => void;
/** Function called when waypoint position changes */
onPositionChange?: (args: CallbackArgs) => void;
/** Whether to activate on horizontal scrolling instead of vertical */
horizontal?: boolean;
/** Distance from top of container in pixels or percentage (e.g., "20px", "20%") */
topOffset?: string | number;
/** Distance from bottom of container in pixels or percentage (e.g., "20px", "20%") */
bottomOffset?: string | number;
/** Custom ancestor to determine if the target is visible in it */
scrollableAncestor?: any;
/** If the onEnter/onLeave events are to be fired on rapid scrolling */
fireOnRapidScroll?: boolean;
/** Use this prop to get debug information in the console log */
debug?: boolean;
/** Child elements to wrap with waypoint functionality */
children?: React.ReactNode;
}
interface CallbackArgs {
/** The position that the waypoint has at the moment */
currentPosition: string;
/** The position that the waypoint had before */
previousPosition: string;
/** The native scroll event that triggered the callback (may be missing) */
event?: Event;
/** The waypoint's distance to the top of the viewport */
waypointTop: number;
/** The waypoint's distance to the bottom of the viewport */
waypointBottom: number;
/** The distance from the scrollable ancestor to the viewport top */
viewportTop: number;
/** The distance from the bottom of the scrollable ancestor to the viewport top */
viewportBottom: number;
}Static constants representing different waypoint positions relative to the viewport.
/** Waypoint is above the viewport */
Waypoint.above: string; // "above"
/** Waypoint is below the viewport */
Waypoint.below: string; // "below"
/** Waypoint is inside (visible in) the viewport */
Waypoint.inside: string; // "inside"
/** Waypoint is invisible (e.g., display: none) */
Waypoint.invisible: string; // "invisible"React Waypoint provides three main callback events for different use cases:
onEnter: Triggered when the waypoint becomes visible in the viewport
<Waypoint
onEnter={({ currentPosition, previousPosition, event, waypointTop, waypointBottom, viewportTop, viewportBottom }) => {
console.log('Element entered viewport');
// Useful for: lazy loading content, analytics tracking, animations
}}
/>onLeave: Triggered when the waypoint leaves the viewport
<Waypoint
onLeave={({ currentPosition, previousPosition, event, waypointTop, waypointBottom, viewportTop, viewportBottom }) => {
console.log('Element left viewport');
// Useful for: cleanup, pausing videos, stopping animations
}}
/>onPositionChange: Triggered whenever the waypoint's position changes
<Waypoint
onPositionChange={({ currentPosition, previousPosition, event, waypointTop, waypointBottom, viewportTop, viewportBottom }) => {
console.log(\`Position changed from \${previousPosition} to \${currentPosition}\`);
// Useful for: scroll spies, position-based UI updates
}}
/>Control when waypoint events trigger using flexible offset options:
Percentage-based offsets (relative to container height):
<Waypoint
topOffset="20%" // Trigger 20% from top of viewport
bottomOffset="10%" // Trigger 10% from bottom of viewport
/>Pixel-based offsets:
<Waypoint
topOffset={100} // Trigger 100px from top
bottomOffset="50px" // Trigger 50px from bottom
/>Negative offsets (trigger before entering viewport):
<Waypoint
topOffset="-100px" // Trigger 100px before entering viewport
bottomOffset="-20%" // Trigger when 20% outside bottom
/>Enable horizontal scroll detection for side-scrolling interfaces:
<Waypoint
horizontal={true}
onEnter={() => console.log('Entered horizontal viewport')}
/>Specify a custom scrollable ancestor instead of auto-detection:
function ScrollableContainer() {
const containerRef = useRef(null);
return (
<div ref={containerRef} style={{ height: '300px', overflow: 'auto' }}>
<div style={{ height: '1000px' }}>
<Waypoint
scrollableAncestor={containerRef.current}
onEnter={() => console.log('Entered custom container viewport')}
/>
</div>
</div>
);
}Control behavior during rapid scrolling where waypoint might be skipped:
<Waypoint
fireOnRapidScroll={true} // Default: fires both onEnter and onLeave during rapid scroll
fireOnRapidScroll={false} // Skip events during rapid scroll
/>Enable detailed console logging for development:
<Waypoint
debug={true}
onEnter={() => console.log('Debug information will show in console')}
/>Wrap existing elements to track their visibility:
{/* Without children - creates invisible span element */}
<Waypoint onEnter={handleEnter} />
{/* With children - tracks the child element */}
<Waypoint onEnter={handleEnter}>
<img src="image.jpg" alt="Lazy loaded image" />
</Waypoint>
{/* Multiple children */}
<Waypoint onEnter={handleEnter}>
<div className="content-section">
<h2>Section Title</h2>
<p>Section content...</p>
</div>
</Waypoint>Lazy Loading Images:
<Waypoint
onEnter={() => setImageSrc(actualImageUrl)}
topOffset="-100px" // Start loading before entering viewport
>
<img src={imageSrc || placeholderSrc} alt="Lazy loaded" />
</Waypoint>Infinite Scroll:
<Waypoint
key={currentPage} // Important: use key to recreate waypoint
onEnter={loadMoreContent}
bottomOffset="100px" // Trigger before reaching bottom
/>Scroll Spy Navigation:
<Waypoint
onPositionChange={({ currentPosition }) => {
if (currentPosition === 'inside') {
setActiveSection('section1');
}
}}
>
<section id="section1">Content...</section>
</Waypoint>Analytics Tracking:
<Waypoint
onEnter={() => analytics.track('Section Viewed')}
topOffset="50%" // Trigger when 50% visible
/>