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, 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 viewport of a user’s browser. With Intersection Observer you can do all these.
Intersection Observer is JavaScript browser API that asynchronously monitors the position of DOM elements with respect to the client’s viewport or a root element.
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.
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:
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 advertisement 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.
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 structure 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 determines when, and how, the Intersection Observer works.
callback
functionWhen the callback
function is executed, a list of targeted elements are 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.
options
objectThe 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 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 the targeted element should intersect the 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 percent of the target element needs to intersect with the root
, and so on.
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.
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.
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 is remaining to do is to import the useElementOnScreen
hooks in the TikTok application.
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 percent 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 went 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 }, ]
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.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.
Handle frontend data discrepancies with eventual consistency using WebSockets, Docker Compose, and practical code examples.
Efficient initializing is crucial to smooth-running websites. One way to optimize that process is through lazy initialization in Rust 1.80.
One Reply to "Build a custom TikTok autoplay React Hook with Intersection Observer"
It’s really helpful….Thank you so much