Learn JavaScript: Novice to Ninja

Chapter 14: HTML5 APIs

HTML5 is the latest version of the Hypertext Markup Language used to create web pages. The latest iteration is HTML 5.1, which finally became a W3C recommendation in November 2016.

HTML5 was for HTML what ES6 was for JavaScript – it added a large number of new features to the specification. It also went beyond the actual markup language and brought together a number of related technologies such as CSS and JavaScript. We’ve already seen in Chapter 8 some of the new form elements as well as the validation API that has been introduced. In this chapter, we’ll be looking at some of the other APIs that were made available in HTML5 and beyond.

In this chapter, we’ll cover the following topics:

  • The development of HTML5 and the JavaScript APIs
  • The data- attribute
  • HTML5 APIs―local storage, geolocation, web workers, and multimedia
  • Drawing shapes with canvas
  • Shims and polyfills ― how to make the most of HTML5 APIs, even when they’re without browser support

HTML5

The W3C plans to develop future versions of HTML5 much more frequently than previously, using smaller version increments. HTML 5.1 has already become the latest standard, and HTML 5.2 is in development. You can read more about the new features in HTML5.1 and what to expect in HTML 5.2 in thispost on SitePoint.

The HTML5 specification is separated into modules that allow different features to be developed at different paces then implemented without having to wait for other features to be completed. It also means that when a previously unforeseen development occurs, a new module can be created to cater for it. Modules can be at different stages of maturity, from ideas to full implementation. A useful site that checks to see if a specific feature can be used isCan I Use.

You can find out more about the HTML5 standard by readingJump Start HTML5 by Tiffany Brown, Kerry Butters, and Sandeep Panda.

The data- Attribute

The data- attribute is a way of embedding data in a web page using custom attributes that are ignored by the browser. They’re also private to a page, so are not intended to be used by an external service – their sole purpose is to be used by a JavaScript program. This means they’re perfect for adding data that can be used as a hook that the program utilizes to access information about a particular element on the page.

The names of these attributes can be decided by the developer, but they must use the following format:

  • Start with data- .
  • Contain only lowercase letters, numbers, hyphens, dots, colons or underscores.
  • Include an optional string value.

Examples could be:

data-powers = 'flight superSpeed'data-rating = '5' data-dropdown data-user = 'DAZ' data-max-length = '32'

The information contained in the attributes can be used to identify particular elements. For example, all the elements with an attribute of data-dropdown could be identified as dropdown menu. The values of the attributes can also be used to filter different elements. For example, we could find all the elements that have a data-rating value of 3 or more.

Each element has a dataset property that can be used to access any data- attributes it contains. Here’s an example of some markup:

<div id='hero' data-powers='flight superSpeed'>    Superman</div>

The data-powers attribute can be accessed using the following code:

const superman = document.getElementById('hero');const powers = superman.dataset.powers;<< 'flight superSpeed'

Notice that the data- prefix is dropped. To access the attribute, powers is used as if it’s a property of the dataset object. If a data- attribute’s name contains hyphens, they are replaced with camel-case notation, so data-max-length would be accessed using dataset.maxLength .

Browser Support

The support for the data- attribute is generally very good in modern browsers. Even Internet Explorer 8 has partial support! Some older browsers are unable to understand the dataset property, however, but any data- attribute can be found using the standard getAttribute method. So the previous code could be replaced with the following if you still need to support older browsers:

const powers = superman.getAttribute('data-powers');

The restriction of only using a string value can be overcome by encoding any JavaScript object or value as a JSON string, then performing type-conversion later, as required. For example, the value of data-max-length will return a string, but can easily be converted into a number using the following code:

const maxLength = Number(element.dataset.maxLength);

Data attributes provide a convenient way of adding data directly into the HTML markup, enabling a richer user experience. More information about data attributes can be found inthis post on SitePoint.

HTML5 APIs

The HTML5 specification contains a number of APIs that help to gain access to hardware, such as cameras, batteries, geolocation, and the graphics card. Hardware evolves quickly, and APIs are frequently introduced to give developers access, and control new features that appear in the latest devices.

In this section, we’ll look at some of the more popular APIs that are already supported in most modern browsers. However, due to the ever-changing nature of most APIs, it’s still best practice to use feature detection before using any of the API methods.

HTML5 Web Storage

The Web Storage API provides a key-value store on the client’s computer that is similar to using cookies but has fewer restrictions, more storage capacity, and is generally easier to use. This makes it perfect for storing information about users, as well as storing application-specific information that can then be used during future sessions.

The Web Storage API has some crucial differences with cookies:

  • Information stored isnotshared with the server on every request.
  • Information is available in multiple windows of the browser (but only if the domain is the same).
  • Storage capacity limit is much larger than the 4KB limit for cookies ( There is no actual limit in the specification, but most browsers have a limit set at 5GB per domain.).
  • Any data stored does not automatically expire as it does with cookies. This potentially makes cookies a better choice for something like showing a popup once a day.

If a browser supports the Web Storage API, the window object will have a property called localStorage , which is a native object with a number of properties and methods used to store data. The information is saved in the form of key-value pairs, and the values can only be strings. There is also a sessionStorage object that works in the same way, although the data is only saved for the current session.

Here is a basic example of storing information. To save a value locally, use:

localStorage.setItem('name', 'Walter White');

To illustrate that it’s being saved locally, try completely closing your browser, reopening it, and entering the following code in the console:

localStorage.getItem('name'); << "Walter White"

Rather than using the getItem() and setItem() methods, assignment can be used instead. In the next example, we simply reference localStorage.name as if it was a variable to change its value:

localStorage.name = 'Heisenberg'; console.log(localStorage.name); << "Heisenberg";

To remove an entry from local storage, use the removeItem method:

localStorage.removeItem('name');

Alternatively, this can be done using the delete operator:

delete localStorage.name;

To completely remove everything stored in local storage, use the clear() method:

localStorage.clear();

Every time a value is saved to local storage, a storage event is fired. Note that this event is only fired on anyotherwindows or tabs from the same domain, and only if the value of the item being saved changes. The event object sent by the event listener to the callback has a number of properties that provide information about the updated item:

  •  key tells us the key of the item that changed
  •  newValue tells us the new value to which it has been changed
  •  oldValue tells us the previous value before it was changed
  •  storageArea tells us if it is stored in local or session storage.

The code following will add an event listener that logs information about any changes to the Web Storage (note that this example won’t work locally as it needs to be running on a server):

addEventListener('storage', (event) => {console.log(`The ${event.key} was updated from ${event.oldValue} to ${event.newValue} and saved in ${event.storageArea}`) }, false);

The fact that only strings can be saved might seem like a restriction at first, but by using JSON, we can store any JavaScript object in local storage. For example, we could save the hero object that we created using a form in Chapter 8 by adding the following line of code to the end of the makeHero() function:

localStorage.setItem('superman', JSON.stringify(hero);

This will save the hero object as a JSON string using the string ‘superman’ as the key. To retrieve the superhero as a JavaScript object:

superman = JSON.parse(localStorage.getItem('superman'));

The Web Storage API provides a useful way of storing various types of information on a user’s computer without the restriction of cookies. More information about it can be found inthis article on SitePoint.

Geolocation

The Geolocation API is used to obtain the geographical position of the device. This means it can be used to find the user’s exact location, then link to nearby places or measure the speed at which the user is moving. This information can then be used to filter data based on the user’s location or speed and direction of travel. An example of this might be a search function that returns results based on your location. Because of privacy concerns, permission to use this has to be granted by the user first.

If geolocation is available, it will be a property of the navigator object that we met in Chapter 9. This property has a method called getCurrentPosition() that will return a position object to a specified callback function, called youAreHere() in the example:

navigator.geolocation.getCurrentPosition(youAreHere);

The position object passed to the youAreHere() function has a coords property with a latitude and longitude property, which together give the coordinates of the device. These coordinates can then be used in conjunction with other applications or web services (such as a mapping service) to obtain the user’s exact location. In this example, we simply show an alert dialog that displays the user’s coordinates:

function youAreHere(position) {    console.log(`Latitude: ${position.coords.latitude}, Longitude: ${position.coords.longitude}`); }

The position object has several other properties that can be used to find out information about the location and movement of the device:

  •  position.speed property returns the ground speed of the device in meters per second.
  •  position.altitude property returns an estimate of the device’s altitude in meters above theWGS84ellipsoid, which is a standard measurement for the center of the Earth.
  •  position.heading property returns the direction the device is moving in. This is measured as a bearing in degrees, clockwise from North.
  •  position.timestamp property returns the time that the position information was recorded.

The position object also has properties that calculate the accuracy of the measurements. These can be useful, as sometimes you only need to know the town or city users are in, while at other times you may need their exact position. position.accuracy property returns the accuracy of the latitude and longitude properties in meters. The lower the returned value, the more accurate the measurements are, as is the case for the position.altitudeAccuracy property, which returns the accuracy of the altitude property in meters.

In addition, the geolocation object has a watchPosition() method that will call a callback function every time the position of the device is updated. This method returns an ID that can be used to reference the position being watched:

const id = navigator.geolocation.watchPosition(youAreHere);

The clearWatch() method can be used to stop the callback being called, using the ID of the watch as an argument:

navigator.geolocation.clearWatch(id);

The Geolocation API provides a useful interface for adding location-based information to a website or application. More information can be found at theMozilla Developer Network.

Web Workers

We saw in earlier chapters that JavaScript is a single-threaded language, meaning that only one process can run at one time. Web workers allow processes to be run in the background, adding support for concurrency in JavaScript. The idea is that any processes that could take a long time are carried out in the background, so a website will continue to function without fear of the dreaded ‘script has become unresponsive’ message that occurs when a script runs for too long, shown below.

An unresponsive script

To get started, use the Worker() constructor function to create a new worker:

const worker = new Worker('task.js');

Chrome Support

At the time of writing, the Chrome browser won’t let you run workers from local files like this. A workaround is start Chrome using the --allow-file-access-from-files flag, or simply use a different browser for this example.

If you decide to use the --allow-file-access-from-files flag, make sure you only use this for development, rather than for regular browsing.

This function takes the name of another JavaScript file as an argument. In the example, this is a file called ‘task.js’. If this file exists, it will be downloaded asynchronously. The worker will only start once the file has finished downloading completely. If the file doesn’t exist, an error is thrown.

The variable that’s assigned to the constructor function ( worker in our example) can now be used to refer to the worker in the main program. In the worker script (‘task.js’), the keyword self is used to refer to the worker.

Web workers use the concept of messages to communicate back and forth between the main script and worker script. The postMessage() method can be used to send a messageandstart the worker working. The argument to this method can send any data to the web worker. To post a messagetothe worker, the following code is used inside the main script:

worker.postMessage('Hello');

To post a messagefromthe worker, the following is used in the worker script:

self.postMessage('Finished');

When a message is posted, a message event is fired, so they can be dealt with using an event listener. The data sent with the message as an argument is stored in the data property of the event object that’s passed to the callback function. The following example would log any data returned from the worker to the console:

worker.addEventListener('message', (event) => {    console.log(event.data);}, false);

When a worker has completed its task, it can be stopped using the terminate() method from within the main script:

worker.terminate();

Or using the close() method from inside the worker script:

self.close();

A Factorizing Example

Back in Chapter 10, we created a function that found the factors of a given number. This works well, but can take a long time to find the factors of large numbers. If it was used in a website, it would stop any other code from running while it calculated the factors. To demonstrate this, save the following code in a file called ‘factors.html’:

<!doctype html><html lang='en'><head>    <meta charset='utf-8'>    <title>Factorizor</title> </head><body>    <button id='rainbow'>Change Color</button>    <form>        <label for='number'>Enter a Number to Factorize:</label>        <input id='number' type='number' name='number' min=1 value='20'>        <button type='submit'>Submit</button>    </form>    <div id='output'></div>    <script src='main.js'></script></body></html>

This web page has a button that will change the background color of the page, and an input field where a number can be entered. The factors will be displayed inside the output div. To get this working, create a file called main.js in the same directory as ‘factors.html’ that contains the following code:

const btn = document.getElementById('rainbow');const rainbow = ['red','orange','yellow','green','blue','rebeccapurple','violet'];function change() {          document.body.style.background = rainbow[Math.floor(7*    Math.random())];}btn.addEventListener('click', change);

This first piece of code was covered way back in Chapter 1 and uses an event listener to change the background color if the button is clicked. We also need to factorize the number entered in the form, so add this code to the end of main.js :

const form = document.forms[0];form.addEventListener('submit', factorize, false);function factorize(event) {    // prevent the form from being submitted    event.preventDefault();         const number = Number(form.number.value);    document.getElementById('output').innerText = factorsOf(number);  }function factorsOf(n) {    if(Number.isNaN(Number(n))) {        throw new RangeError('Argument Error: Value must be an integer');    }    if(n < 0) {        throw new RangeError('Argument Error: Number must be positive');    }    if(!Number.isInteger(n)) {        throw new RangeError('Argument Error: Number must be an integer');    }    const factors = [];    for (let i=1 , max = Math.sqrt(n); i <= max ; i++) {        if (n%i === 0){        factors.push(i,n/i);        }    }    return factors.sort((a,b) =>  a - b);}

This uses the same factorsof() function from Chapter 10 and adds a submit event listener to the form. When the form is submitted, it will find the factors of the number in the input field, then place the result inside the output div.

This works well, even coping with some large numbers, as can be seen in the screenshot below.

Our Factorizor in action

But if you enter a sizable number (around 18–20 digits), it takes longer to process the answer, and the browser will display a warning or the dreaded ‘rainbow-whirl’ that means something is taking a long time.

To make matters worse, it’s impossible to click on the ‘Change Color’ button while the factors are being calculated ― the whole program freezes until the operation is complete. (If you recklessly decided to try a huge number and now have an unresponsive web page on your hands, you can simply close the tab and reopen it, or use the Chrome Task Manager to kill the process.)

The good news is that we can use web workers to solve this problem.

Running This Example

The file containing the worker code is expected to be hosted on a server. This is the best option, but if you want to run an example locally you need to turn off thesame origin policysetting in the browser.

Firstly, we create a new file called factors.js ; and save it in the same folder as main.js . Then we remove the factorsOf() function from the main.js file and add it into our new factors.js ; file. We’ll be adding more to this file later, but first we need to edit the factorize() function in the main.js file so it looks like the following:

function factorize(event) {    // prevent the form from being submitted    event.preventDefault();       document.getElementById('output').innerHTML = '<p>This could take a while ...</p>';    const number = Number(form.number.value);    if(window.Worker) {        const worker = new Worker('factors.js');        worker.postMessage(number);        worker.addEventListener('message', (event) => {        document.getElementById('output').innerHTML = event.data;        }, false);    }}

We start by preventing the form from being submitted, then display a message that says ‘This could take a while …’. This message is displayed until the worker returns a result, so in the cases of small numbers, this will hardly be seen.

After checking whether web workers are supported, it adds a new web worker. It then uses the postMessage() method to send a message to the worker, which is the number we want to factorize. When the number has been factorized, the worker will send a message back to say it has finished.

To deal with this, we set up an event listener that will fire when a message is received back from the worker. The information sent from the worker is stored in the data property of the event object, so we use innerHTML to insert the data into the output div.

Now we go back to the factors.js ; file and add this event listener code to the end of the file:

self.addEventListener('message', (event) => {    const factors = String(factorsOf(Number(event.data)));    self.postMessage(factors);    self.close();}, false);

This will fire when the worker receives a message, occurring when the form is submitted. The number to be factorized is stored in the event.data property. We use the factorsOf() function to find the factors of the number, then convert it into a string and send a message back containing the answer. We then use the close() method to terminate the worker, since its work is done.

Now if we test the code, it will still take a long time to factorize a long number, but the page will not freeze. You can also continue to change the background color while the factors are being calculated in the background.

Shared Web Workers

The examples we have seen so far are known as dedicated web workers. These are linked to the script that loaded the worker, and are unable to be used by another script. You can also create shared web workers that allow lots of different scripts on the same domain to access the same worker object.You can read more about shared web workers in this post on SitePoint.

Web workers allow computationally complex operations to be performed in a separate thread, meaning that the flow of a program won’t suffer interruptions, and an application will not freeze or hang. They are a useful feature that help to keep sites responsive, even when complicated operations are being carried out. You can findmore information about them at the Mozilla Developer Network.

Service Workers

The Service Worker API allows a worker script to run in the background with the added benefit of being able to intercept network requests. This allows you to take alternative action if the network is offline, and effectively create app-like offline experiences. Service workers also allow access to push notifications and background syncing. Service workers require a secure network to run on HTTPS to avoid any malicious code hijacking network requests.

There are a large number of different examples of using the Service Worker API inThe Service Worker Cookbook, which is maintained by Mozilla.

Websockets

As we saw in the last chapter, the main form of communication on the web has always been the HTTP protocol. This uses a system of request and response to send data back and forth. A problem with this method of communication is when you only get a response when a request is sent. But what if the response comes later? For example, imagine a chat application. You send a message to somebody then wait for a reply, but you can’t get a reply unless you send them another request… and you’ll only get the reply if they have sent the response when you send your request. How long do you wait until you send the next request? This was partially solved using Ajax and a method called ‘polling’ where a request was periodically sent to see if there had been a response.

Websocket is a new protocol that allows two-way communication with a server – also known as push messaging. This means that a connection is kept open and responses are ‘pushed’ to the client as soon as they are received.

To see this in action, we’ll create a mini-messaging application that uses the websocket protocol to communicate with an Echo server. This sends a response that is exactly the same as the message it receives.

Create a file called websocket.html and add the following HTML code:

<!doctype html><html lang='en'><head>    <meta charset='utf-8'>    <title>Websocket Example</title></head><body>    <form>        <label for='message'>Enter a Message:</label>        <input type='text' name='message'>        <button type='submit'>Submit</button>    </form>    <div id='output'></div>    <script src='main.js'></script></body></html>

Apart from the usual standard HTML elements, this has a form for entering and submitting a short message and an empty output <div> element, where the message and responses will be displayed.

Next we need to place our JavaScript code inside a file called main.js :

const URL = 'wss://echo.websocket.org/';const outputDiv = document.getElementById('output');const form = document.forms[0];const connection = new WebSocket(URL);

This sets up some variables to store information. The first is URL , which is the URL we’ll be using to connect to the websocket. Notice that it starts ‘wss://’ instead of ‘https://’ This is the secure protocol used by websockets instead of HTTP. The site is the Echo server hosted atwebsocket.org. This accepts messages then returns the same message (like an echo).

The next variable is outputDiv and it’s used to store a reference to the <div> element where we will be displaying the messages. The form variable is also used to store a reference to the form element.

Last of all, we create a variable called connection that stores a reference to our websocket object. This is created using a constructor function, and takes the URL as a parameter. We will use the variable connection to refer to the websocket connection throughout the program.

When the code new WebSocket(URL) runs, it creates an instance of a WebSocket object and tries to connect to the URL. When this is successful, it fires an event called ‘open’. This is one of a number of events that a WebSocket object can emit. To deal with it, we can add an event handler to main.js :

connection.addEventListener('open', () => {    output('CONNECTED');}, false);

This works in the same way as the event handlers we’ve seen previously, and is called on the connection object. In this case, we call a function called output() with the string ‘CONNECTED’ provided as an argument. The output() is used to output messages to the screen. We need to add that function next:

function output(message) {    const para = document.createElement('p');    para.innerHTML = message;    outputDiv.appendChild(para);}

This function takes a string as an argument then appends a new paragraph element to the <div> with an ID of ‘output’. The message is then placed inside this paragraph. This has the effect of producing a constant stream of messages inside this <div> .

Now we need to add some code to allow us to add some messages. We’ll start by adding an event listener to deal with when the form is submitted:

form.addEventListener('submit', message, false);

This invokes a function called message() , so let’s write that now:

function message(event) {    event.preventDefault();    const text = form.message.value;    output(`SENT: ${text}`);    connection.send(text);}

First of all we stop the default behavior, so the form doesn’t actually get submitted. Then we grab the value of the text input and store it in a local variable called text . We then use the output() function again to add the message to the ‘output’ <div> , with the phrase ‘SENT:’ at the start.

The last line is an important one. This calls a method of the connection object called send() . This sends the message to the URL that the websocket is connected to. When this message is received, the server will process it and send a response. The connection object waits for the response, and when it receives one, a ‘message’ event is fired. The ‘echo.websocket.org’ server simply responds with the same message, but any message could be processed in a variety of ways before sending a response.

Let’s create an event handler to deal with the response:

connection.addEventListener('message', (event) => {    output(`RESPONSE: ${event.data}`);}, false);

This uses the event object that is provided as an argument to the event, and we can use the data property to access the data that was returned. It’s then a simple case of using the output() function again to add this message to the growing stream of messages in the ‘output’ <div> , but this time with the phrase ‘RESPONSE:’ added to the beginning.

If you open websocket.html in a browser, you should see something similar to in the screenshot below:

Websocket messages

There are a couple of other events that the connection object responds to that are worth knowing about: The close event occurs when the connection is closed, which can be done using the close() method. The error event is fired when any sort of error occurs with the connection. The information about the error can be accessed in the data property of the event object.

Typical event listeners for these events might looks like the ones below:

connection.addEventListener('close', () => {    output('DISCONNECTED');}, false);connection.addEventListener('error', (event) => {output(`<span style='color: red;'>ERROR: ${event.data}</span>`);}, false);

Websockets are an exciting technology, and you can see lots of examples of how they can be used, as well as finding out more about them atwebsocket.organd inthis article on SitePoint.

Notifications

The Notification API allows you to show messages using the system’s notifications. This is usually a popup in the corner of the screen, but it changes depending on the operating system. An advantage of using the system notification is that they will still be displayed even if the web page that calls them isn’t the current tab.

Before you can send notifications, you need to get permission granted by the user. This can be achieved using the requestPermission() method of a Notification global object. To try this out, visit any website in your browser (https://sitepoint.comfor example), and enter the following code in the console:

if(window.Notification) {    Notification.requestPermission();}

This will ask for permission, similar to in the screenshot below:

Permission for notifications

This returns a promise with the permission property of the Notification object set to either ‘granted’ or ‘denied’. If it’s set to granted, you can create a new notification using a constructor function, like so:

if(window.Notification) {    Notification.requestPermission()    .then((permission) => {        if(Notification.permission === 'granted') {        new Notification('Hello JavaScript!');        }    });}

This will produce a system notification with the title ‘Hello JavaScript!’.

The constructor function’s first parameter is the title of the notification, and is required. The function also accepts a second parameter, which is an object of options. These include body that specifies any text that you want to appear below the title, and icon where you can specify a link to an image that will be displayed as part of the notification:

const notification = new Notification('JavaScript: Novice to Ninja',{    body: 'The new book from SitePoint',    icon: 'sitepointlogo.png'});

Depending on your browser and operating system, some notifications close automatically after a short period of time, and some will stay on the screen until the user clicks on them. You can close the notification programmatically using the close() method:

notification.close();

The notification instance has a number of events that it can react to, including click (when a user clicks on it), show (when the notification appears) and close (when the notification is closed).

For example, you could open a new window when the user clicked on the notification using the following code:

notification.addEventListener('click', () => {window.open('https://sitepoint.com')}, false);

You can read more about theNotification API on MDNandthis article on SitePoint.

Multimedia

Before HTML5, it was notoriously difficult to display audio and video in browsers, and plugins such as Flash often had to be used. HTML5 introduced the <audio> and <video> tags used to insert audio and video clips into a web page. It also introduced a Media API for controlling the playback of the clips using JavaScript.

An audio clip can be inserted into a page with the <audio> tag, using the src attribute to point to the audio file:

<audio src='/song.mp3' controls>Your browser does not support the audio element.</audio>

A video clip can be inserted with the <video> tag, using the src attribute to point to the movie file:

<video src='http://movie.mp4' controls>    Your browser does not support the video element.</video>

Any content inside the <audio> or <video> tags will only display if the browser does not support them; hence, it can be used to display a message to users of older browsers without support for these features. The controls attribute can be added (without any value) and will display the browser’s native controls, such as play, pause, and volume control, as can be seen in the screenshot below.

Browser video controls

The audio or video element can be referenced by a variable using one of the DOM methods we saw in Chapter 6:

const video = document.getElementsByTagName('video')[0];

Audio and video elements have a number of properties and methods to control the playback of the clip.

The play() method will start the clip playing from its current position:

video.play();

The pause() method will pause the clip at its current position:

video.pause();

The volume property is a number that can be used to set the audio volume:

video.volume = 0.9;

The muted property is a boolean value that can be used to mute the audio:

video.muted = true;

The currentTime property is a number value that can be used to jump to another part of the clip:

video.currentTime += 10; // jumps forward 10 seconds

The playbackRate property is used to fast-forward or rewind the clip by changing its value. A value of 1 is playback at normal speed:

video.playbackRate = 8; // fast-forward at 8 times as fast

The loop property is a boolean value that can be set to true to make the clip repeat in a loop:

video.loop = true;

The duration property can be used to see how long the clip lasts:

video.duration;<< 52.209

Checking Properties Are Available

Some of the properties are only available once the browser has received all the metadata associated with the video. This means that, in order to ensure a value is returned, you should use an event listener that fires once the metadata has loaded, like the one shown below:

video.addEventListener('loadedmetadata', () => { console.log(video.duration); });

Audio and video clips also have a number of events that will fire when they occur, including:

  • The play event, which fires when the clip starts and when it resumes after a pause.
  • The pause event, which fires when the clip is paused.
  • The volumechange event, which fires when the volume is changed.
  • The loadedmetadata event, which we saw in the note above, and which fires when all the video’s metadata has loaded.

These events allow you to respond to any interactions the user has with the video. For example, the following event listener can be added to check whether the user has paused the video:

video.addEventListener('pause', () => {console.log('The video has been paused'); }, false)

The audio and video elements bring native support for multimedia into the browser, and the API gives developers full control of the playback of audio tracks and video clips.

This pageon the W3C has a full list of all the properties, methods and events that are available for video elements.

Other APIs

The list of APIs is constantly growing, and includes APIs for accessing a device’s camera, uploading files, accessing the battery status, handling push notifications, building drag-and-drop functionality, creating 3D effects with WebGL, and many more! A comprehensive list of HTML5 APIs can be found at theMozilla Developer Network.

Privacy Concerns

There are some security and privacy considerations to keep in mind when considering some HTML5 APIs – especially those on the cutting edge that haven’t been used ‘in the wild’ for long. For example, there are concerns that the ambient light API mightmake it possible to steal dataand the battery API has been dropped by Apple and Mozilla due to concerns overuser profiling.

You can also read more about HTML5 standards and APIs in these SitePoint books:

Drawing with Canvas

The canvas element was introduced to allow graphics to be drawn onto a web page in real time using JavaScript. A canvas element is a rectangular element on the web page. It has a coordinate system that starts at (0,0) in the top-left corner. To add a canvas element to a page, the <canvas> tag is used specifying a height and width . Anything placed inside the tag will only display if the canvas element is unsupported:

<canvas id='canvas' width='400' height='400'>Sorry, but your browser does not support the canvas element</canvas>

This canvas can now be accessed in a JavaScript program using the document.getElementById() method:

const canvasElement = document.getElementById('canvas');

The next step is to access the context of the canvas. This is an object that contains all the methods used to draw onto the canvas. We’ll be using a 2-D context, but it’s also possible to render in 3-D usingWebGL.

The getContext() method is used to access the context:

const context = canvasElement.getContext('2d');

Now we have a reference to the context, we can access its methods and draw onto the canvas. The fill and stroke colors can be changed by assigning a CSS color to the fillStyle and strokeStyle properties respectively:

context.fillStyle = "#0000cc"; // a blue fill color context.strokeStyle = "#ccc"; // a gray stroke color

These colors will be utilized for everything that’s drawn onto the canvas until they’re changed.

The lineWidth property can be used to set the width of any line strokes drawn onto the canvas. It defaults to one pixel and remains the same until it’s changed:

context.lineWidth = 4;

The fillRect() method can draw a filled-in rectangle. The first two parameters are the coordinates of the top-left corner, the third parameter is the width, and the last parameter is the height. The following produces a filled-in blue rectangle in the top-left corner of the canvas at coordinates (10,10) that is 100 pixels wide and 50 pixels high:

context.fillRect(10,10,100,50);

The strokeRect() method works in the same way, but produces a rectangle that is not filled in. This will draw the outline of a rectangle underneath the last one:

context.strokeRect(10,100,100,50);

Straight lines can be drawn employing the moveTo() and lineTo() methods. These methods can be used together to produce a path. Nothing will actually be drawn onto the canvas until the stroke() method is called. The following example will draw a thick red T shape onto the canvas by moving to the coordinates (150,50), then drawing a horizontal line 30 pixels long, and finally moving to the middle of that line and drawing a vertical line 40 pixels long:

context.beginPath();context.moveTo(130, 50);context.lineTo(180, 50);context.moveTo(155, 50);context.lineTo(155, 90);context.strokeStyle = '#c00';context.lineWidth = 15;context.stroke();

The arc() method can be used to draw an arc of a given radius from a particular point. The first two parameters are the coordinates of the center of the arc; the next parameter is the radius, followed by the start angle, then the finish angle (note that these are measured in radians). The last parameter is a boolean value that says whether the arc should be drawn counter-clockwise. The following example will draw a yellow circle of radius 30 pixels at center (200,200), since Math.PI * 2 represents a full turn:

context.beginPath();context.arc(200, 200, 30, 0, Math.PI * 2, false);context.strokeStyle = '#ff0';context.lineWidth = 4;context.stroke();

The fillText() method is used to write text onto the canvas. The first parameter is the text to be displayed, while the next two parameters are the x and y coordinates, respectively. The font property can be used to set the font style used, otherwise the style is inherited from the canvas element’s CSS setting (note that it needs to be changedbeforethe fillText() method is used to draw the text). The following example will draw the text “Hello” in green at coordinates (20,50), as shown below.

context.fillStyle = '#0c0'; // a blue fill colorcontext.font = 'bold 26px sans-serif';context.fillText('Hello', 20, 200);
Drawing on a canvas

This is only a short introduction to what the canvas element can do. It is being used more and more in websites to draw data charts that are updated in real-time, as well as to animate HTML5 games. Much more information can be found in the excellentJump Start HTML5 by Tiffany Brown, Kerry Butters, and Sandeep Panda.

Shims and Polyfills

HTML5 APIs progress at a rapid rate ― new APIs are constantly being introduced, and existing APIs often change. Modern browsers are very quick to update and implement many of the changes, but you can’t always guarantee that users will have the most up-to-date browser. This is where a shim or a polyfill comes in handy. These are libraries of code that allow you to use the APIs as usual. They then fill in the necessary code that’s not provided natively by the user’s browser.

The terms shim and polyfill are often used interchangeably. The main difference between them is that a shim is a piece of code that adds some missing functionality to a browser, although the implementation method may differ slightly from the standard API. A polyfill is a shim that achieves the same functionality, while also using the API commands that would be used if the feature was supported natively.

This means that your code can use the APIs as normal and it should work as expected in older browsers. The advantage here is that the same set of standard API commands can be used ― you don’t need to write additional code to deal with different levels of support. And when users update their browsers, the transition will be seamless, as their experience will remain the same. Once you are confident that enough users have up-to-date browsers, you can remove the polyfill code without having to update any actual JavaScript code.

A comprehensivelist of shims and polyfills is maintained by the Modernizr team.

Quiz Ninja Project

We’re going to use the Web Storage API to store the high score of the game. This will be stored locally even after the browser has been closed, so players can keep a record of their best attempt and try to beat it. To do this, we first add an extra <div> element to the header to show the high score. Change the <header> element in index.html to the following:

<section class='dojo'>    <div class='quiz-body'>      <header>        <div id='timer'>Time: <strong>20</strong></div>        <div id='score'>Score: <strong>0</strong></div>        <div id='hiScore'>High Score: <strong></strong></div>        <h1>Quiz Ninja!</h1>      </header>    <div id='question'></div>    <div id='response'></div>    <div id='result'></div>    <div id='info'></div>    <button id='start'>Click to Start</button>    </div>  </section>

Now we need to add a method to the game object updates, and return the high score. Add the following to the end of the game object:

hiScore(){    const hi = localStorage.getItem('highScore') || 0;    if(this.score > hi || hi === 0) {      localStorage.setItem('highScore',this.score);      view.render(view.info,'** NEW HIGH SCORE! **');    }    return localStorage.getItem('highScore');  }}

This method sets a local variable called hi to the value that’s stored inside the object under the key highScore . If a high score is yet to be set already, it will be null , so we’ll initialize it to 0 in this case using lazy evaluation. Next, we check to see if value of this.score (which will be the player’s final score) is bigger than the current high score that we just retrieved. If it is, we show a message to congratulate the player, and also update the value stored in localStorage using the setItem() method.

We also need to update the setup() method and teardown() method on the view object.

setup(){  this.show(this.question);  this.show(this.response);  this.show(this.result);  this.hide(this.start);  this.render(this.score,game.score);  this.render(this.result,'');  this.render(this.info,'');  this.render(this.hiScore, game.hiScore());}
teardown(){  this.hide(this.question);  this.hide(this.response);  this.show(this.start);  this.render(this.hiScore, game.hiScore());}

Have a go at playing it by opening up index.html , as shown below, and try to get a new high score.

high scores in action

You can see a live example onCodePen.

Chapter Summary

  • HTML5.1 is the latest incarnation of the Hypertext Markup Language. It covers a variety of technologies, including several APIs that are accessible using JavaScript.
  •  data- attributes help to embed custom data into a web page that can then be used to enhance the user experience with JavaScript.
  • The Web Storage API allows key-value pairs to be stored on the user’s device in a similar way to cookies, but without the same storage restrictions.
  • The Geolocation API allows you to access the geographic coordinates of the user’s device, as long as the user gives permission.
  • The Web Worker API can be used to perform computationally intensive tasks in the background, which helps to avoid websites becoming unresponsive.
  • Websockets are a new protocol for communicating over the internet, and allow real-time, two-way communication.
  • The Notification API allows you to display notifications on the user’s system.
  • The <audio> and <video> elements can be employed to embed audio tracks and video clips in a web page. They also have a Media API that can help control the playback using JavaScript.
  • The canvas element can be used to dynamically draw geometric shapes, text, and images on a web page in real-time using JavaScript.
  • A shim or polyfill is a piece of code that adds support of missing features to older browsers.

In the next chapter, we’ll cover how to organize and optimize your code.

Pages: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16