Lazy load an iframe
Tags: webdev, javascript, react, typescript
Published: 2024-04-04 01:32:17

In our previous posts, we learned how to use the Intersection Observer API to lazy load [images](https://phuoc.ng/collection/intersection-observer-with-react/lazy-load-an-image/) and [background images](https://phuoc.ng/collection/intersection-observer-with-react/lazy-load-a-background-image/). 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.

```tsx
<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.

```tsx
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:

```tsx
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](https://pixabay.com/videos/glacier-express-rhb-lake-45569/) by [@xat-ch](https://pixabay.com/users/xat-ch-12531001/)

{% codesandbox kp6yqd %}

## 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.

```tsx
<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.

```css
.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.

```tsx
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.

```tsx
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`.

```tsx
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`.

```tsx
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.

```tsx
<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:

{% codesandbox 5dz3zl %}

## 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:

-   [DEV](https://dev.to/phuocng)
-   [GitHub](https://github.com/phuocng)
-   [Website](https://phuoc.ng)