@thednp/position-observer
v1.0.7
Published
🏯 PositionObserver is a JavaScript tool that provides a way to asynchronously observe changes in the position of a target element within its viewport.
Downloads
240,471
Maintainers
Readme
PositionObserver
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 uses a very simple design.
The PositionObserver provides a way to asynchronously observe changes in the position of a target element with an ancestor element or with a top-level document's viewport. It tries to do what you would expect after your element has intersected as if you would listen to resize
or scroll
without attaching event listeners.
Installation
npm i @thednp/position-observer
yarn add @thednp/position-observer
pnpm add @thednp/position-observer
deno add npm:@thednp/position-observer@latest
Usage
// import the PositionObserver class
import PositionObserver, { type PositionObserverEntry } from '@thednp/position-observer';
// find a suitable target
const myTarget = 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(myTarget);
if (entry.target === myTarget/* 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'),
}
// 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,
// parent <div#myModal> root clientWidth
clientWidth: number,
// root <div#myModal> clientHeight
clientHeight: number,
}]
// 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: Element | 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 IFrame
s. 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 Element target, it will throw anError
; - 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 or left for a given Element target in relation to its designated root, as well as the clientWidth and clientHeight of that parent;
- when the target Element is intersecting with the bounds of the designated viewport and at least one of the observed values changes, only then 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 invokinggetBoundingClientRect()
again on your target; - this implementation is partially inspired by the async-bounds, the async model is very efficient;
- 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
orvisibility: 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 callingobserver.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 arequestAnimationFrame()
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 ordisconnect
the observer to make room in the main thread; - how about an idea to make your PositionObserver instance work like a
ResizeObserver
, well you can simply filter your callback with the inequality ofentry.boundingClientRect.height
andlastHeight
ORentry.boundingClientRect.width
andlastWidth
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.