In a previous article, I showed you how to refactor the Resize Observer API into something way simpler to use:
// From this
const observer = new ResizeObserver(observerFn)
function observerFn (entries) {
for (let entry of entries) {
// Do something with each entry
}
}
const element = document.querySelector('#some-element')
observer.observe(element);
// To this
const node = document.querySelector('#some-element')
const obs = resizeObserver(node, {
callback({ entry }) {
// Do something with each entry
}
})
Today, we’re going to do the same for MutationObserver
and IntersectionObserver
.
Refactoring Mutation Observer
MutationObserver
has almost the same API as that of ResizeObserver
. So we can practically copy-paste the entire chunk of code we wrote for resizeObserver
to mutationObserver
.
export function mutationObserver(node, options = {}) {
const observer = new MutationObserver(observerFn)
const { callback, ...opts } = options
observer.observe(node, opts)
function observerFn(entries) {
for (const entry of entries) {
// Callback pattern
if (options.callback) options.callback({ entry, entries, observer })
// Event listener pattern
else {
node.dispatchEvent(
new CustomEvent('mutate', {
detail: { entry, entries, observer },
})
)
}
}
}
}
You can now use mutationObserver
with the callback
pattern or event listener pattern.
const node = document.querySelector('.some-element')
// Callback pattern
const obs = mutationObserver(node, {
callback ({ entry, entries }) {
// Do what you want with each entry
}
})
// Event listener pattern
node.addEventListener('mutate', event => {
const { entry } = event.detail
// Do what you want with each entry
})
Much easier!
Disconnecting the observer
Unlike ResizeObserver
who has two methods to stop observing elements, MutationObserver
only has one, the disconnect
method.
export function mutationObserver(node, options = {}) {
// ...
return {
disconnect() {
observer.disconnect()
}
}
}
But, MutationObserver
has a takeRecords
method that lets you get unprocessed records before you disconnect. Since we should takeRecords
before we disconnect
, let’s use it inside disconnect
.
To create a complete API, we can return this method as well.
export function mutationObserver(node, options = {}) {
// ...
return {
// ...
disconnect() {
const records = observer.takeRecords()
observer.disconnect()
if (records.length > 0) observerFn(records)
}
}
}
Now we can disconnect our mutation observer easily with disconnect
.
const node = document.querySelector('.some-element')
const obs = mutationObserver(/* ... */)
obs.disconnect()
MutationObserver’s observe options
In case you were wondering, MutationObserver
’s observe
method can take in 7 options. Each one of them determines what to observe, and they all default to false
.
subtree
: Monitors the entire subtree of nodeschildList
: Monitors for addition or removal children elements. Ifsubtree
istrue
, this monitors all descendant elements.attributes
: Monitors for a change of attributesattributeFilter
: Array of specific attributes to monitorattributeOldValue
: Whether to record the previous attribute value if it was changedcharacterData
: Monitors for change in character datacharacterDataOldValue
: Whether to record the previous character data value
Refactoring Intersection Observer
The API for IntersectionObserver
is similar to other observers. Again, you have to:
- Create a new observer: with the
new
keyword. This observer takes in an observer function to execute. - Do something with the observed changes: This is done via the observer function that is passed into the observer.
- Observe a specific element: By using the
observe
method. - (Optionally) unobserve the element: By using the
unobserve
ordisconnect
method (depending on which Observer you’re using).
But IntersectionObserver
requires you to pass the options in Step 1 (instead of Step 3). So here’s the code to use the IntersectionObserver
API.
// Step 1: Create a new observer and pass in relevant options
const options = {/*...*/}
const observer = new IntersectionObserver(observerFn, options)
// Step 2: Do something with the observed changes
function observerFn (entries) {
for (const entry of entries) {
// Do something with entry
}
}
// Step 3: Observe the element
const element = document.querySelector('#some-element')
observer.observe(element)
// Step 4 (optional): Disconnect the observer when we're done using it
observer.disconnect(element)
Since the code is similar, we can also copy-paste the code we wrote for mutationObserver
into intersectionObserver
. When doing so, we have to remember to pass the options into IntersectionObserver
and not the observe
method.
export function mutationObserver(node, options = {}) {
const { callback, ...opts } = options
const observer = new MutationObserver(observerFn, opts)
observer.observe(node)
function observerFn(entries) {
for (const entry of entries) {
// Callback pattern
if (options.callback) options.callback({ entry, entries, observer })
// Event listener pattern
else {
node.dispatchEvent(
new CustomEvent('intersect', {
detail: { entry, entries, observer },
})
)
}
}
}
}
Now we can use intersectionObserver
with the same easy-to-use API:
const node = document.querySelector('.some-element')
// Callback pattern
const obs = intersectionObserver(node, {
callback ({ entry, entries }) {
// Do what you want with each entry
}
})
// Event listener pattern
node.addEventListener('intersect', event => {
const { entry } = event.detail
// Do what you want with each entry
})
Disconnecting the Intersection Observer
IntersectionObserver
‘s methods are a union of both resizeObserver
and mutationObserver
. It has four methods:
observe
: observe an elementunobserve
: stops observing one elementdisconnect
: stops observing all elementstakeRecords
: gets unprocessed records
So, we can combine the methods we’ve written in resizeObserver
and mutationObserver
for this one:
export function intersectionObserver(node, options = {}) {
// ...
return {
unobserve(node) {
observer.unobserve(node)
},
disconnect() {
// Take records before disconnecting.
const records = observer.takeRecords()
observer.disconnect()
if (records.length > 0) observerFn(records)
},
takeRecords() {
return observer.takeRecords()
},
}
}
Now we can stop observing with the unobserve
or disconnect
method.
const node = document.querySelector('.some-element')
const obs = intersectionObserver(node, /*...*/)
// Disconnect the observer
obs.disconnect()
IntersectionObserver
options
In case you were wondering, IntersectionObserver
takes in three options:
root
: The element used to check if observed elements are visiblerootMargin
: Lets you specify an offset amount from the edges of the rootthreshold
: Determines when to log an observer entry
Here’s an article to help you understand IntersectionObserver
options.
Using this in practice via Splendid Labz
Splendid Labz has a utils
library that contains resizeObserver
, mutationObserver
and IntersectionObserver
.
You can use them if you don’t want to copy-paste the above snippets into every project.
import {
resizeObserver,
intersectionObserver,
mutationObserver
} from 'splendidlabz/utils/dom'
const mode = document.querySelector(‘some-element’)
const resizeObs = resizeObserver(node, /* ... */)
const intersectObs = intersectionObserver(node, /* ... */)
const mutateObs = mutationObserver(node, /* ... */)
Aside from the code we’ve written together above (and in the previous article), each observer method in Splendid Labz is capable of letting you observe and stop observing multiple elements at once (except mutationObserver
because it doesn’t have a unobserve
method)
const items = document.querySelectorAll('.elements')
const obs = resizeObserver(items, {
callback ({ entry, entries }) {
/* Do what you want here */
}
})
// Unobserves two items at once
const subset = [items[0], items[1]]
obs.unobserve(subset)
So it might be just a tad easier to use the functions I’ve already created for you. 😉
Shameless Plug: Splendid Labz contains a ton of useful utilities — for CSS, JavaScript, Astro, and Svelte — that I have created over the last few years.
I’ve parked them all in into Splendid Labz, so I no longer need to scour the internet for useful functions for most of my web projects. If you take a look, you might just enjoy what I’ve complied!
(I’m still making the docs at the time of writing so it can seem relatively empty. Check back every now and then!)
Learning to refactor stuff
If you love the way I explained how to refactor the observer APIs, you may find how I teach JavaScript interesting.
In my JavaScript course, you’ll learn to build 20 real life components. We’ll start off simple, add features, and refactor along the way.
Refactoring is such an important skill to learn — and in here, I make sure you got cement it into your brain.
That’s it! Hope you had fun reading this piece!
A Better API for the Intersection and Mutation Observers originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
from CSS-Tricks https://ift.tt/6L8Vb54
via IFTTT
No comments:
Post a Comment