When creating a web application, you may encounter situations where you need to track the current state of visibility. It happens that you need to play or pause the effect of an animation or video, reduce the intensity or track user behavior for analytics. At first glance, this function seems quite simple to implement, but it is not quite so. Tracking user activity is a rather complicated process.
There is a Page Visibility API that works fine in most cases, but does not handle all possible cases of browser tab inactivity. The Page Visibility API dispatches a visibilitychange event to let listeners know that the visibility state of the page has changed. It does not trigger an event in some cases if the browser window or corresponding tab is hidden from view. To handle some of these cases, we need to use a combination of focus and blur events in both document and window.
So, from this article you will learn how to create a simple React component that tracks Page Visibility State.
Codesandbox will be used here to create the React application (you can also use create-react-app). We will create a small application in which the HTML5 video element will be played only if the browser tab is in focus or active, otherwise it will be paused automatically. Video is used to facilitate testing of application features.
visibilityjs - Wrapper for the Page Visibility API
Crossbrowser & lightweight way to check if user is looking at the page or interacting with it. (wrapper around HTML5 visibility api)
We will use Codesandbox to bootstrap our React application (you can use create-react-app as well). We will create a small app that will have an HTML5 Video element that will play only when the browser tab is in focus or active otherwise it will be paused automatically. We are using a video because it will make testing our app's functionality easy.
Let's start by creating the simplest piece i.e. the video component. It will be a simple component that will receive a Boolean props named active and a String props named src that will hold the URL for the video. If the active props is true then we will play the video otherwise we will pause it.
We will create a simple React class component. We will render a simple video element with its source set to the URL passed using the src props and use React's new ref API to attach a ref on the video DOM node. We will set the video to autoplay assuming when we start the app the page will be active. One thing to note here is Safari now doesn't allow auto-playing media elements without user interaction. The componentDidUpdate lifecycle method is very handy in handling side effects when a component's props change. Therefore, here we will use this lifecycle method to play and pause the video based on the current value of this.props.active.
Browser vendor prefix differences are very annoying to deal with when using certain APIs and the Page Visibility API is certainly one of them. Therefore, we will create a simple utility function that will handle these differences and return us the compatible API based on the user's browser in a uniform manner. We will create and export this small function from pageVisibilityUtils.js under the src directory.
In this function, we will utilize simple if-else statement based control flow to return the browser-specific API. You can see we attached the ms prefix for internet explorer and webkit prefix for webkit browsers. We will store the correct API in hidden and visibilityChange string variables and return them from the function in the form of a simple object. Lastly, we will export the function.
Next, we move onto our main component. We will encapsulate all of our Page Visibility tracking logic in a reusable React class component by leveraging the Render Props pattern. We will create a class component called VisibilityManager. This component will handle the adding and removing of all the DOM based event listeners.
We will start by importing the utility function we created earlier and invoking it to get the correct browser specific APIs. Then, we will create the React component and initialize its state with a single field isVisible set to true. This Boolean state field will be responsible for reflecting our page visibility state. In the component's componentDidMount lifecycle method we will attach an event listener on the document for the visibilitychange event with the this.handleVisibilityChange method as its handler. We will also attach event listeners for the focus and blur events on the document as well as the window element. This time we will set this.forceVisibilityTrue and this.forceVisibilityFalse as the handlers for the focus and blur events respectively.
Now, we will then create the handleVisibilityChange method that will accept a single argument forcedFlag. This forceFlag argument will be used to determine whether the method is called because of the visibilitychange event or the focus or blur events. This is so because the forceVisibilityTrue and forceVisibilityFalse methods do nothing but call the handleVisibilityChange method with true and false value for the forcedFlag argument. Inside the handleVisibilityChange method, we first check whether the forcedFlag argument value is a Boolean (this is because if it is called from the visibilitychange event handler than the argument passed on will be a SyntheticEvent object). If it is a Boolean then we check if it's true or false. When it's true we called the setVisibility method with true otherwise we call the method with false as an argument. The setVisibility method leverages this.setState method to update isVisible field's value in the component's state. But, if the forcedFlag is not a Boolean, then we check the hidden attribute value on the document and call the setVisibility method accordingly. This wraps up our Page Visibility State tracking logic.
To make the component reusable in nature we use the Render Props pattern i.e. instead of rendering a component from the render method, we invoke this.props.children as a function with this.state.isVisible.
Lastly, we mount our React app to the DOM in our index.js file. We import our two React components VisibilityManager and Video and create a small functional React component App by composing them. We pass a function as the children of the VisibilityManager component that accepts is Visible as an argument and passes it to the Video component in its return statement. We also pass a video URL as src props to the Video component. This is how we consume the Render Props based VisiblityManager component. Finally, we use ReactDOM.render method to render our React app on the DOM node with id "root".
Modern browser APIs are getting really powerful and can be used to do amazing things. Most of these APIs are imperative in nature and can be tricky to work with sometimes in React's declarative paradigm. Using powerful patterns like Render Props to wrap these APIs into their own reusable React components can be very useful.
Published 24 Aug