[Deep Dive into the Observer API] 3. ResizeObserver
[Deep Dive into the Observer API] 3. ResizeObserver
ResizeObserver is an observer that detects changes in the size of an element. When an element's width or height changes, it can detect this and perform the desired action.
TYPESCRIPT
const ro = new ResizeObserver(callback); ro.observe(tag, options);
Unlike IntersectionObserver from the previous chapter, options are not assigned in the API. Instead, options can be specified together when registering an element via the observe method.
You can pass options when registering an element to fine-tune the detailed settings. It has the ResizeObserverOptions type.
Specifies the type of the element.
- content-box - The size of the content area as defined by CSS
- border-box - The size of the entire box area as defined by CSS (including margin, padding, and border)
- device-pixel-content-box - The size of the content area defined in device pixel units
ResizeObserver's callback method is an event method that runs whenever an element's size changes.
It is declared with the ResizeObserverCallback type, which can be expressed in code as follows.
TYPESCRIPT
const ro = new ResizeObserver((entries, observer) => {});
entries has the ResizeObserverEntry[] type. It returns the event information of the elements registered in ResizeObserverEntry as an array.
By registering multiple DOM elements in ResizeObserverEntry, you can apply the observer to multiple objects.
🖼️ Basic box
Each item below is explained based on the box shown above.
🖼️ border-box
There are several elements that make up the size of content in CSS.
Each is composed of border and padding in the layout, and the size is determined based on the internal content.
borderBoxSize provides the size of the box encompassing all of these elements. It is an array, and each item has blockSize and inlineSize.
- blockSize - Height (height)
- inlineSize - Width (width)
🖼️ content-box
Returns the size information of the pure content only, excluding the border and padding that make up the box layout in CSS.
Like borderBoxSize, it is an array, and each item's information is the same as well.
- blockSize - Height (height)
- inlineSize - Width (width)
Returns the size information of the content area in device pixel units.
Like borderBoxSize, it is an array, and each item's information is the same as well.
- blockSize - Height (height)
- inlineSize - Width (width)
🖼️ contentRect
An object that returns various size information about elements, some of which overlaps with information provided by other objects.
This object is a legacy property, supported for backward compatibility, and there is a possibility it may be deprecated at some point. (reference)
- width - Width of the pure content area (same as contentBoxSize)
- height - Height of the pure content area (same as contentBoxSize)
- x - The x coordinate of the content, based on the box
- y - The y coordinate of the content, based on the box
- left - The position of the left edge of the content. Usually the same as x
- top - The position of the top edge of the content. Usually the same as y
- right - The position of the right edge of the content.
- bottom - The position of the bottom edge of the content.
Returns the DOM object that triggered the ResizeObserver event.
Returns the ResizeObserver object. Through this, ResizeObserver can also be handled in a chained manner within the callback method.
Let's use ResizeObserver conveniently through a custom hook.
TYPESCRIPT
export function useResizeObserver(): void { // }
Define the useResizeObserver method as above.
To use useResizeObserver, the following three elements are needed.
- The target DOM
- A callback method
- Options
Let's define these three elements as parameters. For the first one, an HTMLElement is normally required, but this hook will be built to accept not only the HTMLElement type but also a string, so that a tag can be targeted using a selector like #id or .class.
TYPESCRIPT
import { useEffect } from "react"; export type UseResizeObserverCallback = (entry: ResizeObserverEntry) => void; /** * Hook method for applying ResizeObserver * * @param {Element | string | null} ref: Element * @param {UseResizeObserverCallback} callback: Callback method * @param {ResizeObserverOptions} options: Options */ export function useResizeObserver(ref: Element | string | null, callback: UseResizeObserverCallback, options?: ResizeObserverOptions): void { // }
The parameters are as above. Next, declare ResizeObserver and assign the callback method.
TYPESCRIPT
import { useEffect } from "react"; export type UseResizeObserverCallback = (entry: ResizeObserverEntry) => void; /** * Hook method for applying ResizeObserver * * @param {Element | string | null} ref: Element * @param {UseResizeObserverCallback} callback: Callback method * @param {ResizeObserverOptions} options: Options */ export function useResizeObserver(ref: Element | string | null, callback: UseResizeObserverCallback, options?: ResizeObserverOptions): void { useEffect(() => { const ro = new ResizeObserver((entries) => { entries.forEach(callback); }); }, [ ref, callback, options ]); }
Initialize ResizeObserver and assign it to the ro variable.
Next, assign the DOM specified by ref.
ref branches according to three possible states, as follows.
- If it is null -> skip it.
- If it is a string -> select the tag with the document.querySelector method, then register that tag.
- If it is an HTMLElement -> since it is already an Element, register it immediately.
This was implemented by checking whether ref is a string via the typeof ref === "string" expression.
You can register a tag through the ro.observe() method.
TYPESCRIPT
import { useEffect } from "react"; export type UseResizeObserverCallback = (entry: ResizeObserverEntry) => void; /** * Hook method for applying ResizeObserver * * @param {Element | string | null} ref: Element * @param {UseResizeObserverCallback} callback: Callback method * @param {ResizeObserverOptions} options: Options */ export function useResizeObserver(ref: Element | string | null, callback: UseResizeObserverCallback, options?: ResizeObserverOptions): void { useEffect(() => { const ro = new ResizeObserver((entries) => { entries.forEach(callback); }); // If the DOM is valid if (ref) { // If ref is a string if (typeof ref === 'string') { const tag = document.querySelector(ref); // If the tag is valid if (tag) { ro.observe(tag, options); } } // If it is a DOM element else { ro.observe(ref, options); } } }, [ ref, callback, options ]); }
Finally, add cleanup code so that ResizeObserver registrations don't stack up every time the component re-renders.
ResizeObserver can be removed via the ro.disconnect() method.
TYPESCRIPT
import { useEffect } from "react"; export type UseResizeObserverCallback = (entry: ResizeObserverEntry) => void; /** * Hook method for applying ResizeObserver * * @param {Element | string | null} ref: Element * @param {UseResizeObserverCallback} callback: Callback method * @param {ResizeObserverOptions} options: Options */ export function useResizeObserver(ref: Element | string | null, callback: UseResizeObserverCallback, options?: ResizeObserverOptions): void { useEffect(() => { const ro = new ResizeObserver((entries) => { entries.forEach(callback); }); // If the DOM is valid if (ref) { // If ref is a string if (typeof ref === 'string') { const tag = document.querySelector(ref); // If the tag is valid if (tag) { ro.observe(tag, options); } } // If it is a DOM element else { ro.observe(ref, options); } } return () => { ro.disconnect(); }; }, [ ref, callback, options ]); }
The full code is as above.
A simple example has been implemented in CodeSandbox.
By making use of ResizeObserver like this, you can easily handle viewport-related aspects of the DOM.
This API can replace the resize event, allowing for a more efficient event implementation.
In the next chapter, let's cover MutationObserver.