npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

nice-react-scroll

v2.0.0

Published

Performance-optimized scroll components for React with centralized scroll management, sticky positioning, and intersection observer utilities

Downloads

27

Readme

nice-react-scroll

Performance-optimized scroll components for React with centralized scroll management, sticky positioning, fade effects, and section navigation.

npm version License: MIT

Table of Contents

Features

  • 🚀 Single RAF-batched scroll listener - One centralized scroll manager for optimal performance
  • 📌 Sticky positioning - Multiple sticky elements with automatic stacking using IntersectionObserver
  • 🎯 Section navigation - Smart navigation with IntersectionObserver and smooth scrolling
  • 🌊 Fade on scroll - Optimized opacity transitions with ResizeObserver caching
  • 📱 Mobile responsive - Built-in support for mobile-specific behavior
  • 🎨 Styled Components - Full integration with styled-components
  • TypeScript - Full type safety and IntelliSense support
  • High performance - 60-90% fewer renders, zero scroll events, GPU-accelerated animations

Installation

npm install nice-react-scroll

Peer Dependencies

npm install react react-dom styled-components

Quick Start

import {
  ScrollProvider,
  StickyProvider,
  Sticky,
  StickySectionLinks,
  StickySection,
  FadeOnScroll
} from 'nice-react-scroll'

function App() {
  return (
    <ScrollProvider>
      <StickyProvider>
        {/* Sticky header */}
        <Sticky order={0}>
          <Header />
        </Sticky>

        {/* Sticky navigation */}
        <Sticky order={1}>
          <StickySectionLinks
            links={[
              { href: '#intro', label: 'Introduction' },
              { href: '#features', label: 'Features' }
            ]}
          />
        </Sticky>

        {/* Content with fade effect */}
        <FadeOnScroll startPosition={20} peakPosition={50}>
          <StickySection id="intro">
            <h1>Welcome</h1>
          </StickySection>
        </FadeOnScroll>

        <StickySection id="features">
          <h2>Features</h2>
        </StickySection>
      </StickyProvider>
    </ScrollProvider>
  )
}

Components

ScrollProvider

Centralized scroll manager that provides a single requestAnimationFrame-batched scroll listener.

import { ScrollProvider } from 'nice-react-scroll'

function App() {
  return (
    <ScrollProvider>
      {/* Your app */}
    </ScrollProvider>
  )
}

Why use ScrollProvider?

  • Prevents multiple scroll listeners from degrading performance
  • Batches scroll updates using RAF for smoother animations
  • Provides useScroll() hook for components that need scroll position

useScroll Hook

Access the current scroll position from any component within ScrollProvider.

import { useScroll } from 'nice-react-scroll'

function MyComponent() {
  const scrollY = useScroll()

  return <div>Scroll position: {scrollY}px</div>
}

StickyProvider & Sticky

Create sticky elements that stack on top of each other with automatic offset calculation.

import { StickyProvider, Sticky } from 'nice-react-scroll'

function App() {
  return (
    <StickyProvider>
      {/* First sticky (top) */}
      <Sticky order={0}>
        <Header />
      </Sticky>

      {/* Second sticky (below first) */}
      <Sticky order={1}>
        <Subheader />
      </Sticky>

      {/* Content */}
      <main>...</main>
    </StickyProvider>
  )
}

Props:

  • order (number): Stacking order (lower numbers appear above). Default: 0

How it works:

  • Elements with order={0} appear first (topmost)
  • Elements with order={1} stack below order={0}
  • Automatically calculates offsets for proper stacking
  • Uses IntersectionObserver for efficient sticky detection (no scroll events!)
  • Callbacks fire only when sticky state actually changes

StickySectionLinks

Navigation component with smooth scrolling and automatic active state detection.

import { StickySectionLinks } from 'nice-react-scroll'

function Navigation() {
  return (
    <StickySectionLinks
      links={[
        { href: '#intro', label: 'Introduction' },
        { href: '#about', label: 'About Us' },
        { href: '#contact', label: 'Contact' }
      ]}
    />
  )
}

Props:

  • links (StickySectionLink[]): Array of navigation links
    • href (string): Section ID with # prefix (e.g., '#intro')
    • label (string): Link text
  • className? (string): Additional CSS class
  • renderLink? (function): Custom renderer for link items

Features:

  • Smooth scroll animation (300ms ease-in-out)
  • Active state detection using IntersectionObserver
  • Automatic offset for sticky headers
  • Works seamlessly with StickySection components

Example:

<StickySectionLinks
  links={[
    { href: '#roles', label: 'Our Roles' },
    { href: '#problem', label: 'The Problem' },
    { href: '#research', label: 'Research' }
  ]}
/>

StickySection

Wrapper component that provides a type-safe way to create sections with IDs for use with StickySectionLinks.

import { StickySection } from 'nice-react-scroll'

function Content() {
  return (
    <>
      <StickySection id="intro">
        <h2>Introduction</h2>
        <p>Welcome to our site</p>
      </StickySection>

      <StickySection id="about">
        <h2>About Us</h2>
        <p>Learn more about what we do</p>
      </StickySection>
    </>
  )
}

Props:

  • id (string, required): Unique identifier for the section
  • children (ReactNode): Content to render inside the section
  • as? (string): HTML element type to use. Default: "section"
  • render? (function): Custom render function (overrides as prop)
  • className? (string): Optional className for styling
  • style? (CSSProperties): Optional inline styles

Using different elements:

<StickySection id="hero" as="div">
  <Hero />
</StickySection>

<StickySection id="article" as="article">
  <Article />
</StickySection>

Using custom render function:

<StickySection
  id="custom"
  render={({ id, children }) => (
    <article id={id} className="custom-wrapper">
      <div className="inner">{children}</div>
    </article>
  )}
>
  <Content />
</StickySection>

FadeOnScroll

Apply opacity transitions to elements based on scroll position.

import { FadeOnScroll } from 'nice-react-scroll'

function Hero() {
  return (
    <FadeOnScroll
      startPosition={0}
      peakPosition={30}
      endPosition={60}
      startOpacity={0}
      peakOpacity={1}
      endOpacity={0.3}
    >
      <img src="hero.jpg" alt="Hero" />
    </FadeOnScroll>
  )
}

Props:

  • startPosition (number): Scroll % where fade begins. Default: 0
  • peakPosition (number): Scroll % where opacity reaches peak. Default: 50
  • endPosition? (number): Scroll % where fade ends. Optional
  • startOpacity (number): Starting opacity (0-1). Default: 0
  • peakOpacity (number): Peak opacity (0-1). Default: 1
  • endOpacity? (number): Ending opacity (0-1). Optional

Scroll position calculation:

  • 0% = element just enters viewport (bottom of element at bottom of viewport)
  • 50% = element center is at viewport center
  • 100% = element exits viewport (top of element at top of viewport)

Common patterns:

Fade in on scroll:

<FadeOnScroll
  startPosition={20}
  peakPosition={50}
  startOpacity={0}
  peakOpacity={1}
>
  <Content />
</FadeOnScroll>

Fade in and out:

<FadeOnScroll
  startPosition={10}
  peakPosition={50}
  endPosition={90}
  startOpacity={0.2}
  peakOpacity={1}
  endOpacity={0.2}
>
  <Content />
</FadeOnScroll>

Parallax-style fade:

<FadeOnScroll
  startPosition={0}
  peakPosition={30}
  endPosition={60}
  startOpacity={0.1}
  peakOpacity={1}
  endOpacity={0.1}
>
  <BackgroundImage />
</FadeOnScroll>

Complete Example

import React from 'react'
import {
  ScrollProvider,
  StickyProvider,
  Sticky,
  StickySectionLinks,
  StickySection,
  FadeOnScroll,
  useScroll
} from 'nice-react-scroll'
import styled from 'styled-components'

const Header = styled.header`
  background: white;
  padding: 1rem;
`

function ScrollIndicator() {
  const scrollY = useScroll()
  return <div>Scroll: {scrollY}px</div>
}

function App() {
  return (
    <ScrollProvider>
      <StickyProvider>
        {/* Sticky Header */}
        <Sticky order={0}>
          <Header>
            <h1>My Website</h1>
            <ScrollIndicator />
          </Header>
        </Sticky>

        {/* Sticky Navigation */}
        <Sticky order={1}>
          <StickySectionLinks
            links={[
              { href: '#intro', label: 'Introduction' },
              { href: '#about', label: 'About' },
              { href: '#services', label: 'Services' },
              { href: '#contact', label: 'Contact' }
            ]}
          />
        </Sticky>

        {/* Hero with Fade */}
        <FadeOnScroll
          startPosition={0}
          peakPosition={40}
          startOpacity={0.3}
          peakOpacity={1}
        >
          <StickySection id="intro">
            <h2>Welcome</h2>
            <p>Scroll to explore</p>
          </StickySection>
        </FadeOnScroll>

        {/* Regular Sections */}
        <StickySection id="about">
          <h2>About Us</h2>
          <p>Learn more about what we do</p>
        </StickySection>

        <StickySection id="services">
          <h2>Our Services</h2>
          <p>Discover our offerings</p>
        </StickySection>

        <StickySection id="contact">
          <h2>Get in Touch</h2>
          <p>Contact us today</p>
        </StickySection>
      </StickyProvider>
    </ScrollProvider>
  )
}

export default App

Advanced Usage

Custom Link Rendering

<StickySectionLinks
  links={links}
  renderLink={(link, isActive, onClick) => (
    <CustomLink
      href={link.href}
      onClick={onClick}
      className={isActive ? 'active' : ''}
    >
      <span>{link.label}</span>
    </CustomLink>
  )}
/>

Multiple Sticky Elements

<StickyProvider>
  <Sticky order={0}>
    <TopNav />
  </Sticky>

  <Sticky order={1}>
    <Breadcrumbs />
  </Sticky>

  <Sticky order={2}>
    <StickySectionLinks links={links} />
  </Sticky>

  <StickySection id="content">
    <Content />
  </StickySection>
</StickyProvider>

Dynamic Fade Effects

function DynamicFade() {
  const [config, setConfig] = useState({
    startPosition: 20,
    peakPosition: 50,
    startOpacity: 0,
    peakOpacity: 1
  })

  return (
    <FadeOnScroll {...config}>
      <Content />
    </FadeOnScroll>
  )
}

Performance Tips

  1. Use ScrollProvider - Always wrap your app in <ScrollProvider> for optimal performance
  2. IntersectionObserver Magic - Sticky detection runs off the main thread with zero scroll events
  3. ResizeObserver Caching - Dimensions are cached and updated only when elements resize
  4. GPU Acceleration - will-change CSS hints enable hardware acceleration
  5. Smart Updates - Components only re-render when values actually change (thresholds applied)
  6. Passive Listeners - The package uses passive listeners by default for best scroll performance

Performance Improvements

Recent optimizations provide dramatic performance gains:

  • 60-90% fewer component renders during scroll
  • 100% elimination of scroll event handlers (IntersectionObserver FTW!)
  • 40-60% better frame times for smoother animations
  • 50-70% less CPU usage during scroll operations
  • Consistent 60 FPS even with multiple sticky elements

Browser Support

  • Modern browsers (Chrome, Firefox, Safari, Edge)
  • Requires IntersectionObserver support
  • Polyfill recommended for older browsers

TypeScript Support

Full TypeScript support with exported types:

import type {
  StickySectionLink,
  StickySectionLinksProps,
  StickySectionProps,
  StickyProps,
  FadeOnScrollProps,
  ScrollContextValue,
  StickyContextValue
} from 'nice-react-scroll'

API Documentation

For detailed API documentation, see API.md.

Contributing

Contributions are welcome! Please read our Contributing Guide for details on:

  • Development setup
  • Coding standards
  • Submitting pull requests
  • Issue reporting

Please also read our Code of Conduct before contributing.

Changelog

See CHANGELOG.md for a history of changes to this project.

Links

License

MIT © Mohammed Ibrahim