function pageChanged() {
if (document.hidden) {
console.log('User is on some other tab/out of focus') / line #1
} else {
console.log('Hurray! User returned') / line #2
}
}
document.addEventListener("visibilitychange", pageChanged);
We're adding an event listener to the document; it fires whenever the page is changed. Sure, the pageChanged function gets an event object as well in the argument, but we can simply use the document.hidden property, which returns a Boolean value depending on the page's visibility at the time the code was called.
You'll add your pause game code at line #1 and your resume game code at line #2.
navigator.onLine API – the user's network status
The navigator.onLine API tells you if the user is online or not. Imagine building a multiplayer game and you want the game to automatically pause if the user loses their internet connection. This is the way to go here!
function state(e) {
if(navigator.onLine) {
console.log('Cool we\'re up');
} else {
console.log('Uh! we\'re down!');
}
}
window.addEventListener('offline', state);
window.addEventListener('online', state);
Here, we're attaching two event listeners to window global. We want to call the state function whenever the user goes offline or online.
The browser will call the state function every time the user goes offline or online. We can access it if the user is offline or online with navigator.onLine, which returns a Boolean value of true if there's an internet connection, and false if there's not.
Clipboard API - programmatically manipulating the clipboard
The Clipboard API finally allows developers to copy to a user's clipboard without those nasty Adobe Flash plugin hacks that were not cross-browser/cross-device-friendly. Here's how you'll copy a selection to a user's clipboard:
<script>
function copy2Clipboard(text) {
const textarea = document.createElement('textarea');
textarea.value = text;
document.body.appendChild(textarea);
textarea.focus();
textarea.setSelectionRange(0, text.length);
document.execCommand('copy');
document.body.removeChild(textarea);
}
</script>
<button onclick="copy2Clipboard('Something good!')">Click me!</button>
First of all, we need the user to actually click the button. Once the user clicks the button, we call a function that creates a textarea in the background using the document.createElement method. The script then sets the value of the textarea to the passed text (this is pretty good!) We then focus on that textarea and select all the contents inside it.
Once the contents are selected, we execute a copy with document.execCommand('copy'); this copies the current selection in the document to the clipboard. Since, right now, the value inside the textarea is selected, it gets copied to the clipboard. Finally, we remove the textarea from the document so that it doesn't disrupt the document layout.
You cannot trigger copy2Clipboard without user interaction. I mean, obviously you can, but document.execCommand('copy') will not work if the event does not come from the user (click, double-click, and so on). This is a security implementation so that a user's clipboard is not messed around with by every website that they visit.
The Canvas API - the web's drawing board
HTML5 finally brought in support for <canvas>, a standard way to draw graphics on the web! Canvas can be used pretty much for everything related to graphics you can think of; from digitally signing with a pen, to creating 3D games on the web (3D games require WebGL knowledge, interested? - visit http://bit.ly/webgl-101).
Let's look at the basics of the Canvas API with a simple example:
<canvas id="canvas" width="100" height="100"></canvas>
<script>
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
ctx.moveTo(0,0);
ctx.lineTo(100, 100);
ctx.stroke();
</script>
This renders the following:
How does it do this?
- Firstly, document.getElementById('canvas') gives us the reference to the canvas on the document.
- Then we get the context of the canvas. This is a way to say what I want to do with the canvas. You could put a 3D value there, of course! That is indeed the case when you're doing 3D rendering with WebGL and canvas.
- Once we have a reference to our context, we can do a bunch of things and add methods provided by the API out-of-the-box. Here we moved the cursor to the (0, 0) coordinates.
- Then we drew a line till (100,100) (which is basically a diagonal on the square canvas).
- Then we called stroke to actually draw that on our canvas. Easy!
Canvas is a wide topic and deserves a book of its own! If you're interested in developing awesome games and apps with Canvas, I recommend you start off with MDN docs: http://bit.ly/canvas-html5.
The Fetch API - promise-based HTTP requests
One of the coolest async APIs introduced in browsers is the Fetch API, which is the modern replacement for the XMLHttpRequest API. Have you ever found yourself using jQuery just for simplifying AJAX requests with $.ajax? If you have, then this is surely a golden API for you, as it is natively easier to code and read!
However, fetch comes natively, hence, there are performance benefits. Let's see how it works:
fetch(link)
.then(data => {
/ do something with data
})
.catch(err => {
/ do something with error
});
Awesome! So fetch uses promises! If that's the case, we can combine it with async/await to make it look completely synchronous and easy to read!
Unlock access to the largest independent learning library in Tech for FREE!
Get unlimited access to 7500+ expert-authored eBooks and video courses covering every tech area you can think of.
Renews at $19.99/month. Cancel anytime
<img id="img1" alt="Mozilla logo" />
<img id="img2" alt="Google logo" />
const get2Images = async () => {
const image1 = await fetch('https://cdn.mdn.mozilla.net/static/img/web-docs-sprite.22a6a085cf14.svg');
const image2 = await fetch('https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_150x54dp.png');
console.log(image1); / gives us response as an object
const blob1 = await image1.blob();
const blob2 = await image2.blob();
const url1 = URL.createObjectURL(blob1);
const url2 = URL.createObjectURL(blob2);
document.getElementById('img1').src = url1;
document.getElementById('img2').src = url2;
return 'complete';
}
get2Images().then(status => console.log(status));
The line console.log(image1) will print the following:
You can see the image1 response provides tons of information about the request. It has an interesting field body, which is actually a ReadableStream, and a byte stream of data that can be cast to a Binary Large Object (BLOB) in our case.
A blob object represents a file-like object of immutable and raw data.
After getting the Response, we convert it into a blob object so that we can actually use it as an image. Here, fetch is actually fetching us the image directly so we can serve it to the user as a blob (without hot-linking it to the main website).
Thus, this could be done on the server side, and blob data could be passed down a WebSocket or something similar.
Fetch API customization
The Fetch API is highly customizable. You can even include your own headers in the request. Suppose you've got a site where only authenticated users with a valid token can access an image. Here's how you'll add a custom header to your request:
const headers = new Headers();
headers.append("Allow-Secret-Access", "yeah-because-my-token-is-1337");
const config = { method: 'POST', headers };
const req = new Request('http://myawesomewebsite.awesometld/secretimage.jpg', config);
fetch(req)
.then(img => img.blob())
.then(blob => myImageTag.src = URL.createObjectURL(blob));
Here, we added a custom header to our Request and then created something called a Request object (an object that has information about our Request). The first parameter, that is, http://myawesomewebsite.awesometld/secretimage.jpg, is the URL and the second is the configuration. Here are some other configuration options:
- Credentials: Used to pass cookies in a Cross-Origin Resource Sharing (CORS)-enabled server on cross-domain requests.
- Method: Specifies request methods (GET, POST, HEAD, and so on).
- Headers: Headers associated with the request.
- Integrity: A security feature that consists of a (possibly) SHA-256 representation of the file you're requesting, in order to verify whether the request has been tampered with (data is modified) or not. Probably not a lot to worry about unless you're building something on a very large scale and not on HTTPS.
- Redirect: Redirect can have three values:
- Follow: Will follow the URL redirects
- Error: Will throw an error if the URL redirects
- Manual: Doesn't follow redirect but returns a filtered response that wraps the redirect response
- Referrer: the URL that appears as a referrer header in the HTTP request.
Accessing and modifying history with the history API
You can access a user's history to some level and modify it according to your needs using the history API. It consists of the length and state properties:
console.log(history, history.length, history.state);
The output is as follows:
{length: 4, scrollRestoration: "auto", state: null}
4
null
In your case, the length could obviously be different depending on how many pages you've visited from that particular tab.
history.state can contain anything you like (we'll come to its use case soon). Before looking at some handy history methods, let us take a look at the window.onpopstate event.
Handling window.onpopstate events
The window.onpopstate event is fired automatically by the browser when a user navigates between history states that a developer has set. This event is important to handle when you push to history object and then later retrieve information whenever the user presses the back/forward button of the browser.
Here's how we'll program a simple popstate event:
window.addEventListener('popstate', e => {
console.log(e.state); / state data of history (remember history.state ?)
})
Now we'll discuss some methods associated with the history object.
Modifying history - the history.go(distance) method
history.go(x) is equivalent to the user clicking his forward button x times in the browser. However, you can specify the distance to move, that is history.go(5); . This equivalent to the user hitting the forward button in the browser five times.
Similarly, you can specify negative values as well to make it move backward. Specifying 0 or no value will simply refresh the page:
history.go(5); / forwards the browser 5 times
history.go(-1); / similar effect of clicking back button
history.go(0); / refreshes page
history.go(); / refreshes page
Jumping ahead - the history.forward() method
This method is simply the equivalent of history.go(1).
This is handy when you want to just push the user to the page he/she is coming from. One use case of this is when you can create a full-screen immersive web application and on your screen there are some minimal controls that play with the history behind the scenes:
if(awesomeButtonClicked && userWantsToMoveForward()) {
history.forward()
}
Going back - the history.back() method
This method is simply the equivalent of history.go(-1). A negative number, makes the history go backwards. Again, this is just a simple (and numberless) way to go back to a page the user came from. Its application could be similar to a forward button, that is, creating a full-screen web app and providing the user with an interface to navigate by.
Pushing on the history - history.pushState()
This is really fun. You can change the browser URL without hitting the server with an HTTP request. If you run the following JS in your browser, your browser will change the path from whatever it is (domain.com/abc/egh) to /i_am_awesome (domain.com/i_am_awesome) without actually navigating to any page:
history.pushState({myName: "Mehul"}, "This is title of page", "/i_am_awesome");
history.pushState({page2: "Packt"}, "This is page2", "/page2_packt"); / <-- state is currently here
The History API doesn't care whether the page actually exists on the server or not. It'll just replace the URL as it is instructed.
The popstate event when triggered with the browser's back/forward button, will fire the function below and we can program it like this:
window.onpopstate = e => { / when this is called, state is already updated.
/ e.state is the new state. It is null if it is the root state.
if(e.state !== null) {
console.log(e.state);
} else {
console.log("Root state");
}
}
To run this code, run the onpopstate event first, then the two lines of history.pushState previously. Then press your browser's back button. You should see something like:
{myName: "Mehul"}
which is the information related to the parent state. Press back button one more time and you'll see the message Root State.
pushState does not fire onpopstate event. Only browsers' back/forward buttons do.
Pushing on the history stack - history.replaceState()
The history.replaceState() method is exactly like history.pushState(), the only difference is that it replaces the current page with another, that is, if you use history.pushState() and press the back button, you'll be directed to the page you came from.
However, when you use history.replaceState() and you press the back button, you are not directed to the page you came from because it is replaced with the new one on the stack. Here's an example of working with the replaceState method:
history.replaceState({myName: "Mehul"}, "This is title of page", "/i_am_awesome");
This replaces (instead of pushing) the current state with the new state.
Although using the History API directly in your code may not be beneficial to you right now, many frameworks and libraries such as React, under the hood, use the History API to create a seamless, reload-less, smooth experience for the end user.
If you found this article useful, do check out the book Learn ECMAScript, Second Edition to learn the ECMAScript standards for designing quality web applications.