In our previous posts, we learned how to use the Intersection Observer API to lazy load images and background images. This powerful tool allows us to detect when an element enters the viewport and take action accordingly, such as loading resources on demand.
Now, let's talk about iframes. They're commonly used to embed external content, like videos, maps, or ads. But iframes can be slow and resource-intensive, especially if they contain large or complex content. That's where lazy loading comes in. By deferring the loading of iframes until they're actually needed, you can improve your page's performance and user experience.
In this post, we'll dive into how to use the Intersection Observer API to lazy load iframes.
Setting up an iframe for lazy loading
Setting up lazy loading for an iframe is similar to lazy loading images. Instead of setting the src
attribute directly, we'll use a custom data attribute like data-src
. This way, the iframe won't load immediately when the page loads.
<iframe
ref={iframeRef}
data-src="https://path/to/iframe"
/>
We've added a ref
to the iframe, which we'll use to observe it with the IntersectionObserver.
const iframeRef = React.useRef<HTMLIFrameElement>(null);
Setting up the IntersectionObserver
In the next step, we'll set up an IntersectionObserver to watch the iframe and detect when it comes into view. Once the iframe becomes visible, we'll grab the URL from the data-src
attribute and use it to set the src
attribute, which will start loading the iframe.
Here's a code snippet that uses the useEffect
hook to create an instance of the IntersectionObserver:
React.useEffect(() => {
const iframe = iframeRef.current;
const observer = new IntersectionObserver(([entry]) => {
if (entry.isIntersecting) {
// Load the iframe when it's visible
iframe.src = iframe.dataset.src;
observer.unobserve(iframe);
}
}, {
threshold: 0,
});
observer.observe(iframe);
return () => {
observer.disconnect();
};
}, []);
In this code snippet, we're creating an Intersection Observer that watches an element (in this case, an iframe) to see if it's visible in the viewport. If it is, we grab the URL from the data-src
attribute using iframe.dataset.src
, and use it to set the src
attribute of the iframe. Then, we stop observing the iframe since it's already been loaded.
Check out the demo below and see how it works by scrolling down to the bottom.
Video credit: Glacier express by @xat-ch
Improving user experience with loading indicators
To enhance the user experience, it's essential to display a loading indicator or placeholder while an iframe is loading. You can easily achieve this by adding a loading
class to the iframe, which will display a loading indicator as a background image. This creates a placeholder where the iframe will be displayed.
<iframe data-src="..." className="loading"></iframe>
To define the loading
class in CSS, we set its background
property to an image file that contains a loading indicator. We use center center no-repeat
to position the image at the center of the iframe and prevent it from repeating.
.loading {
background: url('/path/to/loading.svg') center center no-repeat;
}
After the iframe finishes loading, you can remove the loading
class and display its content instead of the placeholder. To achieve this, we simply add an event listener for the load
event on the iframe.
const handleLoadFrame = () => {
const iframe = iframeRef.current;
if (iframe) {
iframe.classList.remove('loading');
}
};
// Render
<iframe onLoad={handleLoadFrame} />
If you're not interested in using a background image for the loading indicator, there's another option. We can use state variables to keep track of when the iframe is loading and when it's loaded.
To do this, we'll use an enum called Status
with three possible values: NotLoaded
, Loading
, and Loaded
. We'll also create a state variable called status
to keep track of the current status of the iframe.
enum Status {
NotLoaded,
Loading,
Loaded,
}
const [status, setStatus] = React.useState(Status.NotLoaded);
When the iframe is first displayed, it's set to NotLoaded
. As soon as it begins to load, we change the status to Loading
.
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
setStatus(Status.Loading);
}
});
}, {
threshold: 0,
});
Once the iframe finishes loading, we update the status to Loaded
. To achieve this, we can add an event listener for the load
event on the iframe. When this event triggers, we set the status to Loaded
.
const handleLoadFrame = () => {
setStatus(Status.Loaded);
};
// Render
<iframe onLoad={handleLoadFrame} />
Using this setup, we can choose to display different components based on the current status of the iframe. For instance, while the iframe is loading, we could show a spinner or progress bar. Once it's completely loaded, we could then display the content of the iframe.
<div className="container">
{status === Status.Loading && (
<div className="loading">Loading ...</div>
)}
<iframe ... />
</div>
That's it! We've successfully created an iframe that only loads when it appears on the screen. This can significantly improve the performance of pages with multiple iframes.
Take a look at the demo below to see it in action:
Conclusion
Using the Intersection Observer API to lazy load iframes is an easy and effective way to boost your page's performance. By delaying the loading of iframes until they're actually visible in the viewport, you can speed up your page's initial load time and make for a smoother user experience. Just make sure you handle the loading states properly so your users know what's happening.
If you want more helpful content like this, feel free to follow me:
Top comments (0)