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

implicit-sections-observer

v1.0.0-rc.0

Published

A tiny tool to observe heading-inferred sections and track their viewport intersection on page scroll and resize.

Readme

Implicit Sections Observer

implicit-sections-observer infers sections from your headings and continuously reports how those sections intersect with the viewport on scroll change or page resize. I made it for precisely highlighting table of contents. But with its observation data you are free to do anything you want, not just highlighting a table of contents.

It has two superpowers:

  • Implicit sections: Implicit sections are sections that do not explicitly exist but are understood by its algorithm, just as you understand what a section of this page is without looking for some kind of wrapper element around each section. You are no longer forced to wrap those in <section> elements.
  • Seamless intersection data: You receive continuous intersection information not only when an implicit section enters or leaves the viewport, but on every scroll or page resize while it is partially visible.

Installation

npm i implicit-sections-observer
# or
pnpm add implicit-sections-observer
# or
yarn add implicit-sections-observer

Quick start

Suppose you have content living directly in <body>:

<body>
  <h1>H1 Heading</h1>
  <p>…</p>
  <h2>H2 Heading</h2>
  <p>…</p>
  <!-- and so on -->
</body>
import { implicitSectionsObserver } from 'implicit-sections-observer';

const observer = implicitSectionsObserver(
  {
    // grab the headings to infer the implicit sections
    headings: document.querySelectorAll(':is(h1,h2,h3,h4,h5,h6)'),
  },
  (data) => {
    // You get the observation data here
    console.log(data);
  },
);

// To start observing call the `start` method
observer.start();

// To stop observing call the `stop` method
// observer.stop();

If your content lives inside an <article> (or any other container):

const observer = implicitSectionsObserver(
  {
    // this time grab the headings within the `<article>` to infer the implicit sections
    headings: document.querySelectorAll('article :is(h1, h2, h3, h4, h5, h6)'),
    contentContainer: document.querySelector('article'), // needed for correct last-section height
  },
  (data) => {
    console.log(data);
  },
);

contentContainer defaults to document.body. Passing the actual content container ensures the final implicit section gets the correct height.

Limitation: Nested scroll containers are not supported. The library is designed for the common case of page-level scrolling.

API

Inputs

The first parameter of implicitSectionObserver function expects an object argument of following shape:

interface Inputs {
  // Heading elements that define the start of each implicit section
  headings: NodeListOf<HTMLHeadingElement> | HTMLHeadingElement[];

  // Element that contains all the content (used to calculate the height of the
  // last implicit section). Defaults to `document.body`.
  contentContainer?: HTMLElement | null;

  // Extra space at the top of the viewport. Default: 0
  marginTop?: number;

  // Extra space at the bottom of the viewport. Default: 0
  marginBottom?: number;
}

Callback data

Every time the scroll position changes (or on the initial observation) the callback receives an object with this shape:

interface ObservationData {
  // Entries in document order. Currently intersecting sections come first.
  // Sections that just left the viewport are listed at the end.
  entries: Array<{
    // The heading that starts this implicit section
    target: HTMLHeadingElement;

    // How much of the section is currently visible
    intersectionRatio: number;

    // Whether the section intersects the (possibly margin-adjusted) viewport
    isIntersecting: boolean;

    // Only present on the first intersecting entry.
    // Fraction of the top of that section that is currently above the viewport.
    // e.g. `0.5` means the top half is scrolled out of view.
    topNotIntersectingRatio?: number;
  }>;

  // Handy reference to the topmost intersecting entry
  topIntersectingEntry?: {
    target: HTMLHeadingElement;
    intersectionRatio: number;
    isIntersecting: boolean;
    topNotIntersectingRatio: number;
  };

  // Handy reference to the bottommost intersecting entry.
  // Absent when only one section is intersecting.
  bottomIntersectingEntry?: {
    target: HTMLHeadingElement;
    intersectionRatio: number;
    isIntersecting: boolean;
  };

  // Current `scrollY`
  scrollTop: number;

  // Scroll delta since the previous observation
  scrollDiff: number;

  // `performance.now()` at the moment of this observation
  timestamp: number;

  // Milliseconds since the previous observation
  timeDiff: number;
}

See the actual type definition file included with this library for stricter type definitions.

Methods

// Begin observing
observer.start();

// Stop observing
observer.stop();

// Useful for replacing stale observation data with fresh data (e.g., after an accordion opens inside an intersecting implicit section).
observer.refresh();