Table of contents
- What is Intersection Observer?
- How Intersection Observer works
- Specifications and browser compatibility
- Applications for Intersection Observer
- Using Intersection Observer
- The callback function
- The options object
- Targeting an element to be observed
- Creating an Intersection Observer custom Hook in React
- Conclusion
Have you ever wondered how social media applications like TikTok, Instagram, or Twitter detect a particular video post that is in the viewport, autoplay it, and then stop it immediately after it goes out of view?
In this article, I will explain how Intersection Observer can be used to implement this autoplay and pause feature by creating a React custom Hook for use in a TikTok clone.
As a developer, you might want to implement an autoplay feature in a video player application, lazy load an image, or detect when an advertisement is in the viewport of a user’s browser. With Intersection Observer you can do all these.
What is Intersection Observer?
Intersection Observer is a JavaScript browser API that asynchronously monitors the position of DOM elements with respect to the client’s viewport or a root element.
How Intersection Observer works
Basically, the Intersection Observer API triggers a callback function in specific situations.
These situations include when the position of the selected element comes into the client’s viewport, when a selected element intersects a parent or root element, or when the observer is initially declared.
Specifications and browser compatibility
At the time of writing this article, the specifications are still a working draft. However, updates can be found here.
As for the browser compatibility, here is the current report:
Applications for Intersection Observer
Intersection Observer can be used for a wide variety of various applications outside of the scope of this article.
They include optional rendering of DOM elements, lazy loading, loading content on-demand with infinite scrolling, rendering advertisements and animations, and creating carousels.
The example I am using in this article (creating a custom autoplay Hook for a TikTok clone) can help you become familiar with the Intersection Observer API in order to start exploring the other options it can offer your apps.
Using Intersection Observer
Firstly, we want to find out if our browser supports the Intersection Observer API.
We can write a condition to check, like so:
if ('IntersectionObserver' in window) {
console.log("IntersectionObserver is supported!");
} else {
console.log("IntersectionObserver is not supported!");
}
The ItersectionObserver
object is usually structured like this:
let options= {
root: null,
rootMargin: '0px',
threshold: 0.5
};
const callback = (entries){ // entries = array of targeted elements
entries.forEach(entry=>{
// what happens each entry
})
}
let observerObj = new IntersectionObserver(callback, options);
observerObj.observe();
Here, the IntersectionObserver
object accepts two arguments. The callback
function, which is triggered after Intersection Observer is executed, and an optional options
object. This is an object with certain properties that determine when, and how, the Intersection Observer works.
The callback
function
When the callback
the function is executed, a list of targeted elements is checked by the Intersection Observer. These elements all have specific properties.
Examples of those properties are:
boundingClientRect
:intersectionRatio
:intersectionRect
isIntersecting
rootBounds
target
time
These properties are used to check the current element’s behavior relative to their root element.
In this article, we will be using isIntersecting
to check if the current entry is intersecting with the root. This will be the indicator that our video is in the viewport, and therefore ready to begin playing.
The options
object
The options
object contains the following properties:
The root
is the browser’s viewport by default, or if set as null
. If an element is specified as the root, it has to be a parent to the targeted element. The root is what the targeted element needs to intersect with before the callback
function is triggered
The rootMargin
sets the margin around the root element before detecting the intersection. By default, it is 0 (which triggers the action exactly when the root
property enters the viewport), but it can be valued in the same manner as a CSS margin in case you’d like the callback
function to occur at a different moment.
The threshold
represents what percentage of the targeted element should intersect root
before the callback
function is executed. It can be either a number or an array of numbers; the accepted values range from 0 to 1.
If it is 0, it means the first pixel of the target element needs to intersect with the root
element before the callback
function is executed. if it is 0.5, 50 per cent of the target element needs to intersect with the root
, and so on.
Targeting an element to be observed
To target an element with JavaScript, we have to use the querySelector
function, which will search the DOM for a given id
or class
.
In React, we can use the useRef
Hook to target an element. Then, we pass targetElement
as a parameter in the observe function, like so:
/*
In JavaScript we can use querySelector to select a DOM element like this...
*/
let targetElement = document.querySelector('#item')
observerObj.observe(targetElement)
//In React we can use the useRef hook like this...
let targetRef = useRef(null); //Set a component to be ref of targetRef
let targetElement = targetRef.current
observerObj.observe(targetElement)
In the TikTok clone, we will be using the useRef
Hook to target each video component in order to track when it comes into the viewport.
Creating an Intersection Observer custom Hook in React
To create a reusable Intersection Observer Hook, we will create a new function called useElementOnScreen
and implement Intersection Observer using options
and targetRef
we passed in as props:
import { useEffect, useMemo, useState } from 'react'
const useElementOnScreen = (options, targetRef) => {
const [isVisibile, setIsVisible] = useState()
const callbackFunction = entries => {
const [entry] = entries //const entry = entries[0]
setIsVisible(entry.isIntersecting)
}
const optionsMemo = useMemo(() => {
return options
}, [options])
useEffect(() => {
const observer = new IntersectionObserver(callbackFunction, optionsMemo)
const currentTarget = targetRef.current
if (currentTarget) observer.observe(currentTarget)
return () => {
if(currentTarget) observer.unobserve(currentTarget)
}
}, [targetRef, optionsMemo])
return isVisibile
}
export default useElementOnScreen
Having understood what the options
and targetRef
are, we need to pass them as props to the useElementOnScreen
Hook, as we will need them as parameters for a new Intersection Observer function.
Then, we set a default state for the element’s visibility
as null
.
Inside the callback
function, we are setting the isVisible
state to the value returned if the targeted Element isIntersecting
(we are always expecting true
or false
).
After observing the target element, we return the isVisible
state. The returned value of isVisible
is what we will use to decide when a video should play or stop.
If the isVisible
state of a video component is true
we play the video, else if it is false
we stop the video.
Using the Intersection Observer custom Hook in a TikTok clone
Setting up the application
For the sake of brevity, I have created a starter project that contains the entire source code of the TikTok clone where we will implement the Intersection Observer hook we just created above. It is available on my GitHub repository.
To start the application running, open your terminal to a new work folder and run the following commands:
git clone https://github.com/wolz-CODElife/Tiktok-clone.git
cd Tiktok-clone
npm install
In the folder downloaded, the following files and directories should be present:
The files and folders we are working with are inside the src
.As shown above, I have already included the Intersection Observer hook that we created in the previous section of this article in the hooks
directory. All that is remaining to do is to import the useElementOnScreen
hooks in the TikTok application.
Setting up autoplay
Now, let’s update the Video.js
component to play and stop a video depending on its visibility status.
Inside the Video.js
file, put the following code:
import React, { useEffect, useRef, useState } from "react";
import "./Video.css";
import VideoFooter from "./VideoFooter";
import VideoSidebar from "./VideoSidebar";
import useElementOnScreen from './hooks/useElementOnScreen'
import VideoPlayButton from "./VideoPlayButton";
const Video = ({ url, channel, description, song, likes, messages, shares }) => {
const [playing, setPlaying] = useState(false);
const videoRef = useRef(null);
const options = {
root: null,
rootMargin: '0px',
threshold: 0.3
}
const isVisibile = useElementOnScreen(options, videoRef)
const onVideoClick = () => {
if (playing) {
videoRef.current.pause();
setPlaying(!playing);
} else {
videoRef.current.play();
setPlaying(!playing);
}
};
useEffect(() => {
if (isVisibile) {
if (!playing) {
videoRef.current.play();
setPlaying(true)
}
}
else {
if (playing) {
videoRef.current.pause();
setPlaying(false)
}
}
}, [isVisibile])
return (
<div className="video">
<video className="video_player" loop preload="true" ref={videoRef} onClick={onVideoClick} src={url}></video>
<VideoFooter channel={channel} description={description} song={song} />
<VideoSidebar likes={likes} messages={messages} shares={shares} />
{!playing && <VideoPlayButton onVideoClick={onVideoClick} />}
</div>
);
};
export default Video;
Here, we imported the custom Hook (useElementOnScreen
), then used the value returned (which could be true
or false
) as the isVisible
value.
Note that we set the following options for the Intersection Observer: root
is null
, which means we are using the window as a parent element. rootMargin
is 0px
, and threshold
is 0.3
which means once 30 per cent of the target element is in the viewport, the callback function is triggered.
Next, we use UseEffect
to change the playing
state of the video if the isVisible
value changes, like so:
if (isVisibile) {
if (!playing) {
videoRef.current.play();
setPlaying(true)
}
}
else {
if (playing) {
videoRef.current.pause();
setPlaying(false)
}
}
This code means that, if the video is visible, the playing
state is set to true
. If it is not yet playing, and if the video is not visible, the playing
state is set to false
.
With this done, we can run the application with the following:
npm start
If everything goes well, we should have something like this:
If you wish to change the videos or even use a live database, edit the video
state in App.js
.
Currently, we have the following array of objects:
[
{
url: 'https://res.cloudinary.com/codelife/video/upload/v1633232723/tiktok-clone/tiktok2_qxafx3.mp4',
channel: 'DanceCrew',
description: 'Video by Lara Jameson from Pexels',
song: 'Bounce - Ruger',
likes: 250,
messages: 120,
shares: 40
},
{
url: 'https://res.cloudinary.com/codelife/video/upload/v1633232725/tiktok-clone/tiktok1_np37xq.mp4',
channel: 'Happyfeet',
description: '#happyfeetlegwork videos on TikTok',
song: 'Kolo sound - Nathan',
likes: 250,
messages: 120,
shares: 40
},
{
url: 'https://res.cloudinary.com/codelife/video/upload/v1633232726/tiktok-clone/tiktok3_scmwvk.mp4',
channel: 'thiskpee',
description: 'The real big thug boys💛🦋 The real big thug boys💛🦋 ',
song: 'original sound - KALEI KING 🦋',
likes: 250,
messages: 120,
shares: 40
},
]
Conclusion
Having created the application successfully, we should have learned how Intersection Observer works and how you can use it to implement an autoplay feature similar to that in TikTok or Instagram.
With this knowledge, you can try implementing lazy loading images, carousels or even an infinitely scrolling blog feeds page!
You can check the live demo of my TikTok clone here.I advise viewing it on a desktop browser for the best experience.
If you have any questions or remarks, please feel free to let me know in the comments.