23
loading...
This website collects cookies to deliver better user experience
transform
property for that. But this is out of scope of this article.offsetTop
or offsetLeft
. Unfortunately, there is one (serious) detail to keep in mind - it returns the position relative to the parent element and not the absolute position relative to the page. Even there is a solution using offset.js script, it still forces reflow.left
, top
, right
, bottom
, x
, y
, width
, and height
values of selected element. It's relatively fast when you have a small number of elements. But it's getting to be slower and forcing a reflow when the number of elements starts to rise dramatically, or when calling multiple time.IntersectionObserver
is primarily used to calculate the visibility of the element in the viewport.The Intersection Observer API provides a way to asynchronously observe changes in the intersection of a target element with an ancestor element or with a top-level document's viewport.
IntersectionObserver
API has a boundingClientRect
property that calculates the element dimension independently on its visibility.boundingClientRect
is the IntersectionObserver
API interface that returns a read-only value of the rectangle describing the smallest rectangle that contains the entire target element. It's like the getBoundingClientRect()
but without forcing a reflow. You'll get left
, top
, right
, bottom
, x
, y
, width
, and height
.IntersectionObserver
constructor via entry.boundingClientRect
.// new `IntersectionObserver` constructor
const observer = new IntersectionObserver((entries) => {
// Loop through all `entries` returned by the observer
for (const entry of entries) {
// The `entry.boundingClientRect` is where all the dimensions are stored
const bounds = entry.boundingClientRect;
// Log the `bounds` for every element
console.log(bounds);
// Then do whatever with `bounds`
}
// Disconnect the observer to stop from running in the background
observer.disconnect();
});
// Select all the `.element` elements
const elements = document.querySelectorAll(".element");
// Loop through all elements
for (const element of elements) {
// Run the `observe` function of the `IntersectionObserver` on the element
observer.observe(element);
}
entry.boundingClientRect
is where the magic happens. This property stores all the element dimensions and positions.IntersectionObserver
constructor that takes a list of elements as an argument and applies its calculations. Note to mention - you can pass custom options to the observer, but we're going to keep defaults one, as we don't need to track visibility.const observer = new IntersectionObserver((entries) => {
});
IntersectionObserver
, we need to loop through all entries
that will be passed later in the loop. This is the place where you get elements bounds for further use{.bg-green .bg-opacity-20}. We'll use bounds
constant to store the entry.boundingClientRect
values so when you need to get x
or height
value of the element, just use bounds.x
or bounds.height
.for (const entry of entries) {
const bounds = entry.boundingClientRect;
// Use `bounds` like you need
// Example: `bounds.height` will return the element `height` value in px
}
observer.disconnect();
.elements
constant.const elements = document.querySelectorAll(".element");
observer.observe(element);
is called. Instead, it waits and then takes a bunch of elements and runs the calculations asynchronously.for (const element of document.querySelectorAll(".element")) {
observer.observe(element);
}
IntersectionObserver
is, I've made a quick comparison with the old getBoundingClientRect()
method.<div>
elements and give them a .element
class with basic stylings such as size and background color. There are no other elements that could affect performance.getBoundingClientRect()
vs IntersectionObserver
.const elements = document.querySelectorAll(".element");
// `getBoundingClientRect()`
for (const element of elements) {
const bounds = element.getBoundingClientRect();
}
// `IntersectionObserver`
const observer = new IntersectionObserver((entries) => {
for (const entry of entries) {
const bounds = entry.boundingClientRect;
}
observer.disconnect();
});
for (const element of elements) {
observer.observe(element);
}
getBoundingClientRect()
results without any further manipulation, everything runs pretty fast. Check the live demo to see how it performs in your browser.IntersectionObserver
in this live demo everything is fast, too. It seems there is no big difference until you check the Performance tab in Google Chrome tools. When running getBoundingClientRect()
, the browser is forced to do a reflow and it takes longer to evaluate the script.IntersectionObserver
makes no reflows, and the script runs as fast as possible. Take to count that the page has 5000 elements, so parsing and recalculating styles take more time in both cases.::after
pseudo-element.data-bounds
attribute on the element.const elements = document.querySelectorAll(".element");
// `getBoundingClientRect()`
for (const element of elements) {
const bounds = element.getBoundingClientRect();
}
// `IntersectionObserver`
const observer = new IntersectionObserver((entries) => {
for (const entry of entries) {
const bounds = entry.boundingClientRect;
}
observer.disconnect();
});
for (const element of elements) {
observer.observe(element);
}
IntersectionObserver
method looks like there's no difference, the getBoundingClientRect()
method got mad. It takes 1.14s to evaluate the script and makes a huge amount of reflows.IntersectionObserver
runs in asynchronous mode. It's true, so let's make the getBoundingClientRect()
asynchronous with this script:const promises = [];
async function loop() {
for (const element of elements) {
let bounds = await element.getBoundingClientRect();
promises.push(bounds);
}
Promise.all(promises).then((results) => {
for (const [i, element] of Object.entries(elements)) {
let result = results[Number(i)];
element.dataset.bounds = `x: ${result.x} y:${result.y} width: ${result.width} height: ${result.height}`;
}
});
}
loop();
IntersectionObserver
IntersectionObserver
can be used not just to check the element visibility, but also to calculate its dimensions and position. Compared to getBoundingClientRect()
it's faster and doesn't produce any reflows. Even when the getBoundingClientRect()
is used in asynchronous function, it's still slower.