Lazy Load YouTube Video Iframe – Show on Scroll

Last Updated:
Lazy Load YouTube Videos in React

This post contain affiliate links to Udemy courses, meaning when you click the links and make a purchase, I receive a small commission. I only recommend courses that I believe support the content, and it helps maintain the site.

This article will explain and provide a usable YouTube lazy load Iframe component that can be used on a React website.

I recently added a YouTube video to a clients Gatsby website and found that it had a massive hit on performance. It added a massive amount of weight to the page, before even doing anything!

This short example solves your problem!

Update

I recently wrote a piece on deferring the video until a user clicks. This is a more effective solution than waiting until scroll. Would recommend taking a look at this before implementing anything!

robertmarshall.dev/blog/on-click-lazy-load-video-iframe-in-react/

YouTubes Embed Code with No Alterations

If you copy the YouTube embed code, this is what it looks like:

<iframe
    title="YouTube video player"
    src="https://www.youtube.com/embed/P0oU3jHCUBM"
    width="1280"
    height="720"
    frameborder="0"
    allowfullscreen="allowfullscreen"
></iframe>

This music video by Fwar, a good friend of mine. If you like it you can find more of his music on Spotify.

No suggestion or indication of lazy loading seems to be factored in. This surprised me as Chrome has rolled out a lazy attribute that defers loading of offscreen Iframes and images until a user scrolls near them (more information on that here). I tried adding this and it increased performance slightly. However the YouTube scripts for the player were still being loaded too soon causing a performance hit.

We need to completely defer all YouTube related scripts loading until the user actually needs them.

Using the Intersection Observer API To Lazy Load Iframes

The Intersection Observer API is often used to lazy load images, but it can also be used to load all manner of other things. Why not use it to load YouTube iframes!

I initially considered building out the whole intersection observer functionality myself, but when digging a bit deeper I found that there were a number of polyfills and other magic needed to support edge cases. Not wanting to re-invent the wheel I decided to use the useIntersectionObserver() hook provided by Jared Lunde from his brilliant React Hook package.

How To Use useIntersectionObserver()

The thing I love most about hooks is that they are generally broken down into a single use, super easy to use function. This hook is no exception to that rule. Using the hook is as simple as importing it from the package, and plugging it in.

import { useState } from 'react'
import useIntersectionObserver from '@react-hook/intersection-observer'

const Component = () => {
    const [ref, setRef] = useState()
    const { isIntersecting } = useIntersectionObserver(ref)
    return <div ref={setRef}>Is intersecting? {isIntersecting}</div>
}

[support-block]

Adding the Intersection Observer Functionality to the Iframe in a Component

When I first plugged in the Intersection Observer hook into the iframe I noticed it hid and showed itself as I scrolled up and down the page. This is because the observer was working as it should do and only showed the component when it was on the screen. I changed the useState in the example to a useRef, and added a conditional to make sure it was shown and locked.

import { useRef } from 'react'
import useIntersectionObserver from '@react-hook/intersection-observer'

const LazyIframe = ({ url, title }) => {
    const containerRef = useRef()
    const lockRef = useRef(false)
    const { isIntersecting } = useIntersectionObserver(containerRef)
    if (isIntersecting && !lockRef.current) {
        lockRef.current = true
    }
    return (
        <div ref={containerRef}>
            {lockRef.current && (
                <iframe
                    title={title}
                    src={url}
                    frameborder="0"
                    allow="accelerometer; encrypted-media; gyroscope; picture-in-picture"
                    allowfullscreen="allowfullscreen"
                ></iframe>
            )}
        </div>
    )
}

export default LazyIframe

The container div wrapping the iframe is used as a reference point, and that is tracked to see if the iframe has scrolled onto the page yet.

What About Cumulative Layout Shift?

Now we have a component which defers all scripts and video until the user scrolls onto the page. Great!

But as the user scrolls down the page we have a jump in content. A large YouTube video sized jank, as the previously empty space is filled by an iframe.

To solve this there needs to be a placeholder that can hold the shape of the video until it has loaded fully. Time for some trusty CSS.

We know that the container div will always be on the page, so we can use this as the placeholder. Then we fill that space with video once it has loaded.

The Final Solution

import { useRef } from 'react'
import useIntersectionObserver from '@react-hook/intersection-observer'

const LazyIframe = ({ url, title }) => {
    const containerRef = useRef()
    const lockRef = useRef(false)
    const { isIntersecting } = useIntersectionObserver(containerRef)
    if (isIntersecting) {
        lockRef.current = true
    }
    return (
        <div
            style={{
                overflow: 'hidden',
                paddingTop: '56.25%',
                position: 'relative',
                width: '100%',
            }}
            ref={containerRef}
        >
            {lockRef.current && (
                <iframe
                    title={title}
                    style={{
                        bottom: 0,
                        height: '100%',
                        left: 0,
                        position: 'absolute',
                        right: 0,
                        top: 0,
                        width: '100%',
                    }}
                    src={url}
                    frameborder="0"
                    allow="accelerometer; encrypted-media; gyroscope; picture-in-picture"
                    allowfullscreen="allowfullscreen"
                ></iframe>
            )}
        </div>
    )
}

export default LazyIframe

And there you are! A fully deferred iframe component to house YouTube videos. This could also be used for any oembed item.

Hopefully this helps with a Youtube iframe lazy load! You can find me on Twitter if you have any questions.

Related Posts

Helpful Bits Straight Into Your Inbox

Subscribe to the newsletter for insights and helpful pieces on React, Gatsby, Next JS, Headless WordPress, and Jest testing.