We all try to make our apps and pages to be responsive: this is one of the main web-development standards for years. We strive to support all possible screen sizes while maintaining a friendly user interface. Everyone nowadays used to respond to viewport changes:
- either use Media queries in CSS
@media (mediaQueryString) {...}
[MDN] or in JSwindow.matchMedia(mediaQueryString)
[MDN] - or listen to window resize via
window.addEventListener('resize', ()=>{...})
But what if we need to watch the element size independent of the viewport? Say we have a web-component or autonomous block and want to update it whenever its width or height changes for any reason, something like Element.onResize(doSomething)
.
Today we are going to learn how to use the Resize Observer API by example.
API
The ResizeObserver interface reports changes to the dimensions of an Element's content or border box, or the bounding box of an SVGElement.
Observation will respond to every change of Element's size and fires if Element is rendered and itโs size is not 0,0
as well as when:
- Element is inserted/removed from DOM,
- Element display gets set to
none
.
Observation will do not fire for:
- non-replaced inline Elements (
span
,strong
,i
,b
,em
, etc), - changes to Element by CSS transforms.
The API provides us two instruments to work with: ResizeObserver
and ResizeObserverEntry
. Let's talk about them in specific.
ResizeObserver
ResizeObserver is used to observe changes to Element's size. All we need is to create our own instance and pass a callback function that will be fired every time, when the size changes:
(Here and below I will use TypeScript to show the exact types)
const myObserver = new ResizeObserver(
(entries: ResizeObserverEntry[], observer: ResizeObserver) => {
for (let entry of entries) {
// Do something with an entry (see in next section)
}
});
And then start to observe the desired element:
const myElement = document.getElementById('my-element');
myObserver.observe(myElement);
To stop watching the element we call unobserve()
method:
myObserver.unobserve(myElement);
To end observing all elements that were observed before by this instance:
myObserver.disconnect();
Options (optional)
ResizeObserver can observe different kinds of CSS sizes:
- content-box (default value): size of element's content area,
- border-box: size of element's box border area (content + padding + border),
-
device-pixel-content-box: size of element's content area in device pixels. Easier to understand it as
window.devicePixelRatio * contentSize
. Note that due to browser-specific subpixel calculations it's only approximately.
What size to watch can be passed as an option when starting to observe:
interface ResizeObserverOptions {
box?: 'content-box' | 'border-box' | 'device-pixel-content-box' | undefined;
}
const myOptions: ResizeObserverOptions = {
box: 'border-box'
};
myObserver.observe(myElement, myOptions);
However it will not affect the value returned by observer's callback, so use it only if you have a reason to.
ResizeObserverEntry
ResizeObserverEntry contains information about the element whose size has changed:
- target: the Element itself,
- contentBoxSize: size of content area,
- borderBoxSize: size of box border area,
- devicePixelContentBoxSize: size of content area in device pixels,
-
contentRect: the Element's DOMRect, same as if we call
Element.getBoundingClientRect()
directly.
Note that contentRect was added only due to current compatibility issues, it may be deprecated in next versions of ResizeObserver API. Consider not to use it in production.
Size
Every ...BoxSize
property of an Entry represents an array with ResizeObserverSize
object with 2 readonly sizes:
interface ResizeObserverSize {
readonly inlineSize: number;
readonly blockSize: number;
}
Think about it as width/height
element properties, so inlineSize
becomes width
and blockSize
becomes height
.
How to use
Usage is pretty easy, say we have a box of strawberries and getting them bigger make us really happy (and vice versa):
<h1>Mood: <span id="mood">๐</span></h1>
<div id="box">
๐๐๐
๐๐๐
๐๐๐
</div>
<form>
<label>
Love amount โค๏ธ: <input id="grower" type="range" value="16" min="8" max="32" step="1">
</label>
</form>
Let's write some logic to grow our strawberries:
const mood = document.getElementById('mood')
const box = document.getElementById('box')
const grower = document.getElementById('grower')
grower.addEventListener('input', () => {
box.style.fontSize = grower.value + 'px'; // Give them some love to grow!
})
Now we can use ResizeObserver to change our mood depending on the box size:
const resizeObserver = new ResizeObserver((entries) => {
for (let entry of entries) {
const { inlineSize: width } = entry.contentBoxSize[0];
mood.textContent = width > 90 ? "๐" : width < 50 ? "๐ข" : "๐";
}
});
resizeObserver.observe(box);
You can check how it works all together:
Another great example of the ResizeObserver API is scrolling down the chat window when a new message is added. An example can be seen here.
Browser support
Despite the fact that ResizeObserver API is still in Editorโs Draft(still in progress), according to Can I use its global 94.13%
support is pretty impressive. There is also a nice and powerful polyfill that allows you to use it in older browsers (even IE 9-10 ๐).
Hope you enjoyed this guide, stay tuned for more.
Top comments (4)
Very similar structure to Intersection Observer API. Great article, Makar! Cheers!
Thanks Hector, I like it a lot, it really quite easy to use and very useful as well
In a true W3C Fashion, a confusing API that only work 30% of the time
IMO they should just scrap HTML & CSS altogether and start over with a proper rendering API and Markup language
I've spent days trying to make something working in a WYSIWYG component and make it react to zoom & resize changes with no success
a very stupid thing that takes me 5 minutes to accomplish in Flutter or Kotlin Jetpack or with any other Sane UI Rendering APIs
Hello, one question please. What it means the Zero in:
const { inlineSize: width } = entry.contentBoxSize[0]
And how it is related with the others elements in the example. I mean zero is the first element in the array, but in which cases it would one or two, etc.
Thanks.