Make Websites Efficient With Page Visibility

Make Websites Efficient With Page Visibility
The video player will automatically play and pause depending on the visibility of the page.


Learn a few techniques to create a responsive website that efficiently handles being out of sight with the power-saving Page Visibility API.

We've had tabbed browsing for about a decade. Most users are familiar with the idea of having more than one website open at a time, but it's hard to deduce when your site has their attention. Traditional 'hacks' are to attach an onblur or onfocus listener, but these are far from flawless as they often give false positives.

Enter the Page Visibility API. Although this JS API is still in its infancy, it’s 'a means for site developers to programmatically determine the current visibility state of the page in order to develop power and CPU-efficient web applications'. It's currently a W3C Candidate Recommendation which has been championed by Microsoft and Google, so it’s not surprising to find it's supported in Chrome 13 and will be supported in IE10, as well as Firefox 10 and Opera 12.10. In this tutorial we'll pause and play a video; prevent erroneous visits being added to page analytics; and stop Ajax requests until the user returns, saving greatly on server load.
Make Websites Efficient With Page Visibility
Although a tad dry, it’s well worth reading up on the W3C spec – it tells you most of what you need to know and can really help you out if you get stuck


STEP 1: Adding The Listener
It's incredibly easy to start using the Page Visibility API; if you’ve ever added a click handler this syntax will look very familiar. The visibilitychange event is triggered by several actions: when a user navigates to/from the tab your site is in, when the browser is minimized and when the OS is locked.

CODE:
document.addEventListener( 'visibilitychange', function() {
//do stuff
}, false);
/* jQuery way */
$(document).on( 'visibilitychange', function() {
//do stuff
});
Page not in view: evidence that our JavaScript is up and running by switching between tabs.
Page not in view: evidence that our JavaScript is up and running by switching between tabs.


STEP 2: Catering For All Browsers
Of course, this wouldn’t be web development if it were that easy. Currently, most of the browsers that support the Page Visibility API do so with their own vendor prefix (except Opera). To account for this we can quickly check if the prefixed version is undefined or not.

CODE:
var prefix;
if ( typeof document.hidden !== 'undefined' ) {
prefix = '';
} else if ( typeof document.webkitHidden !== 'undefined' ) {
prefix = 'webkit';
} else if ( typeof document.mozHidden !== 'undefined' ) {
prefix = 'moz';
} else if ( typeof document.msHidden !== 'undefined' ) {
prefix = 'ms';
} else {
window.alert('Your browser doesn\'t support the Page Visibility API');
return;
}

In view! Now to deliver all that rich content we’ve been saving up…
In view! Now to deliver all that rich content we’ve been saving up…


STEP 3: Helping Ourselves
Right, we've now determined if the browser supports the API and which vendor preix to use. To use this data though, we need to check the Boolean value of document.hidden. We could check for each prefix like if (document.hidden || document.webkitHidden ||, etc), but that could quickly get tiring for our fingers. Instead, we’ve written a function to prevent RSI.

CODE:
function isHidden() {
if ( prefix === '' || typeof document.hidden !== 'undefined' ) {
return document.hidden;
} else if ( prefix === 'webkit' || typeof document.webkitHidden !== 'undefined' ) {
return document.webkitHidden;
} else if ( prefix === 'moz' || typeof document.mozHidden !== 'undefined' ) {
return document.mozHidden;
} else if ( prefix === 'ms' || typeof document.msHidden !== 'undefined' ) {
return document.msHidden;
} else {
return null;
}
}

STEP 4: The Universal Approach
With that in place, we can simply call that function to determine the page’s visibility. You’ll probably notice that we have explicitly checked if hidden is false as opposed to the shorthand !isHidden() because that would evaluate as true if isHidden() returned null.

CODE:
document.addEventListener( prefix + 'visibilitychange', function(event) {
if ( isHidden() ) {
} else if ( isHidden() === false) {
}
}, false);

STEP 5: Video HTML
Now we’re starting to get to grips with the Page Visibility API it’s time to do something with it. To kick-off, we’re going to be pausing and playing a video when the user visits a different tab. We’ll start by writing the HTML for the video and include the visibility.js script.

CODE:
<video width="550" height="360" controls autoplay>
<source src="my-video.webm" type="video/webm; codecs='vp8.0, vorbis'">
<source src="my-video.mp4" type="video/mp4; codecs='avc1.4D401E, mp4a.40.2'">
<source src="my-video.ogv" type='video/ogg; codecs="theoravorbis"'>
<p>Your browser can't play this video :(</p>
</video>
<script src="visibility.js"></script>

STEP 6: Controlling The Video
HTML5 video allows us to control its playback with JavaScript through its own very simple API. We’re going to be using two of its methods: play() and pause(). To use these we need to lock on to the video’s node; this assumes it’s the only video element on the page by getting the first in the list.

CODE:
var video = document.getElementsByTagName('video')[0];
document.addEventListener( prefix + 'visibilitychange', function(event) {
if ( isHidden() ) {
video.pause();
} else if ( isHidden() === false ) {
video.play();
}
}, false);

STEP 7: Pause For A Moment
This has the desired effect! Marvel as you never miss a frame of video playback again. Unfortunately, our implementation will pause and start it even if the video has already been watched or was paused by the user. To get around this we are going to check if the video is paused at the point they navigate away from the page.

CODE:
if ( isHidden() ) {
paused = video.paused;
video.pause();
}

STEP 8: Play It Again…
In the previous step, we used a property called paused to check if the video was stopped; we’re now going to use some other properties specific to the video tag to make an educated guess as to whether the video should be played when the user navigates back to our site.

CODE:
if ( video.currentTime > 0 && !paused && !video.ended ) {
video.play();
}

STEP 9: Let’s Recap
Great! We should now have a video that only plays if it was playing, wasn’t paused, and hasn’t ended. This is a good example of progressive enhancement where – if the technology is available – we can add extra levels of functionality to make users’ lives a little easier.

STEP 10: Visibility State
As well as document.hidden the Page Visibility API also adds another attribute: document.visibilityState. This has four possible values: visible, hidden, unloaded, and prerender. Like hidden, it’s current vendor-prefixed so let’s first make a similar function to isHidden() that deals with this.

CODE:
function visibilityState() {
if ( prefix === '' || typeof document.hidden !==\'undefined' ) {
return document.visibilityState;
} else if ( prefix === 'webkit' || typeof document.webkitHidden !== 'undefined' ) {
return document.webkitVisibilityState;
} else if ( prefix === 'moz' || typeof document.mozHidden !== 'undefined' ) {
return document.mozVisibilityState;
} else if ( prefix === 'ms' || typeof document.msHidden !== 'undefined' ) {
return document.msVisibilityState;
} else {
return null;
}
}

STEP 11: Verifying Analytics
When looking at visibilityState the First two values make sense, but prerender is a bit weirder. Some browsers (such as Chrome) pre-render pages that they think the user will click on to speed up browsing. Because this counts as a page view it can skew analytics into thinking they’re getting more views than they really are. We can Fix this with the Page Visibility API.

CODE:
if ( visibilityState() !== 'prerender' ) {
//your analytics code
}

STEP 12: How Long?
You might also find it useful to work out how long a user has been away from your website – either for analytics or to modify an element of your page in some way. When the visibilitychange event is fired it gives us a number of properties – one of which is the timestamp for when the event occurred.

CODE:
if ( isHidden() ) {
timeAway = new Date(event.timeStamp);
}

STEP 13: We Missed You…
We've used JavaScript’s built-in Date object so that we can work with the UNIX timestamp. To calculate how long they were away we can simply subtract one date from the other. We’re also dividing it by 1,000 to convert milliseconds to seconds and rounding the result up to make it more readable.

CODE:
} else if ( isHidden() === false ) {
var delta = new Date(event.timeStamp) - timeAway;
window.alert('You were away for ' + Math.round(delta / 1000) + ' seconds');
}

STEP 14: Changing The Title
This only fires when they leave and come back, but what if you want to do something on your site once the user has been away for a certain amount of time? We can use setInterval() to check how long they’ve been gone. If the user is away for, say, ten minutes, we change the title to ‘Close me!’.

CODE:
setInterval(function() {
var delta = Date.now() - timeAway;
if ( delta > 600000 ) {
document.title = 'Close me!';
clearInterval( this );
}
}, 10000);
Amaze (or annoy) your users by telling them to close your tab by changing the page’s title after a set time.
Amaze (or annoy) your users by telling them to close your tab by changing the page’s title after a set time.


STEP 15: Reverting The Title
Once they come back, we want to change the title back to what it was; you can do this with a simple if statement. It’s worth noting that just because we can do things like this it doesn’t mean we necessarily should. Adding a setInterval when a page isn’t being viewed is extra load on the CPU, but on the other hand, it does offer developers some extra creativity.

CODE:
} else if ( isHidden() === false ) {
if (document.title !== 'Page Visibility API tutorial') {
document.title = 'Page Visibility API tutorial';
}
}
The tab preview shows it has still only made one request.
The tab preview shows it has still only made one request.


STEP 16: Turning To Ajax
Okay, so the last example was a bit gimmicky – it’s unlikely many web developers are going to start encouraging people to close their page after they’ve been gone for ten minutes! Perhaps a more useful application of the Page Visibility API is to stop Ajax requests when they’re not needed.

CODE:
var requests = [],
makeRequests;
function getSomething() {
}
function stopRequests() {
}
To visualise Ajax requests the counter increases for every request made.
To visualise Ajax requests the counter increases for every request made.


STEP 17: This Was A Triumph
The requests variable is going to store all requests (ideally you’d remove requests once they’ve been completed). We’re going to use the jQuery ajax method for its simplicity. Within the getSomething() function, add:

CODE:
var request = $.ajax({
url: ' HYPERLINK "http://lab.fetimo.com/pagevis/resource.json'" http://mysite.com/resource.json',
success: function(response) {
$('body').append(response.message + '<br>');
}
});
requests.push(request);
You probably have better things to do with requests than simply output "This was a Triumph", but whatever the message, the code is the same.
You probably have better things to do with requests than simply output "This was a Triumph", but whatever the message, the code is the same.


STEP 18: Cancel That
jQuery has a handy property called beforeSend on its ajax method which lets you – among other things – set custom headers and, ultimately, decide if the request should be made. We’ll use this to only send a request if the page is visible by returning false if the page is hidden; returning false stops the request from being made.

CODE:
request = $.ajax({
beforeSend: function() {
if ( isHidden() ) {
stopRequests();
return false;
}
},

STEP 19: Setting intervals
To simulate an Ajax-style application making many requests we'll set up a setInterval to make a request every second within getSomething(). We assign it to a variable so that we can clear it in the next step – otherwise, it would be making requests forever! We also don’t want multiple timers, so it has been wrapped in an if statement.

CODE:
if ( !makeRequests ) {
makeRequests = setInterval(function() {
getSomething();
}, 1000);
}

STEP 20: Abort!
Next, we'll write what stopRequests() is meant to do. We will loop through the array and use jQuery’s abort() function on each of the active connections. We’ll then clear the interval that emulates an application making requests for us and reset the variable to be undefined; if we didn’t do this it would remain as the ID of the setInterval.

CODE:
requests.forEach(function( request ) {
request.abort();
});
clearInterval( makeRequests );
makeRequests = undefined;

STEP 21: Initialise Requests
The last thing to do is initialize getSomething() to start making requests both when the page is visible and on initial page load. This will complete emulating a JavaScript app making requests so that we can see the effect of our dabbling with the Page Visibility API.

CODE:
} else if ( isHidden() === false ) {
getSomething();
}
}, false); //close event listener
getSomething();
Once we go back to the tab, it starts to make requests again.
Once we go back to the tab, it starts to make requests again.


STEP 22: Tidying up
Now that we know this works you can change the requests array from storing every request made to just active ones by removing them on request completion. If you know you only ever make one request at a time you can simply do request.pop(); however, here we’re going to compare each element in the array and see if it’s exactly the same as the variable in memory.

CODE:
complete: function() {
requests.forEach(function( req, i ) {
if ( req === request ) {
requests.splice( i, 1 );
}
});
}
Make Websites Efficient With Page Visibility Make Websites Efficient With Page Visibility Reviewed by Kamal Thakur on Saturday, September 19, 2020 Rating: 5

No comments:

Powered by Blogger.