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 🙏

© 2024 – Pkg Stats / Ryan Hefner

@thednp/position-observer

v1.0.1

Published

🏯 The PositionObserver class provides a way to asynchronously observe changes in the position of a target element with the top-level document's viewport.

Downloads

462

Readme

PositionObserver

Coverage Status ci NPM Version typescript version vitest version vite version

If you were looking for an observer that could replace all your resize and/or scroll EventListeners, this should be it! The PositionObserver works with the IntersectionObserver API under the hood and the functionality resembles very much to it, but with a much more simple design.

What can you use after your element has intersected? How to listen to resize or scroll without attaching event listeners? What's the most efficient solution to observe scroll, size and position all at once? Why doesn't MutationObserver cover this? Here's where the PositionObserver comes in handy.

Installation

npm i @thednp/position-observer
yarn add @thednp/position-observer
pnpm install @thednp/position-observer
deno install npm:@thednp/position-observer@latest

Usage

// import the PositionObserver class
import PositionObserver, { type PositionObserverEntry } from '@thednp/position-observer';

// find a suitable target
const target = document.getElementById('myElement');

// define a callback
const callback = (entries: PositionObserverEntry[], currentObserver: PositionObserver) => {
  /* keep an eye on your entries */
  // console.log(entries);

  // access the observer inside your callback
  // to find entry for myTarget
  const entry = currentObserver.getEntry(target);
  if (entry.isVisible/* and/or other conditions */) {
    // do something about it
  }
};

// set some options
const options = {
  // if not set, it will use the document.documentElement
  root: document.getElementById('myModal-or-something'),
}

// create the observer
const observer = new PositionObserver(callback, options);

// start observing the target element position
observer.observe(target);

// when the position of the element changes from DOM manipulations and/or
// the position change was triggered by either scroll / resize events
// these will be the entries of this observer callback example
[{
  // the observed target element
  target: <div#myElement>,
  // the target's bounding client react
  boundingClientRect: DOMRect,
  // this will always be true when at least one pixel of the target is visible in the viewport
  isVisible: true,
}]

// anytime you need the entry, find it!!
observer.getEntry(target);

// stop observing the changes for #myElement at any point
observer.unobserve(target);

// when no targets require observation
// you should disconect the observer
observer.disconect();

Instance Options

root: HTMLElement | undefined

Sets the instance._root private property which identifies the Element whose bounds are treated as the bounding box of the viewport for the element which is the observer's target. If not defined then the Document.documentElement will be used.

When observing multiple targets from a scrollable parent element, that parent must be set as root. The same applies to embeddings and IFrames. See the ScrollSpy example for implementation details.

How it works

  • when the observer is initialized without a callback, it will throw an Error;
  • if you call the observe() method without a valid HTMLElement target, it will throw an Error;
  • if the target isn't attached to the DOM, it will not be added to the observer entries;
  • once propertly set up, the PositionObserver will observe the changes of either top, left, width or height for a given HTMLElement target, all in relation to the designated parent element;
  • only when at least one of these values changes the target's entry will be queued for the callback runtime.

Notes

  • use with caution: for performance reasons, if your callback is focused on values of the target's bounding client rect, be sure to make use of entry.boundingClientRect values (observer.getEntry(target)) instead of invoking getBoundingClientRect() again on your target;
  • this implementation is partially inspired by the async-bounds, the async model is very efficient;
  • the entry.isVisible property is limited to the position of the target within the specified viewport, it doesn't take into account CSS properties or other specific attributes;
  • if nothing happens when observing a target, please know that the observer's runtime will only call the callback for elements that are descendents of the given root element; this also means that if a target is removed from the document, the target's entry will not be queued into the runtime;
  • also if the target element is hidden with either display: none or visibility: hidden or attributes with the same effect, the bounding box always has ZERO values and never changes, so make sure to have your target visible before calling observer.observe(target);
  • because the functionality is powered by requestAnimationFrame and IntersectionObserver, all computation is always processed asynchronously before the next paint, in some cases you might want to consider wrapping your PositionObserver callback in a requestAnimationFrame() invokation for a consistent syncronicity and to eliminate any unwanted anomalies;
  • while the performance benefits over the use of event listeners is undeniable, it's still important to unobserve targets or disconnect the observer to make room in the main thread;
  • if you keep track of the changes to the entry.isVisible property you could say you have an equivalent for IntersectionObserver.isIntersecting;
  • how about an idea to make your PositionObserver instance work like a ResizeObserver, well you can simply filter your callback with the inequality of entry.boundingClientRect.height and lastHeight OR entry.boundingClientRect.width and lastWidth cases, easy right?
  • lastly, the PositionObserver will observe changes to all sides of a target, but in some cases you might want to narrow down to the changes triggered by scroll, mainly top and left, in which case you can filter your callback to a single side entry.boundingClientRect.top !== lastTop, further increasing performance.

License

The PositionObserver is released under the MIT license.