Lazy Load Vimeo Video Iframe – Show on Scroll
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 Vimeo lazy load Iframe component that can be used on a React website.
I recently added a Vimeo video to a 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. It is about YouTube but could be refactored to work with most providers.
robertmarshall.dev/blog/on-click-lazy-load-video-iframe-in-react/
If you are wanting a Vanilla JS solution to loading on scroll, take a look at: Lazy Loading Videos on Scroll with Vanilla JS.
Vimeo Embed Code with No Alterations
If you copy the Vimeo embed code, this is what it looks like:
<iframe
title="Vimeo video player"
src="https://player.vimeo.com/video/588259728?h=09704145bb&dnt=1&app_id=122963&autoplay=1"
width="1280"
height="720"
frameborder="0"
allowfullscreen="allowfullscreen"
></iframe>
This video is the On The Ark showreel. A great video production agency based in Leeds.
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.
We need to completely defer all Vimeo 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 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>
}
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 Vimeo video sized jump, 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. Lets introduce some 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 for Vimeo videos.
Hopefully this helps with a Vimeo iframe lazy load! You can find me on Twitter if you have any questions.
If you are interested in how the useIntersectionObserver
works then take a look at Git repo for it here. It uses useLayoutEffect
which works in a very similar way to a useEffect
.