Resizing DOM Elements Effortlessly: A Practical Guide to ResizeObserver

Mehul Kothari
9 min readOct 1, 2023

--

In the realm of modern web development, building responsive and adaptive user interfaces is paramount. A fundamental requirement for achieving this is the ability to efficiently monitor changes in the size of DOM (Document Object Model) elements.

ResizeObserver — a JavaScript API specifically designed to meet this challenge. In this comprehensive guide, we’ll embark on a deep dive into ResizeObserver, unraveling its inner workings, exploring its benefits, addressing its limitations, comparing it with alternative approaches, and providing a wealth of detailed examples.

Table of Contents

  1. Introduction to ResizeObserver
  2. Understanding How ResizeObserver Works
  3. The Underlying Mechanism of ResizeObserver
  4. Pros of Leveraging ResizeObserver
  5. Cons and Considerations
  6. Comparing ResizeObserver with Alternative Approaches
  7. In-Depth Examples of Using ResizeObserver
  8. Advanced Tips and Best Practices
  9. Using ResizeObserver in Frameworks like React and Angular
  10. Conclusion

1. Introduction to ResizeObserver

ResizeObserver is a relatively new JavaScript API introduced to address the shortcomings of traditional methods for tracking changes in DOM element size. This API is purpose-built for efficiently observing size changes in DOM elements, making it an indispensable tool for creating responsive and adaptive web applications.

2. Understanding How ResizeObserver Works

a. Initialization

The process of using ResizeObserver involves several key steps. Here’s a breakdown of how it works:

  1. Initialization: You create a ResizeObserver instance and attach a callback function to it.
const resizeObserver = new ResizeObserver(entries => {
entries.forEach(entry => {
console.log(`Element resized: ${entry.target}`);
console.log(`New size: ${entry.contentRect.width} x ${entry.contentRect.height}`);
});
});

b. Element Observation

  1. Element Observation: To monitor elements, you register them with the ResizeObserver instance using the observe method. You can observe one or more DOM elements simultaneously.
const targetElement = document.querySelector('.resize-me');
resizeObserver.observe(targetElement);

c. The Callback Function

  1. Geometry Changes: When the size of the observed element changes (e.g., due to window resizing, content changes, or CSS modifications), the underlying Geometry Observer detects the change.

d. Idle Callbacks

  1. Idle Callbacks: Instead of immediately invoking the callback function, ResizeObserver schedules it to run during the browser's idle time. This ensures that it doesn't interfere with critical tasks on the main thread.

e. Callback Execution

  1. Callback Execution: During a browser idle period, the scheduled callback function is executed by ResizeObserver. It receives a list of ResizeObserverEntry objects, each containing information about the observed elements' new and old sizes.

f. Custom Logic

  1. Custom Logic: In your callback function, you can implement custom logic to respond to the size changes of the observed elements. For example, you can update layouts, trigger animations, or load content as needed.

g. Observation Continues

  1. Observation Continues: ResizeObserver continues to observe the elements, detecting and scheduling callbacks for any future size changes.

3. The Underlying Mechanism of ResizeObserver

To fully comprehend how ResizeObserver operates behind the scenes, let's dissect its inner workings:

Geometry Observer

ResizeObserver is implemented as a "Geometry Observer" in the web platform. This means it has access to a low-level mechanism that allows it to observe changes in an element's geometry. This powerful capability enables ResizeObserver to monitor alterations in element dimensions efficiently.

Idle Callbacks

One of the key challenges in web development is ensuring that scripts and operations do not block or slow down the main thread. If they do, it can lead to poor user experiences, including unresponsive interfaces.

ResizeObserver mitigates this issue by utilizing idle callbacks. An idle callback is a mechanism that allows a script to schedule a function to be executed during a browser's idle time. This idle time occurs when the browser is not actively performing tasks that are critical for user interaction.

When ResizeObserver receives notifications from the Geometry Observer about changes in element sizes, it doesn't immediately invoke its callback function. Instead, it schedules the callback to be executed during the browser's next idle period.

This approach ensures that ResizeObserver's work doesn't interfere with critical tasks like rendering, user input handling, or other JavaScript operations. It prioritizes user experience and performance.

4. Pros of Leveraging ResizeObserver

a. Efficiency

ResizeObserver is highly optimized for performance. It circumvents continuous polling and only invokes the callback when actual size changes occur, rendering it significantly more efficient than traditional methods.

b. Granular Size Changes

ResizeObserver provides reports for both width and height changes, offering fine-grained control over how you respond to size alterations.

c. Multiple Element Observations

You can observe multiple elements with a single ResizeObserver instance, reducing the overhead of creating multiple event listeners.

d. Avoiding Workarounds

It obviates the need for workarounds such as polling or listening to window resize events when monitoring element size changes.

5. Cons and Considerations

a. Browser Support

As of my last update in September 2021, ResizeObserver was not supported in some older browsers. Be sure to check compatibility for your target audience and consider fallback mechanisms for unsupported browsers.

b. Complexity for Simple Cases

For straightforward scenarios like tracking the window's resizing, traditional resize event listeners might be more straightforward to use.

6. Comparing ResizeObserver with Alternative Approaches

a. ResizeObserver vs. Window Resize Events

  • ResizeObserver: Tailored for observing element size changes. Efficient and does not necessitate frequent polling.
  • Window Resize Events: More general-purpose, suitable for tracking the window's size changes but less suitable for monitoring individual element size changes.

b. ResizeObserver vs. MutationObserver

  • ResizeObserver: Monitors changes in element size. Ideal for responsive design and layout adjustments.
  • MutationObserver: Observes changes in the DOM tree, including attribute and content changes. Not specifically designed for monitoring size changes directly.

7. In-Depth Examples of Using ResizeObserver

Let’s dive into practical examples of how you can utilize ResizeObserver in your web development projects:

Example: Dynamic Resizing

  • 1. Dynamic Resizing: Whenever a user resizes their browser window, the description content now adjusts to fit the screen width, providing an optimal viewing experience.
  • 2. Initial Rendering and Updates: Upon the initial rendering of the page or when a user updates the description, the content is resized it fits the available space.
  • 3. Viewport Changes: Changes in the viewport size are now automatically detected, enabling the description content to adapt accordingly.
  • 4. Layout Adjustments: When a user collapses or expands elements, such as a sidebar affecting the width of the main content area, the description content smoothly adjusts to the newly available width.
import React, { useState, useEffect, useRef, useCallback } from "react";
import "./styles.css";
import styled from "styled-components";
import Typography from "@splunk/react-ui/Typography";
import Link from "@splunk/react-ui/Link";
import Paragraph from "@splunk/react-ui/Paragraph";
import { variables } from "@splunk/themes";
import Dropdown from "@splunk/react-ui/Dropdown";

// Styled components for styling
const StyledTypography = styled(Typography)`
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin: 0;
word-break: break-word;
`;

const StyledLabel = styled(Link)`
padding: 0 0.5rem;
min-width: 5.625rem;
max-height: 1.25rem;
`;

const StyledWholeContent = styled(Paragraph)`
padding: 0.5rem 1rem;
font-size: ${variables.fontSizeSmall};
min-width: 25rem;
max-width: 25rem;
word-break: break-word;
`;

const longDescription =
"Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s";

export default function App() {
// State variables
const [description] = useState(longDescription);
const [isSidebarCollapsed, setSidebarCollapsed] = useState(false);
const [showWholeDescription, setShowWholeDescription] = useState(false);
const [isShowMoreVisible, setIsShowMoreVisible] = useState(false);

// Refs
const showMoreLessLabelRef = useRef();
const contentRef = useRef();

// UseEffect to observe resizing
useEffect(() => {
let resizeObserver;
const contentElement = contentRef.current;

// Function to check for overflow
const checkOverflow = (observerElement) => {
resizeObserver = new ResizeObserver((entries) => {
// Checking whether the description is overflowing
const element = entries[0].target;
if (element.scrollWidth > element.clientWidth) {
setIsShowMoreVisible(true);
} else {
setIsShowMoreVisible(false);
}
});

// Observing the content element for resizing
if (observerElement) {
resizeObserver.observe(observerElement);
}
};

if (contentElement) {
checkOverflow(contentElement);
}

// Cleanup function to disconnect observer
return () => {
if (resizeObserver && contentElement) {
resizeObserver.unobserve(contentElement);
}
};
}, [isShowMoreVisible]);

// Function to toggle sidebar collapse
const toggleSideMenu = () => {
setSidebarCollapsed(!isSidebarCollapsed);
};

// Function to set reference for show more/less label
const handleSetShowMoreLessLabelRef = (el) => {
showMoreLessLabelRef.current = el;
};

// Callback function for closing dropdown
const closeDropdownToggle = useCallback((data) => {
if (data.reason !== "contentClick") {
setShowWholeDescription(false);
}
showMoreLessLabelRef.current?.focus();
}, []);

// Function to render content
const renderContent = (description) => {
const toggleShowMore = (
<StyledLabel elementRef={(el) => handleSetShowMoreLessLabelRef(el)}>
{!showWholeDescription ? "Show more" : "Show less"}
</StyledLabel>
);

return (
<>
<StyledTypography as="p" variant="body" ref={contentRef}>
{description}
</StyledTypography>
{isShowMoreVisible && (
<Dropdown
open={showWholeDescription}
onRequestOpen={() => setShowWholeDescription(true)}
onRequestClose={closeDropdownToggle}
toggle={toggleShowMore}
>
<StyledWholeContent>{description}</StyledWholeContent>
</Dropdown>
)}
</>
);
};

return (
<div>
<div className="main">
<div
className={
isSidebarCollapsed ? "collapse left-side" : "expand left-side"
}
>
Left Side section
<br /> <br />
<button onClick={toggleSideMenu}>
{isSidebarCollapsed ? "Expand" : "Collapse"} Side Menu
</button>
</div>

<div className="right-side">{renderContent(description)}</div>
</div>
</div>
);
}

CodeSandbox Demo: Dynamic Resizing with ResizeObserver

This example showcases using ResizeObserver to dynamically adjust content based on browser window resizing.

Example 2: Lazy Loading Images

ResizeObserver to trigger the lazy loading of images when they become visible in the viewport. Lazy loading is a technique used to defer the loading of non-essential content (in this case, images) until it’s actually needed. This can improve page load times and overall user experience.

HTML

htmlCopy code
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="styles.css">
<title>Lazy Loading Images</title>
</head>
<body>
<img class="lazy-image" data-src="image1.jpg" alt="Lazy-Loaded Image 1">
<img class="lazy-image" data-src="image2.jpg" alt="Lazy-Loaded Image 2">
<!-- Add more lazy images as needed -->
<script src="script.js"></script>
</body>
</html>
  • In the HTML, we have a basic web page structure with two img elements having the class lazy-image. These images have a data-src attribute, which contains the actual image source URL. The alt attribute provides alternative text for accessibility.

CSS (styles.css)

body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
}

.lazy-image {
width: 100%;
max-width: 400px;
margin: 20px;
opacity: 0; /* Initially hide the images */
transition: opacity 0.3s;
}

JavaScript (script.js)

const lazyImages = document.querySelectorAll('.lazy-image');
const lazyLoad = new ResizeObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src; // Load the actual image source
img.style.opacity = 1; // Make the image visible
lazyLoad.unobserve(img); // Stop observing once loaded
}
});
});
lazyImages.forEach(img => lazyLoad.observe(img));

How It Works

The key to this lazy loading technique is the ResizeObserver. When the page loads, the images have their src attributes initially set to placeholder URLs or are empty, meaning the actual images are not loaded. As the user scrolls down the page and the images come into the viewport, the ResizeObserver detects their visibility changes and triggers the callback function. In the callback, the image's src attribute is set to the actual image URL, and its opacity is changed to make it visible.

This approach saves bandwidth and speeds up page loading because only the images that are actually visible to the user are loaded, while the rest remain unloaded until needed.

8. Advanced Tips and Best Practices

To make the most of ResizeObserver, consider the following advanced tips and best practices:

  • Graceful Degradation: Provide a fallback mechanism, such as using traditional resize event listeners, if ResizeObserver is not supported in a particular browser.
  • Throttle or Debounce: Depending on your use case, consider using throttle or debounce techniques to limit the frequency of callback invocations for better performance.

9. Using ResizeObserver in Frameworks like React and Angular

ResizeObserver can also be integrated into popular JavaScript frameworks like React and Angular. These frameworks offer wrappers and utilities for using ResizeObserver seamlessly in your components.

  • React: You can use libraries like react-resize-observer to incorporate ResizeObserver into your React applications.
  • Angular: Angular provides built-in support for ResizeObserver through the @angular/cdk/resize-observer module.

10. Conclusion

ResizeObserver emerges as a powerful tool for efficiently monitoring DOM element size changes. It is particularly invaluable for creating responsive web designs, optimizing content loading, and addressing scenarios demanding dynamic layout adjustments. While browser support may vary, it is a promising addition to modern web development. Keep a vigilant eye on updates to ensure compatibility with your target audience’s browsers, as support for ResizeObserver continues to expand.

As you incorporate ResizeObserver into your web projects, you’ll find it to be a versatile and essential component for crafting user-friendly and adaptive web experiences. By harnessing its capabilities, you can stay on the cutting edge of web development and deliver compelling user interfaces.

Read more blogs here

--

--

Mehul Kothari

Mean stack and Full stack Developer. Open to work as freelancer for designing and developing websites.