The Web Platform APIs Complete Reference
The Web Platform APIs Complete Reference
THE WEB
PLATFORM
API
COMPLETE
REFERENCE
A book for Frontend Developers
BY FLAVIO COPES
HTTPS://FLAVIOCOPES.COM
Properties
Methods
The DOM is the browser internal representation of a web page. When the browser retrieves
your HTML from your server, the parser analyzes the structure of your code, and creates a
model of it. Based on this model, the browser then renders the page on the screen.
Browsers expose an API that you can use to interact with the DOM. That’s how modern
JavaScript frameworks work, they use the DOM API to tell the browser what to display on the
page.
In Single Page Applications, the DOM continuously changes to re ect what appears on the
screen, and as a developer you can inspect it using the Browser Developer Tools.
The DOM is language-agnostic, and the de-facto standard to access the DOM is by using
JavaScript, since it’s the only language that browsers can run.
The main 2 objects provided by the DOM API, the ones you will interact the most with, are
document and window .
Properties and methods of this object can be called without referencing window explicitly,
because it represents the global object. So, the previous property window.document is
usually called just document .
Properties
Here is a list of useful properties you will likely reference a lot:
console points to the browser debugging console. Useful to print error messages or
logging, using console.log , console.error and other tools (see the Browser DevTools
article)
document as already said, points to the document object, key to the DOM interactions
you will perform
history gives access to the History API
location gives access to the Location interface (https://developer.mozilla.org/en-
US/docs/Web/API/Location) , from which you can determine the URL, the protocol, the
hash and other useful information.
localStorage is a reference to the Web Storage API localStorage object
sessionStorage is a reference to the Web Storage API sessionStorage object
Methods
The window object also exposes useful methods:
See the full reference of all the properties and methods of the window object at
https://developer.mozilla.org/en-US/docs/Web/API/Window
Here is a representation of a portion of the DOM pointing to the head and body tags:
Here is a representation of a portion of the DOM showing the head tag, containing a title tag
with its value:
Here is a representation of a portion of the DOM showing the body tag, containing a link, with a
value and the href attribute with its value:
The Document object can be accessed from window.document , and since window is the
global object, you can use the shortcut document object directly from the browser console, or
in your JavaScript code.
This Document object has a ton of properties and methods. The Selectors API methods are the
ones you’ll likely use the most:
document.getElementById()
document.querySelector()
document.querySelectorAll()
document.getElementsByTagName()
document.getElementsByClassName()
You can get the document title using document.title , and the URL using document.URL .
The referrer is available in document.referrer , the domain in document.domain .
From the document object you can get the body and head Element nodes
(https://developer.mozilla.org/en-US/docs/Web/API/Element) :
You can also get a list of all the element nodes of a particular type, like an HTMLCollection
(https://developer.mozilla.org/en-US/docs/Web/API/HTMLCollection) of all the links using
document.links , all the images using document.images , all the forms using
document.forms .
The document cookies are accessible in document.cookie . The last modi ed date in
document.lastModified .
You can do much more, even get old school and ll your scripts with document.write() , a
method that was used a lot back in the early days of JavaScript to interact with the pages.
See the full reference of all the properties and methods of the document object at
https://developer.mozilla.org/en-US/docs/Web/API/Document
Types of Nodes
There are di erent types of nodes, some of which you already saw in the example images
above. The main ones you will see are:
From each of those elements, you can navigate the DOM structure and move to di erent
nodes.
They are almost the same, except when ran on the html element: parentNode returns the
parent of the speci ed node in the DOM tree, while parentElement returns the DOM node’s
parent Element, or null if the node either has no parent, or its parent isn’t a DOM Element.
The DOM also exposes a Node.children method, but it will not just include Element nodes,
but it includes also the white space between elements as Text nodes, which is not something
you generally want.
To get the rst child Element Node, use Node.firstElementChild , and to get the last child
Element Node, use Node.lastElementChild :
The DOM also exposes Node.firstChild and Node.lastChild , with the di erence that
they do not “ lter” the tree for Element nodes only, and they will also show empty Text nodes
that indicate white space.
Node.childNodes
Node.firstElementChild
Node.lastElementChild
Node.previousElementSibling
Node.nextElementSibling
The DOM also exposes previousSibling and nextSibling , but as their counterparts
described above, they include white spaces as Text nodes, so you generally avoid them.
With
you can create new elements, and add them the the DOM elements you want, as children, by
using document.appendChild() :
first.removeChild(second) removes the child node “second” from the node “ rst”.
document.insertBefore(newNode, existingNode) inserts “newNode” as a sibling of
“existingNode”, placing it before that in the DOM tree structure.
element.appendChild(newChild) alters the tree under “element”, adding a new child
Node “newChild” to it, after all the other children.
element.prepend(newChild) alters the tree under “element”, adding a new child Node
“newChild” to it, before other child elements. You can pass one or more child Nodes, or
even a string which will be interpreted as a Text node.
element.replaceChild(existingChild, newChild) alters the tree under “element”,
replacing “existingChild” with a new Node “newChild”.
element.insertAdjacentElement(position, newElement) inserts “newElement” in
the DOM, positioned relatively to “element” depending on “position” parameter value. See
the possible values (https://developer.mozilla.org/en-
US/docs/Web/API/Element/insertAdjacentElement) .
element.textContent = 'something' changes the content of a Text node to
“something”.
PROGRESSIVE WEB APPS
A Progressive Web App is an app that can provide additional features based
on the device support, including o ine capabilities, push noti cations and
almost native app look and speed, and local caching of resources
Introduction
What is a Progressive Web App
Progressive Web Apps alternatives
Features
Bene ts
Core concepts
Service Workers
The App Manifest
Example
Caching
Introduction
Progressive Web Apps (PWA) are the latest trend of mobile application development using
web technologies, at the time of writing (march 2018) work on Android and iOS devices with
iOS 11.3 or higher, and macOS 10.13.4 or higher.
PWA is a term that identi es a bundle of techniques that have the goal of creating a better
experience for web-based apps.
This technique was originally introduced by Google in 2015, and proves to bring many
advantages to both the developer and the users.
Developers have access to building almost-first-class applications using a web stack, which is
always considerably easier and cheaper than building native applications, especially when
considering the implications of building and maintaining cross-platform apps.
Devs can bene t from a reduced installation friction, at a time when having an app in the
store does not actually bring anything in terms of discoverability for 99,99% of the apps, and
Google search can provide the same bene ts if not more.
A Progressive Web App is a website which is developed with certain technologies that make the
mobile experience much more pleasant than a normal mobile-optimized website, to a point
that it’s almost working like a native app, as it o ers the following features:
O ine support
Loads fast
Is secure
Is capable of emitting push noti cations
Has an immersive, full-screen user experience without the URL bar
Mobile platforms (Android at the time of writing, but it’s not technically limited to that) o er an
increasing support for Progressive Web Apps to the point of asking the user to add the app to
the home screen when they detect a site a user is visiting is a PWA.
But rst, a little clari cation on the name. Progressive Web App can be a confusing term, and a
good de nition is web apps that take advantage of modern browsers features (like web workers and
the web app manifest) to let their mobile devices “upgrade” the app to the role of a first-class citizen
app.
Let’s focus on the pros and cons of each, and let’s see where PWAs are a good t.
Native Mobile Apps
Native mobile apps are the most obvious way to build a mobile app. Objective-C or Swift on
iOS, Java / Kotlin on Android and C# on Windows Phone.
Each platform has its own UI and UX conventions, and the native widgets provide the
experience that the user expects. They can be deployed and distributed through the platform
App Store.
The main pain point with native apps is that cross-platform development requires learning,
mastering and keeping up to date with many di erent methodologies and best practices, so if
for example you have a small team or even you’re a solo developer building an app on 3
platforms, you need to spend a lot of time learning the technology but also the environment,
manage di erent libraries, and use di erent work ows (for example, iCloud only works on iOS
devices, there’s no Android version).
Hybrid Apps
Hybrid applications are built using Web Technologies, but deployed to the App Store. In the
middle sits a framework or some way to package the application so it’s possible to send it for
review to the traditional App Store.
Most common platforms are Phonegap, Xamarin, Ionic Framework, and many others, and
usually what you see on the page is a WebView that essentially loads a local website.
The key aspect of Hybrid Apps is the write once, run anywhere concept, the di erent
platform code is generated at build time, and you’re building apps using JavaScript, HTML and
CSS, which is amazing, and the device capabilities (microphone, camera, network, gps…) are
exposed through JavaScript APIs.
The bad part of building hybrid apps is that unless you do a great job, you might settle on
providing a common denominator, e ectively creating an app that’s sub-optimal on all
platforms because the app is ignoring the platform-speci c human-computer interaction
guidelines.
Their motto, to distinguish this approach from hybrid apps, is learn once, write anywhere,
meaning that the approach is the same across platforms, but you’re going to create completely
separate apps in order to provide a great experience on each platform.
Performance is comparable to native apps, since what you build is essentially a native app,
which is distributed through the App Store.
Features
Progressive Web Apps have one thing that separates them completely from the above
approaches: they are not deployed to the app store..
This is a key advantage, since the app store is bene cial if you have the reach and luck to be
featured, which can make your app go viral, but unless you’re in the 0,001% you’re not going to
get much bene ts from having your little place on the App Store.
Progressive Web Apps are discoverable using Search Engines, and when a user gets to your
site which has PWAs capabilities, the browser in combination with the device asks the user
if they want to install the app to the home screen. This is huge because regular SEO can
apply to your PWA, leading to much less reliance on paid acquisition.
Not being in the App Store means you don’t need the Apple or Google approval to be in the
users pockets, and you can release updates when you want, without having to go through the
standard approval process which is typical of iOS apps.
PWAs are basically HTML5 applications / responsive websites on steroids, with some key
technologies that were recently introduced that make some of the key features possible. If you
remember the original iPhone came without the option to develop native apps, and developers
were told to develop HTML5 mobile apps, that could be installed to the home screen, but the
tech back then was not ready for this.
The use of service workers allow the app to always have fresh content, and download it in the
background, and provide support for push notifications to provide greater re-engagement
opportunities.
Also, shareability makes for a much nicer experience for users that want to share your app, as
they just need a URL.
Benefits
So why should users and developers care about Progressive Web Apps?
1. PWA are lighter. Native Apps can weight 200MB or more, while a PWA could be in the range
of the KBs.
2. No native platform code
3. Lower the cost of acquisition (it’s much more hard to convince a user to install an app than
to visit a website to get the rst-time experience)
4. Signi cant less e ort is needed to build and release updates
5. Much more support for deep links than regular app-store apps
Core concepts
Responsive: the UI adapts to the device screen size
App-like feel: it doesn’t feel like a website, but rather as an app as much as possible
Offline support: it will use the device storage to provide o ine experience
Installable: the device browser prompts the user to install your app
Re-engaging: push noti cations help users re-discover your app once installed
Discoverable: search engines and SEO optimization can provide a lot more users than the
app store
Fresh: the app updates itself and the content once online
Safe: uses HTTPS
Progressive: it will work on any device, even older one, even if with less features (e.g. just
as a website, not installable)
Linkable: easy to point to it, using URLs
Service Workers
Part of the Progressive Web App de nition is that it must work o ine.
Since the thing that allows the web app to work o ine is the Service Worker, this implies that
Service Workers are a mandatory part of a Progressive Web App.
WARNING: Service Workers are currently only supported by Chrome (Desktop and Android),
Firefox and Opera. See http://caniuse.com/#feat=serviceworkers for updated data on the
support.
TIP: Don’t confuse Service Workers with Web Workers. They are a completely different thing.
A Service Worker is a JavaScript le that acts as a middleman between the web app and the
network. Because of this it can provide cache services and speed the app rendering and
improve the user experience.
Because of security reasons, only HTTPS sites can make use of Service Workers, and this is part
of the reasons why a Progressive Web App must be served through HTTPS.
Service Workers are not available on the device the rst time the user visits the app. What
happens is that the rst visit the web worker is installed, and then on subsequent visits to
separate pages of the site will call this Service Worker.
You add a link to the manifest in all your web site pages header:
Example
{
"name": "The Weather App",
"short_name": "Weather",
"description": "Progressive Web App Example",
"icons": [{
"src": "images/icons/icon-128x128.png",
"sizes": "128x128",
"type": "image/png"
}, {
"src": "images/icons/icon-144x144.png",
"sizes": "144x144",
"type": "image/png"
}, {
"src": "images/icons/icon-152x152.png",
"sizes": "152x152",
"type": "image/png"
}, {
"src": "images/icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png"
}, {
"src": "images/icons/icon-256x256.png",
"sizes": "256x256",
"type": "image/png"
}],
"start_url": "/index.html?utm_source=app_manifest",
"orientation": "portrait",
"display": "standalone",
"background_color": "#3E4EB8",
"theme_color": "#2F3BA2"
}
This is the equivalent of the Apple HIG (Human Interface Guidelines) suggestions to use a
splash screen that resembles the user interface, to give a psychological hint that was found to
lower the perception of the app taking a long time to load.
Caching
The App Shell is cached separately from the contents, and it’s setup so that retrieving the shell
building blocks from the cache takes very little time.
Registration
Scope
Installation
Activation
A Service Worker is programmable proxy between your web page and the network, providing
the ability to intercept and cache network requests, e ectively giving you the ability to create
an offline-first experience for your app.
It’s a special kind of web worker, a JavaScript le associated with a web page which runs on a
worker context, separate from the main thread, giving the bene t of being non-blocking - so
computations can be done without sacri cing the UI responsiveness.
Being on a separate thread it has no DOM access, and no access to the Local Storage APIs and
the XHR API as well, and it can only communicate back to the main thread using the Channel
Messaging API.
Promises
Fetch API
Cache API
And they are only available on HTTPS protocol pages, except for local requests, which do not
need a secure connection for an easier testing.
Background Processing
Service Workers run independent of the application they are associated to, and they can
receive messages when they are not active.
The main scenarios where Service Workers are very useful are:
they can be used as a caching layer to handle network requests, and cache content to be
used when o ine
to allow push notifications
A Service Worker only runs when needed, and it’s stopped when not used.
Offline Support
Traditionally the o ine experience for web apps has been very poor. Without a network, often
web mobile apps simply won’t work, while native mobile apps have the ability to o er either a
working version, or some kind of nice message.
This is not a nice message, but this is what web pages look like in Chrome without a network
connection:
Possibly the only nice thing about this is that you get to play a free game by clicking the
dinosaur, but it gets boring pretty quickly.
In the recent past the HTML5 AppCache already promised to allow web apps to cache
resources and work o ine, but its lack of exibility and confusing behavior made it clear that it
wasn’t good enough for the job, failing its promises (and it’s been discontinued
(https://html.spec.whatwg.org/multipage/o ine.html#o ine) ).
This gives the base of what is called the App Shell architecture.
Caching network requests
Using the Fetch API we can edit the response coming from the server, determining if the server
is not reachable and providing a response from the cache instead.
Registration
Installation
Activation
Registration
Registration tells the browser where the server worker is, and it starts the installation in the
background.
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/worker.js')
.then((registration) => {
console.log('Service Worker registration completed with scope: ',
registration.scope)
}, (err) => {
console.log('Service Worker registration failed', err)
})
})
} else {
console.log('Service Workers not supported')
}
Even if this code is called multiple times, the browser will only perform the registration if the
service worker is new, not registered previously, or if has been updated.
Scope
The register() call also accepts a scope parameter, which is a path that determines which
part of your application can be controlled by the service worker.
It defaults to all les and subfolders contained in the folder that contains the service worker
le, so if you put it in the root folder, it will have control over the entire app. In a subfolder, it
will only control pages accessible under that route.
The example below registers the worker, by specifying the /notifications/ folder scope.
navigator.serviceWorker.register('/worker.js', {
scope: '/notifications/'
})
The / is important: in this case, the page /notifications won’t trigger the Service Worker,
while if the scope was
{
scope: '/notifications'
}
NOTE: The service worker cannot “up” itself from a folder: if its file is put under
/notifications , it cannot control the / path or any other path that is not under
/notifications .
Installation
If the browser determines that a service worker is outdated or has never been registered
before, it will proceed to install it.
This is a good event to prepare the Service Worker to be used, by initializing a cache, and
cache the App Shell and static assets using the Cache API.
Activation
The activation stage is the third step, once the service worker has been successfully registered
and installed.
At this point, the service worker will be able to work with new page loads.
It cannot interact with pages already loaded, which means the service worker is only useful on
the second time the user interacts with the app, or reloads one of the pages already open.
A good use case for this event is to cleanup old caches and things associated with the old
version but unused in the new version of the service worker.
Once a Service Worker is updated, it won’t become available until all pages that were loaded
with the old service worker attached are closed.
This ensures that nothing will break on the apps / pages already working.
Refreshing the page is not enough, as the old worker is still running and it’s not been removed.
Fetch Events
A fetch event is red when a resource is requested on the network.
This o ers us the ability to look in the cache before making network requests.
For example the snippet below uses the Cache API to check if the request URL was already
stored in the cached responses, and return the cached response if this is the case. Otherwise, it
executes the fetch request and returns it.
Background Sync
Background sync allows outgoing connections to be deferred until the user has a working
network connection.
This is key to ensure a user can use the app o ine, and take actions on it, and queue server-
side updates for when there is a connection open, instead of showing an endless spinning
wheel trying to get a signal.
navigator.serviceWorker.ready.then((swRegistration) => {
return swRegistration.sync.register('event1')
});
doSomething() returns a promise. If it fails, another sync event will be scheduled to retry
automatically, until it succeeds.
This also allows an app to update data from the server as soon as there is a working
connection available.
Push Events
Service Workers enable web apps to provide native Push Noti cations to users.
Push and Noti cations are actually two di erent concepts and technologies, but combined to
provide what we know as Push Notifications. Push provides the mechanism that allows a
server to send information to a service worker, and Noti cations are the way service workers
can show information to the user.
Since Service Workers run even when the app is not running, they can listen for push events
coming, and either provide user noti cations, or update the state of the app.
Push events are initiated by a backend, through a browser push service, like the one provided
by Firebase.
Here is an example of how the service worker can listen for incoming push events:
const options = {
title: 'I got a message for you!',
body: 'Here is the body of the message',
icon: '/img/icon-192x192.png',
tag: 'tag-for-this-notification',
}
event.waitUntil(
self.registration.showNotification(title, options)
)
})
Otherwise, since the service worker acts before the page is loaded, and the console is cleared
before loading the page, you won’t see any log in the console.
XMLHTTPREQUEST (XHR)
The introduction of XMLHttpRequest (XHR) in browsers have been a huge
win for the Web Platform, in the mid 2000. Let's see how it works.
Introduction
An example XHR request
Additional open() parameters
onreadystatechange
Aborting an XHR request
Comparison with jQuery
Comparison with Fetch
Cross Domain Requests
Introduction
The introduction of XMLHttpRequest (XHR) in browsers have been a huge win for the Web
Platform, in the mid 2000.
Things that now look normal, back in the day looked like coming from the future. I’m thinking
about GMail or Google Maps, for example, all based in great part on XHR.
XHR was invented at Microsoft in the nineties, and became a de-facto standard as all browsers
implemented it in the 2002-2006 period, and the W3C standardized XMLHttpRequest in 2006.
As it sometimes happen in the Web Platform, initially there were a few inconsistencies that
made working with XHR quite di erent cross-browser.
Libraries like jQuery got a boost of popularity by providing an easy to use abstraction for
developers, and in turn helped spread the usage of this technology.
The xhr connection is set up to perform a GET request to https://yoursite.com , and it’s
started with the send() method:
We can specify the other HTTP methods of course ( get , post , head , put , delete ,
options ).
Other parameters let you specify a ag to make the request synchronous if set to false, and a
set of credentials for HTTP authentication:
onreadystatechange
The onreadystatechange is called multiple times during an XHR request. We explicitly ignore
all the states other than readyState === 4 , which means the request is done.
The states are
fetch('https://yoursite.com')
.then((data) => {
console.log(data)
}
)
.catch((err) => {
console.error(err)
})
One of the most obvious is the enforcement of the same origin policy.
You cannot access resources on another server, unless the server explicitly supports this using
CORS (Cross Origin Resource Sharing).
FETCH API
Learn all about the Fetch API, the modern approach to asynchronous
network requests which uses Promises as a building block
Catching errors
Response Object
Metadata
headers
status
statusText
url
Body content
Request Object
Request headers
POST Requests
Fetch drawbacks
How to cancel a fetch request
Introduction to the Fetch API
Since IE5 was released in 1998, we’ve had the option to make asynchronous network calls in
the browser using XMLHttpRequest (XHR).
Quite a few years after this, GMail and other rich apps made heavy use of it, and made the
approach so popular that it had to have a name: AJAX.
Working directly with the XMLHttpRequest has always been a pain and it was almost always
abstracted by some library, in particular jQuery has its own helper functions built around it:
jQuery.ajax()
jQuery.get()
jQuery.post()
and so on.
They had a huge impact on making this more accessible in particular with regards to making
sure all worked on older browsers as well.
The Fetch API, has been standardized as a modern approach to asynchronous network
requests, and uses Promises as a building block.
Fetch has a good support across the major browsers, except IE.
fetch('/file.json')
and you’re already using it: fetch is going to make an HTTP request to get the file.json
resource on the same domain.
As you can see, the fetch function is available in the global window scope.
Now let’s make this a bit more useful, let’s actually see what the content of the le is:
fetch('./file.json')
.then(response => response.json())
.then(data => console.log(data))
Calling fetch() returns a promise. We can then wait for the promise to resolve by passing a
handler with the then() method of the promise.
That handler receives the return value of the fetch promise, a Response object.
Catching errors
Since fetch() returns a promise, we can use the catch method of the promise to intercept
any error occurring during the execution of the request, and the processing done in the then
callbacks:
fetch('./file.json')
.then(response => {
//...
}
.catch(err => console.error(err))
Response Object
The Response Object returned by a fetch() call contains all the information about the
request and the response of the network request.
Metadata
headers
Accessing the headers property on the response object gives you the ability to look into the
HTTP headers returned by the request:
fetch('./file.json')
.then(response => {
console.log(response.headers.get('Content-Type'))
console.log(response.headers.get('Date'))
})
status
This property is an integer number representing the HTTP response status.
fetch('./file.json')
.then(response => console.log(response.status))
statusText
statusText is a property representing the status message of the response. If the request is
successful, the status is OK .
fetch('./file.json')
.then(response => console.log(response.statusText))
url
url represents the full URL of the property that we fetched.
fetch('./file.json')
.then(response => console.log(response.url))
Body content
A response has a body, accessible using the text() or json() methods, which return a
promise.
fetch('./file.json')
.then(response => response.text())
.then(body => console.log(body))
fetch('./file.json')
.then(response => response.json())
.then(body => console.log(body))
(async () => {
const response = await fetch('./file.json')
const data = await response.json()
console.log(data)
})()
Request Object
The Request object represents a resource request, and it’s usually created using the new
Request() API.
Example:
And exposes several methods including json() , text() and formData() to process the
body of the request.
Request headers
Being able to set the HTTP request header is essential, and fetch gives us the ability to do this
using the Headers object:
or more simply
To attach the headers to the request, we use the Request object, and pass it to fetch()
instead of simply passing the URL.
Instead of:
fetch('./file.json')
we do
The Headers object is not limited to setting value, but we can also query it:
headers.has('Content-Type');
headers.get('Content-Type');
headers.delete('X-My-Custom-Header');
POST Requests
Fetch also allows to use any other HTTP method in your request: POST, PUT, DELETE or
OPTIONS.
Specify the method in the method property of the request, and pass additional parameters in
the header and in the request body:
const options = {
method: 'post',
headers: {
"Content-type": "application/x-www-form-urlencoded; charset=UTF-8"
},
body: 'foo=bar&test=1'
}
fetch(url, options)
.catch((err) => {
console.error('Request failed', err)
})
Fetch drawbacks
While it’s a great improvement over XHR, especially considering its Service Workers integration,
Fetch currently has no way to abort a request once it’s done. With Fetch it’s also hard to
measure upload progress.
If you need those things in your app, the Axios JavaScript library might be a better t.
How to cancel a fetch request
For a few years after fetch was introduced, there was no way to abort a request once
opened.
fetch('./file.json', { signal })
You can set a timeout that res an abort event 5 seconds after the fetch request has started, to
cancel it:
Conveniently, if the fetch already returned, calling abort() won’t cause any error.
When an abort signal occurs, fetch will reject the promise with a DOMException named
AbortError :
fetch('./file.json', { signal })
.then(response => response.text())
.then(text => console.log(text))
.catch(err => {
if (err.name === 'AbortError') {
console.error('Fetch aborted')
} else {
console.error('Another error', err)
}
})
THE CHANNEL MESSAGING
API
The Channel Messaging API allows iframes and workers to communicate
with the main document thread, by passing messages
How it works
How it works
Calling new MessageChannel() a message channel is initialized.
port1
port2
Those properties are a MessagePort object. port1 is the port used by the part that created
the channel, and port2 is the port used by the channel receiver (by the way, the channel is
bidirectional, so the receiver can send back messages as well).
Sending the message is done through the
otherWindow.postMessage()
A message can be a JavaScript value like strings, numbers, and some data structures are
supported, namely
File
Blob
FileList
ArrayBu er
“Origin” is a URI (e.g. https://example.org ). You can use '*' to allow less strict checking,
or specify a domain, or specify '/' to set a same-domain target, without needing to specify
which domain is it.
The other browsing context listens for the message using MessagePort.onmessage , and it
can respond back by using MessagePort.postMessage .
The main document de nes an iframe and a span where we’ll print a message that’s sent
from the iframe document. As soon as the iframe document is loaded, we send it a
message on the channel we created.
<!DOCTYPE html>
<html>
<body>
<iframe src="iframe.html" width="500" height="500"></iframe>
<span></span>
</body>
<script>
const channel = new MessageChannel()
const display = document.querySelector('span')
const iframe = document.querySelector('iframe')
iframe.addEventListener('load', () => {
iframe.contentWindow.postMessage('Hey', '*', [channel.port2])
}, false)
<!DOCTYPE html>
<html>
<script>
window.addEventListener("message", (event) => {
if (event.origin !== "http://example.org:8080") {
return
}
// process
As you can see we don’t even need to initialize a channel, because the window.onmessage
handler is automatically run when the message is received from the container page.
data : the object that’s been sent from the other window
origin : the origin URI of the window that sent the message
source : the window object that sent the message
e.ports[0] is the way we reference port2 in the iframe, because ports is an array, and
the port was added as the rst element.
An example with a Service Worker
A Service Worker is an event-driven worker, a JavaScript le associated with web page. Check
out the Service Workers guide to know more about them.
What’s important to know is that Service Workers are isolated from the main thread, and we
must communicate with them using messages.
This is how a script attached to the main document will handle sending messages to the Service
Worker:
In the Service Worker code, we add an event listener for the message event:
More on the inner workings of Service Workers in the Service Workers guide.
CACHE API
The Cache API is part of the Service Worker speci cation, and is a great way
to have more power on resources caching.
Introduction
Detect if the Cache API is available
Initialize a cache
Add items to the cache
cache.add()
cache.addAll()
Manually fetch and add
Introduction
The Cache API is part of the Service Worker speci cation, and is a great way to have more
power on resources caching.
It allows you to cache URL-addressable resources, which means assets, web pages, HTTP APIs
responses.
It’s not meant to cache individual chunks of data, which is the task of the IndexedDB API.
It’s currently available in Chrome >= 40, Firefox >=39, and Opera >= 27.
Internet Explorer and Safari still do not support it, while Edge has support for that is in preview
release.
Mobile support is good on Android, supported on the Android Webview and in Chrome for
Android, while on iOS it’s only available to Opera Mobile and Firefox Mobile users.
Detect if the Cache API is available
The Cache API is exposed through the caches object. To detect if the API is implemented in
the browser, just check for its existance using:
if ('caches' in window) {
//ok
}
Initialize a cache
Use the caches.open API, which returns a promise with a cache object ready to be used:
caches.open('mycache').then((cache) => {
// you can start using the cache
})
cache.add()
add accepts a single URL, and when called it fetches the resource and caches it.
caches.open('mycache').then((cache) => {
cache.add('/api/todos')
})
To allow more control on the fetch, instead of a string you can pass a Request object, part of
the Fetch API speci cation:
caches.open('mycache').then((cache) => {
const options = {
// the options
}
cache.add(new Request('/api/todos', options))
})
cache.addAll()
addAll accepts an array, and returns a promise when all the resources have been cached.
caches.open('mycache').then((cache) => {
cache.addAll(['/api/todos', '/api/todos/today']).then(() => {
//all requests were cached
})
})
The Cache API o ers a more granular control on this via cache.put() . You are responsible
for fetching the resource and then telling the Cache API to store a response:
caches.open('mycache').then((cache) => {
cache.match('/api/todos').then((res) => {
//res is the Response Object
})
})
caches.keys().then((keys) => {
// keys is an array with the list of keys
})
caches.open('mycache').then((cache) => {
cache.delete('/api/todos')
})
Delete a cache
The caches.delete() method accepts a cache identi er and when executed it wipes the
cache and its cached items from the system.
caches.delete('mycache').then(() => {
// deleted successfully
})
THE PUSH API
The Push API allows a web app to receive messages pushed by a server,
even if the web app is not currently open in the browser or not running on
the device.
Overview
Getting the user’s permission
IE, Edge do not support it yet, and Safari has its own implementation
(https://developer.apple.com/noti cations/safari-push-noti cations/) .
Since Chrome and Firefox support it, approximately 60% of the users browsing on the desktop
have access to it, so it’s quite safe to use.
This lets you deliver noti cations and content updates, giving you the ability to have a more
engaged audience.
This is huge because one of the missing pillars of the mobile web, compared to native apps,
was the ability to receive noti cations, along with o ine support.
How it works
Overview
When a user visits your web app, you can trigger a panel asking permission to send updates. A
Service Worker is installed, and operating in the background listens for a Push Event.
Push and Notifications are a separate concept and API, sometimes mixed because of the push
notifications term used in iOS. Basically, the Notifications API is invoked when a push event is
received using the Push API.
Your server sends the noti cation to the client, and the Service Worker, if given permission,
receives a push event. The Service Worker reacts to this event by triggering a notification.
Many sites implement this panel badly, showing it on the first page load. The user is not yet
convinced your content is good, and they will deny the permission. Do it wisely.
if (!('serviceWorker' in navigator)) {
// Service Workers are not supported. Return
return
}
Check if the Push API is supported
if (!('PushManager' in window)) {
// The Push API is not supported. Return
return
}
window.addEventListener('load', () => {
navigator.serviceWorker.register('/worker.js')
.then((registration) => {
console.log('Service Worker registration completed with scope: ',
registration.scope)
}, (err) => {
console.log('Service Worker registration failed', err)
})
})
To know more about how Service Workers work in details, check out the Service Workers guide.
The API to do this changed over time, and it went from accepting a callback function as a
parameter to returning a Promise, breaking the backward and forward compatibility, and we
need to do both as we don’t know which approach is implemented by the user’s browser.
The permissionResult value is a string, that can have the value of: - granted - default -
denied
If the user clicks Block, you won’t be able to ask for the user’s permission any more,
unless they manually go and unblock the site in an advanced settings panel in the browser
(very unlikely to happen).
window.addEventListener('load', () => {
navigator.serviceWorker.register('/worker.js')
.then((registration) => {
askPermission().then(() => {
const options = {
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(APP_SERVER_KEY)
}
return registration.pushManager.subscribe(options)
}).then((pushSubscription) => {
// we got the pushSubscription object
}
}, (err) => {
console.log('Service Worker registration failed', err)
})
})
APP_SERVER_KEY is a string - called Application Server Key or VAPID key - that identi es the
applications’s public key, part of a public / private key pair.
It will be used as part of the validation that for security reasons occurs to make sure you (and
only you, not someone else) can send a push message back to the user.
sendToServer(subscription)
Server-side, the /api/subscription endpoint receives the POST request and can store the
subscription information into its storage.
We initialize Express.js:
This utility function makes sure the request is valid, has a body and an endpoint property,
otherwise it returns an error to the client:
The next utility function saves the subscription to the database, returning a promise resolved
when the insertion completed (or failed). The insertToDatabase function is a placeholder,
we’re not going into those details here:
resolve(id)
})
})
}
We use those functions in the POST request handler below. We check if the request is valid,
then we save the request and then we return a data.success: true response back to the
client, or an error:
saveSubscriptionToDatabase(req, res.body)
.then((subscriptionId) => {
res.setHeader('Content-Type', 'application/json')
res.send(JSON.stringify({ data: { success: true } }))
})
.catch((err) => {
res.status(500)
res.setHeader('Content-Type', 'application/json')
res.send(JSON.stringify({
error: {
id: 'unable-to-save-subscription',
message: 'Subscription received but failed to save it'
}
}))
})
})
app.listen(3000, () => {
console.log('App listening on port 3000')
})
const vapidKeys = {
publicKey: PUBLIC_KEY,
privateKey: PRIVATE_KEY
}
webpush.setVapidDetails(
'mailto:[email protected]',
vapidKeys.publicKey,
vapidKeys.privateKey
)
Then we set up a triggerPush() method, responsible for sending the push event to a client.
It just calls webpush.sendNotification() and catches any error. If the return error HTTP
status code is 410 (https://developer.mozilla.org/docs/Web/HTTP/Status/410) , which means
gone, we delete that subscriber from the database.
We don’t implement getting the subscriptions from the database, but we leave it as a stub:
The meat of the code is the callback of the POST request to the /api/push endpoint:
What the above code does is: it gets all the subscriptions from the database, then it iterates on
them, and it calls the triggerPush() function we explained before.
Once the subscriptions are done, we return a successful JSON response, unless an error
occurred and we return a 500 error.
It’s a normal JavaScript event listener, on the push event, which runs inside a Service Worker:
Displaying a notification
Here we intersect a bit with the Noti cations API, but for a good reason, as one of the main use
cases of the Push API is to display noti cations.
Inside our push event listener in the Service Worker, we need to display the noti cation to the
user, and to tell the event to wait until the browser has shown it before the function can
terminate. We extend the event lifetime until the browser has done displaying the noti cation
(until the promise has been resolved), otherwise the Service Worker could be stopped in the
middle of your processing:
Add a body
Add an image
Those messages are consistent and native, which means that the receiving person is used to
the UI and UX of them, being system-wide and not speci c to your site.
In combination with the Push API this technology can be a successful way to increase user
engagement and to enhance the capabilities of your app.
The Notifications API interacts heavily with Service Workers, as they are required for Push
Notifications. You can use the Notifications API without Push, but its use cases are limited.
n.close()
Permissions
To show a noti cation to the user, you must have permission to do so.
Notification.requestPermission()
in this very simple form, and it will show a permission permission granting panel - unless
permission was already granted before.
To do something when the user interacts (allows or denies), you can attach a processing
function to it:
Notification.requestPermission((permission) => {
process(permission)
}).then((permission) => {
process(permission)
})
See how we pass in a callback and also we expect a promise. This is because of di erent
implementations of Notification.requestPermission() made in the past, which we now
must support as we don’t know in advance which version is running in the browser. So to keep
things in a single location I extracted the permission processing in the process() function.
In both cases that function is passed a permission string which can have one of these values:
Create a notification
The Notification object exposed by the window object in the browser allows you to create
a noti cation and to customize its appearance.
Here is the simplest example, that works after you asked for permissions:
Notification.requestPermission()
new Notification('Hey')
Add a body
First, you can add a body, which is usually shown as a single line:
new Notification('Hey', {
body: 'You should see this!'
})
Add an image
You can add an icon property:
new Notification('Hey', {
body: 'You should see this!',
icon: '/user/themes/writesoftware/favicon.ico'
})
More customization options, with platform-specific properties, can be found at
https://developer.mozilla.org/docs/Web/API/Notification
Close a notification
You might want to close a noti cation once you opened it.
n.close()
or with a timeout:
setTimeout(n.close(), 1 * 1000)
INDEXEDDB
IndexedDB is one of the storage capabilities introduced into browsers over
the years. Here's an introduction to IndexedDB, the Database of the Web
supported by all modern Browsers
Introduction to IndexedDB
Create an IndexedDB Database
Delete a database
Delete an object store
To delete data in an object store use a transaction
Introduction to IndexedDB
IndexedDB is one of the storage capabilities introduced into browsers over the years. It’s a
key/value store (a noSQL database) considered to be the definitive solution for storing data
in browsers.
It’s an asynchronous API, which means that performing costly operations won’t block the UI
thread providing a sloppy experience to users. It can store an inde nite amount of data,
although once over a certain threshold the user is prompted to give the site higher limits.
Local/session storage have the disadvantage of being capped at a small (and inconsistent) size,
with browsers implementation o ering from 2MB to 10MB of space per site.
In the past we also had Web SQL, a wrapper around SQLite, but now this is deprecated and
unsupported on some modern browsers, it’s never been a recognized standard and so it
should not be used, although 83% of users have this technology on their devices according to
Can I Use (http://caniuse.com/#feat=sql-storage) .
While you can technically create multiple databases per site, you generally create one single
database, and inside that database you can create multiple object stores.
A database is private to a domain, so any other site cannot access another website
IndexedDB stores.
strings
numbers
objects
arrays
dates
For example you might have a store that contains posts, another that contains comments.
A store contains a number of items which have a unique key, which represents the way by
which an object can be identi ed.
You can alter those stores using transactions, by performing add, edit and delete operations,
and iterating over the items they contain.
Since the advent of Promises in ES2015, and the subsequent move of APIs to using promises,
the IndexedDB API seems a bit old school.
While there’s nothing wrong in it, in all the examples that I’ll explain I’ll use the IndexedDB
Promised Library (https://github.com/jakearchibald/idb) by Jake Archibald, which is a tiny layer
on top of the IndexedDB API to make it easier to use.
This library is also used on all the examples on the Google Developers website regarding
IndexedDB
And then include it in your page, either using Webpack or Browserify or any other build system,
or simply:
<script src="./node_modules/idb/lib/idb.js"></script>
Before using the IndexedDB API, always make sure you check for support in the browser, even
though it’s widely available, you never know which browser the user is using:
(() => {
'use strict'
if (!('indexedDB' in window)) {
console.warn('IndexedDB not supported')
return
}
//...IndexedDB code
})()
How to create a database
Using idb.open() :
The rst 2 parameters are self-explanatory. The third param, which is optional, is a callback
called only if the version number is higher than the current installed database version. In
the callback function body you can upgrade the structure (stores and indexes) of the db.
We use the name upgradeDB for the callback to identify this is the time to update the
database if needed.
If you installed a previous version, the callback allows you to perform a the migration:
The index gives you a way to retrieve a value later by that speci c key, and it must be unique
(every item must have a di erent key)
A key can be set to auto increment, so you don’t need to keep track of it on the client code. If
you don’t specify a key, IndexedDB will create it transparently for us:
but you can specify a speci c eld of object value to auto increment as well:
upgradeDb.createObjectStore('notes', {
keyPath: 'id',
autoIncrement: true
})
As a general rule, use auto increment if your values do not contain a unique key already (for
example, an email address for users).
Indexes
An index is a way to retrieve data from the object store. It’s de ned along with the database
creation in the idb.open() callback in this way:
The unique option determines if the index value should be unique, and no duplicate values
are allowed to be added.
if (!upgradeDb.objectStoreNames.contains('store3')) {
upgradeDb.createObjectStore('store3')
}
Delete a database
idb.delete('mydb')
.then(() => console.log('done'))
dbPromise.then((db) => {
const tx = db.transaction('store', 'readwrite')
const store = tx.objectStore('store')
store.delete(key)
return tx.complete
})
.then(() => {
console.log('Item deleted')
})
When using put , the value is the rst argument, the key is the second. This is because if you
specify keyPath when creating the object store, you don’t need to enter the key name on
every put() request, you can just write the value.
To add items later down the road, you need to create a transaction, that ensures database
integrity (if an operation fails, all the operations in the transaction are rolled back and the state
goes back to a known state).
For that, use a reference to the dbPromise object we got when calling idb.open() , and run:
dbPromise.then((db) => {
const val = 'hey!'
const key = 'Hello again'
The IndexedDB API offers the add() method as well, but since put() allows us to both add
and update, it’s simpler to just use it.
Getting items from a store
dbPromise.then((db) => {
const tx = db.transaction('store', 'readonly')
const store = tx.objectStore('store')
return store.openCursor()
})
.then(function logItems(cursor) {
if (!cursor) { return }
console.log('cursor is at: ', cursor.key)
for (const field in cursor.value) {
console.log(cursor.value[field])
}
return cursor.continue().then(logItems)
})
.then(() => {
console.log('done!')
})
let range
if (lower !== '' && upper !== '') {
range = IDBKeyRange.bound(lower, upper)
} else if (lower === '') {
range = IDBKeyRange.upperBound(upper)
} else {
range = IDBKeyRange.lowerBound(lower)
}
dbPromise.then((db) => {
const tx = db.transaction(['dogs'], 'readonly')
const store = tx.objectStore('dogs')
const index = store.index('age')
return index.openCursor(range)
})
.then(function showRange(cursor) {
if (!cursor) { return }
console.log('cursor is at:', cursor.key)
for (const field in cursor.value) {
console.log(cursor.value[field])
}
return cursor.continue().then(showRange)
})
.then(() => {
console.log('done!')
})
}
searchDogsBetweenAges(3, 10)
THE SELECTORS API
Access DOM elements using querySelector and querySelectorAll. They
accept any CSS selector, so you are no longer limited by selecting elements
by `id`
Introduction
The Selectors API
Basic jQuery to DOM API examples
Select by id
Select by class
Select by tag name
Introduction
jQuery and other DOM libraries got a huge popularity boost in the past, among with other
features they provided, thanks to an easy way to select elements on a page.
Traditionally browsers provided one single way to select a DOM element, and that was by its
id attribute, with getElementById() (https://developer.mozilla.org/en-
US/docs/Web/API/Document/getElementById) , a method o ered by the document object.
document.querySelector()
document.querySelectorAll()
They accept any CSS selector, so you are no longer limited by selecting elements by id .
document.querySelector('#test')
document.querySelector('.my-class')
document.querySelector('#test .my-class')
document.querySelector('a:hover')
Select by id
$('#test')
document.querySelector('#test')
Select by class
$('.test')
document.querySelectorAll('.test')
$('div, span')
document.querySelectorAll('div, span')
$('[data-example="test"]')
document.querySelectorAll('[data-example="test"]')
$(':nth-child(4n)')
document.querySelectorAll(':nth-child(4n)')
$('#test li')
document.querySelectorAll('#test li')
THE WEB STORAGE API
The Web Storage API provides a way to store data in the browser. It de nes
two storage mechanisms which are very important: Session Storage and
Local Storage, part of the set of storage options available on the Web
Platform
Introduction
How to access the storage
Methods
setItem(key, value)
getItem(key)
removeItem(key)
key(n)
clear()
Desktop
Mobile
Going over quota
Developer Tools
Chrome
Firefox
Safari
Introduction
The Web Storage API de nes two storage mechanisms which are very important: Session
Storage and Local Storage.
They are part of the set of storage options available on the Web Platform, which includes:
Cookies
IndexedDB
The Cache API
Application Cache is deprecated, and Web SQL is not implemented in Firefox, Edge and IE.
Both Session Storage and Local Storage provide a private area for your data. Any data you
store cannot be accessed by other websites.
Session Storage maintains the data stored into it for the duration of the page session. If
multiple windows or tabs visit the same site, they will have two di erent Session Storage
instances.
When a tab/window is closed, the Session Storage for that particular tab/window is cleared.
Session storage is meant to allow the scenario of handling different processes happening on
the same site independently, something not possible with cookies for example, which are
shared in all sessions.
Local Storage instead persists the data until it’s explicitly removed, either by you or by the user.
It’s never cleaned up automatically, and it’s shared in all the sessions that access a site.
Both Local Storage and Session Storage are protocol specific: data stored when the page is
accessed using http is not available when the page is served with https , and vice versa.
Web Storage is only accessible in the browser. It’s not sent to the server like cookies do.
How to access the storage
Both Local and Session Storage are available on the window object, so you can access them
using sessionStorage and localStorage .
Their set of properties and methods is exactly the same, because they return the same object,
a Storage (https://developer.mozilla.org/en-US/docs/Web/API/Storage) object.
The Storage Object has a single property, length , which is the number of data items stored
into it.
Methods
setItem(key, value)
setItem() adds an item to the storage. Accepts a string as key, and a string as a value:
localStorage.setItem('username', 'flaviocopes')
localStorage.setItem('id', '123')
If you pass any value that’s not a string, it will be converted to string:
getItem(key)
getItem() is the way to retrieve a string value from the storage, by using the key string that
was used to store it:
localStorage.getItem('username') // 'flaviocopes'
localStorage.setItem('id') // '123'
removeItem(key)
removeItem() removes the item identi ed by key from the storage, returning nothing (an
undefined value):
localStorage.removeItem('id')
key(n)
Every item you store has an index number. So the rst time you use setItem() , that item can
be referenced using key(0) . The next with key(1) and so on.
If you reference a number that does not point to a storage item, it returns null .
Every time you remove an item with removeItem(), the index consolidates:
localStorage.setItem('a', 'a')
localStorage.setItem('b', 'b')
localStorage.key(0) //"a"
localStorage.key(1) //"b"
localStorage.removeItem('b')
localStorage.key(1) //null
localStorage.setItem('b', 'b')
localStorage.setItem('c', 'c')
localStorage.key(1) //"b"
localStorage.removeItem('b')
localStorage.key(1) //"c"
clear()
clear() removes everything from the storage object you are manipulating:
localStorage.setItem('a', 'a')
localStorage.setItem('b', 'b')
localStorage.length //2
localStorage.clear()
localStorage.length //0
The amount of storage available on Web might di er by storage type (local or session),
browser, and by device type. A research
(https://www.html5rocks.com/en/tutorials/o ine/quota-research/) by html5rocks.com points
out those limits:
Desktop
Chrome, IE, Firefox: 10MB
Safari: 5MB for local storage, unlimited session storage
Mobile
Chrome, Firefox: 10MB
iOS Safari and WebView: 5MB for local storage, session storage unlimited unless in iOS6
and iOS7 where it’s 5MB
Android Browser: 2MB local storage, unlimited session storage
try {
localStorage.setItem('key', 'value')
} catch(domException) {
if (['QuotaExceededError',
'NS_ERROR_DOM_QUOTA_REACHED'].includes(domException.name)) {
// handle quota limit exceeded error
}
}
Developer Tools
The DevTools of the major browsers all o er a nice interface to inspect and manipulate the
data stored in the Local and Session Storage.
Chrome
Firefox
Safari
COOKIES
Cookies are a fundamental part of the Web, as they allow sessions and in
general to recognize the users during the navigation
Introduction
Restrictions of cookies
Set cookies
Set a cookie expiration date
Set a cookie path
Set a cookie domain
Cookie Security
Secure
HttpOnly
SameSite
Chrome
Firefox
Safari
Alternatives to cookies
Introduction
By using Cookies we can exchange information between the server and the browser to provide
a way to customize a user session, and for servers to recognize the user between requests.
HTTP is stateless, which means all request origins to a server are exactly the same and a server
cannot determine if a request comes from a client that already did a request before, or it’s a
new one.
Cookies are sent by the browser to the server when an HTTP request starts, and they are sent
back from the server, which can edit their content.
In the past cookies were used to store various types of data, since there was no alternative. But
nowadays with the Web Storage API (Local Storage and Session Storage) and IndexedDB, we
have much better alternatives.
Especially because cookies have a very low limit in the data they can hold, since they are sent
back-and-forth for every HTTP request to our server - including requests for assets like images
or CSS / JavaScript les.
Cookies have a long history, they had their rst version in 1994, and over time they were
standardized in multiple RFC revisions.
RFC stands for Request for Comments, the way standards are defined by the Internet
Engineering Task Force (IETF), the entity responsible for setting standards for the Internet
The latest speci cation for Cookies is de ned in the RFC 6265
(https://tools.ietf.org/html/rfc6265) , which is dated 2011.
Restrictions of cookies
Cookies can only store 4KB of data
Cookies are private to the domain. A site can only read the cookies it set, not other
domains cookies
You can have up to 20 limits of cookies per domain (but the exact number depends on the
speci c browser implementation)
Cookies are limited in their total number (but the exact number depends on the speci c
browser implementation). If this number is exceeded, new cookies replace the older ones.
In the client side, cookies are exposed by the document object as document.cookie
Set cookies
The simplest example to set a cookie is:
document.cookie = 'foo=bar'
This will add a new cookie to the existing ones (it does not overwrite existing cookies)
If you don’t set a path, it defaults to the current document location. This means that to apply a
global cookie from an inner page, you need to specify path="/" .
If not set, it defaults to the host portion even if using a subdomain (if on
subdomain.mydomain.com, by default it’s set to mydomain.com). Domain cookies are included
in subdomains.
Cookie Security
Secure
Adding the Secure parameter makes sure the cookie can only be transmitted securely over
HTTPS, and it will not be sent over unencrypted HTTP connections:
document.cookie = 'foo=bar; Secure;'
Note that this does not make cookies secure in any way - always avoid adding sensitive
information to cookies
HttpOnly
One useful parameter is HttpOnly , which makes cookies inaccessible via the
document.cookie API, so they are only editable by the server:
SameSite
SameSite , still experimental and only supported by Chrome and Firefox
(https://caniuse.com/#feat=same-site-cookie-attribute, lets servers require that a cookie is not
sent on cross-site requests, but only on resources that have the cookie domain as the origin,
which should be a great help towards reducing the risk of CSRF (Cross Site Request Forgery)
attacks.
document.cookie = 'foo=bar2'
Similar to updating the value, to update the expiration date, reassign the value with a new
expires or max-age property:
Just remember to also add any additional parameters you added in the rst place, like path or
domain .
Delete a cookie
To delete a cookie, unset its value and pass a date in the past:
(and again, with all the parameters you used to set it)
This will return a string with all the cookies set for the page, semicolon separated:
//ES7
if (document.cookie.split(';').filter((item) => {
return item.includes('foo=')
}).length) {
//foo exists
}
Abstractions libraries
There are a number of di erent libraries that will provide a frendlier API to manage cookies.
One of them is https://github.com/js-cookie/js-cookie, which supports up to IE7, and has a lot of
stars on GitHub (which is always good).
Cookies.set('name', 'value')
Cookies.set('name', 'value', {
expires: 7,
path: '',
domain: 'subdomain.site.com',
secure: true
})
//JSON
Cookies.set('name', { foo: 'bar' })
Cookies.getJSON('name') // => { foo: 'bar' }
It all comes down to adding more kilobytes to download for each user, so it’s your choice.
PHP has $_COOKIE Go has cookies facilities in the net/http standard library
and so on.
When using Express.js, you can create cookies using the res.cookie
(http://expressjs.com/en/api.html#res.cookie) API:
req.cookies.foo //bar
req.cookies.foo1 //bar1
they will be available in the req.signedCookies object instead. Signed cookies will be
completely unreadable in the frontend, but transparently encoded/decoded on the server side.
Chrome
Firefox
Safari
Alternatives to cookies
Are cookies the only way to build authentication and sessions on the Web?
No! There is a technology that recently got popular, called JSON Web Tokens (JWT), which is a
Token-based Authentication.
Introduction
Access the History API
Navigate the history
Add an entry to the history
Modify history entries
Access the current history entry state
The onpopstate event
Introduction
The History API lets you interact with the browser history, trigger the browser navigation
methods and change the address bar content.
It’s especially useful in combination with modern Single Page Applications, on which you never
make a server-side request for new pages, but instead the page is always the same: just the
internal content changes.
A modern JavaScript application running in the browser that does not interact with the
History API, either explicitly or at the framework level, is going to be a poor experience to the
user, since the back and forward buttons break.
Also, when navigating the app, the view changes but the address bar does not.
And also the reload button breaks: reloading the page, since there is no deep linking, is going
to make the browser show a di erent page
The History API was introduced in HTML5 and is now supported by all modern browsers
(https://caniuse.com/#feat=history) . IE supports it since version 10, and if you need to support
IE9 and older, use the History.js library (https://github.com/browserstate/history.js/) .
history.back()
this goes to the previous entry in the session history. You can forward to the next page using
history.forward()
This is exactly just like using the browser back and forward buttons.
go() lets you navigate back or forward multiple levels deep. For example
To know how many entries there are in the history, you can call
history.length
The rst is an object which can contain anything (there is a size limit however, and the object
needs to be serializable).
The second parameter is currently unused by major browsers, so you generally pass an empty
string.
The third parameter is a URL associated to the new state. Note that the URL needs to belong to
the same origin domain of the current URL.
Calling this won’t change the content of the page, and does not cause any browser action like
changing window.location would.
history.back()
the browser goes straight to /posts , since /post/first was replaced by /post/second
history.state
returns the current state object (the rst parameter passed to pushState or
replaceState ).
will log the new state object (the rst parameter passed to pushState or replaceState )
every time you call history.back() , history.forward() or history.go() .
EFFICIENTLY LOAD
JAVASCRIPT WITH DEFER
AND ASYNC
When loading a script on an HTML page, you need to be careful not to harm
the loading performance of the page. Depending on where and how you
add your scripts to an HTML page will in uence the loading time
Blocking parsing
Blocking rendering
domInteractive
Keeping things in order
TL;DR, tell me what’s the best
When loading a script on an HTML page, you need to be careful not to harm the loading
performance of the page.
whenever the HTML parser nds this line, a request will be made to fetch the script, and the
script is executed.
Once this process is done, the parsing can resume, and the rest of the HTML can be analyzed.
As you can imagine, this operation can have a huge impact on the loading time of the page.
If the script takes a little longer to load than expected, for example if the network is a bit slow
or if you’re on a mobile device and the connection is a bit sloppy, the visitor will likely see a
blank page until the script is loaded and executed.
<html>
<head>
<title>Title</title>
<script src="script.js"></script>
</head>
<body>
...
</body>
</html>
As I told earlier, when the parser nds this line, it goes to fetch the script and executes it. Then,
after it’s done with this task, it goes on to parse the body.
This is bad because there is a lot of delay introduced. A very common solution to this issue is to
put the script tag to the bottom of the page, just before the closing </body> tag.
Doing so, the script is loaded and executed after all the page is already parsed and loaded,
which is a huge improvement over the head alternative.
This is the best thing you can do, if you need to support older browsers that do not support two
relatively recent features of HTML: async and defer .
if you specify both, async takes precedence on modern browsers, while older browsers that
support defer but not async will fallback to defer .
These attributes make only sense when using the script in the head portion of the page, and
they are useless if you put the script in the body footer like we saw above.
Performance comparison
The parsing is paused until the script is fetched, and executed. Once this is done, parsing
resumes.
The script is fetched asynchronously, and when it’s ready the HTML parsing is paused to
execute the script, then it’s resumed.
The script is fetched asynchronously, and it’s executed only after the HTML parsing is done.
Parsing nishes just like when we put the script at the end of the body tag, but overall the
script execution nishes well before, because the script has been downloaded in parallel with
the HTML parsing.
Blocking rendering
Neither async nor defer guarantee anything on blocking rendering. This is up to you and
your script (for example, making sure your scripts run after the onLoad ) event.
domInteractive
Scripts marked defer are executed right after the domInteractive event, which happens
after the HTML is loaded, parsed and the DOM is built.
CSS and images at this point are still to be parsed and loaded.
Once this is done, the browser will emit the domComplete event, and then onLoad .
Unless you are ne with delaying the rst render of the page, making sure that when the page
is parsed the JavaScript you want is already executed.
WEBP
WebP is an Open Source image format developed at Google, which
promises to generate images smaller in size compared to JPG and PNG
formats, while generating better looking images
Introduction
How much smaller?
Generating WebP images
Browsers support
How can you use WebP today?
Introduction
WebP is an Open Source image format developed at Google, which promises to generate
images smaller in size compared to JPG and PNG formats, while generating better looking
images.
And, using WebP you can set the quality ratio of your images, so you decide if you want to get
better quality or a smaller size (like it happens in JPG images).
So WebP can do all GIF, JPG and PNG images can do, in a single format, and generate
smaller images. Sounds like a deal.
If you want to compare how images look in the various formats, here’s a great gallery by
Google (https://developers.google.com/speed/webp/gallery) .
WebP is not new, it’s been around for several years now.
WebP achieves overall higher compression than either JPEG or JPEG 2000. Gains in file size
minimization are particularly high for smaller images which are the most common ones found on
the web.
You should experiment with the kind of images you intend to serve, and form your opinion
based on that.
In my tests, lossless compression compared to PNG generates WebP images 50% smaller. PNG
reaches that le sizes only when using lossy compression.
Command-line tools also exist to convert images to WebP directly. Google provides a set of
tools (https://developers.google.com/speed/webp/download) for this.
cwebp is the main command line utility to convert any image to .webp , use it with
Chrome
Opera
Opera Mini
UC Browser
Samsung Internet
However, only Chrome for Desktop and Opera 19+ implement all the features of WebP, which
are:
lossy compression
lossless compression
transparency
animation
Other browsers only implement a subset, and Firefox, Safari, Edge and IE do not support WebP
at all, and there are no signs of WebP being implemented any time soon in those browsers.
But Chrome alone is a good portion of the web market, so if we can serve those users an
optimized image, to speed up serving them and consume less bandwidth, it’s great. But check if
it actually reduces your images size.
Check with your JPG/PNG image optimization tooling results, and see if adding an additional
layer of complexity introduced by WebP is useful or not.
You can use a server-level mechanism that serves WebP images instead of JPG and PNG when
the HTTP_ACCEPT request header contains image/webp .
The rst is the most convenient, as completely transparent to you and to your web pages.
The <picture> tag is generally used for responsive images, but we can use it for WebP too, as
this tutorial from HTML5 Rocks (https://www.html5rocks.com/en/tutorials/responsive/picture-
element/#toc- le-type) explains.
You can specify a list of images, and they will be used in order, so in the next example,
browsers that support WebP will use the rst image, and fallback to JPG if not:
<picture>
<source type="image/webp" srcset="image.webp">
<img src="image.jpg" alt="An image">
</picture>
SVG
SVG is an awesome and incredibly powerful image format. This tutorial gives
you an overview of SVG by explaining all you need to know in a simple way
Introduction
The advantages of SVG
Your rst SVG image
Using SVG
SVG Elements
text
circle
rect
line
path
textPath
polygon
g
Introduction
Despite being standardized in the early 2000s, SVG (a shorthand for Scalable Vector Graphics) is
a hot topic these days.
SVG has been penalized for quite a few years by the poor browser support (most notably IE).
I found this quote from a 2011 book: “at the time of writing, direct embedding of SVG into
HTML works only in the very newest browsers”. 7 years ago, this is now a thing of the past, and
we can use SVG images safely.
Today we can use SVG images safely, unless you have a lot of users with IE8 and below, or with
older Android devices. In this case, fallbacks exist.
Some part of the success of SVG is due to the variety of screen displays we must support, at
di erent resolutions and sizes. A perfect task for SVG.
Also, the rapid decline of Flash in the last few years led to a renewed interest in SVG, which is
great for a lot of things that Flash did in the past.
SVG is a vector image le format. This makes them very di erent than image format such as
PNG, GIF or JPG, which are raster image le formats.
Thanks to being de ned in XML, SVG images are much more flexible than JPG or PNG images,
and** we can use CSS and JavaScript to interact with them**. SVG images can even contain CSS
and JavaScript.
SVG images can render vector-style images a lot smaller than other formats, and are mainly
used on logos and illustrations. Another huge use case is icons. Once domain of Icon Fonts like
FontAwesome, now designers prefer using SVG images because they are smaller and they
allow to have multi-color icons.
SVG provides some image editing e ects, like masking and clipping, applying lters, and more.
SVG are just text, and as such it can be e ciently compressed using GZip.
Most of the times you won’t have to edit the SVG code, but you will use tools like Sketch or
Figma or any other vector graphics tool to create the image, and export it as SVG.
The current version of SVG is 1.1, and SVG 2.0 is under development.
Using SVG
SVG images can be displayed by the browser by including them in a img tag:
In addition, pretty uniquely, SVG they can be directly included in the HTML page:
<!DOCTYPE html>
<html>
<head>
<title>A page</title>
</head>
<body>
<svg width="10" height="10">
<rect x="0" y="0" width="10" height="10" fill="blue" />
</svg>
</body>
</html>
Please note that HTML5 and XHTML require a different syntax for inline SVG images. Luckily
XHTML is a thing of the past, as it was more complex than necessary, but it’s worth knowing in
case you still need to work on XHTML pages.
The ability to inline SVG in HTML makes this format a unicorn in the scene, as other images
can’t do this, and must be fetched by opening a separate request for each one.
SVG Elements
In the example above you saw the usage of the rect element. SVG has a lot of di erent
elements.
Coordinates start at 0,0 at the top-left of the drawing area, and extend from left to right for
x , from top to bottom for y .
The images you see reflect the code shown above. Using the Browser DevTools you can inspect
and change them.
text
The text element adds text. The text can be selected using the mouse. x and y de ne the
starting point of the text
<svg>
<text x="5" y="30">A nice rectangle</text>
</svg>
A nice rectangle
circle
De ne a circle. cx and cy are the center coordinates, and r is the radius. fill is a common
attribute and represents the gure color.
<svg>
<circle cx="50" cy="50" r="50" fill="#529fca" />
</svg>
rect
De nes a rectangle. x , y are the starting coordinates, width and height are self-
explanatory.
<svg>
<rect x="0" y="0" width="100" height="100" fill="#529fca" />
</svg>
line
x1 and y1 de ne the starting coordinates. x2 and y2 de ne the ending coordinates.
stroke is a common attribute and represents the line color.
<svg>
<line x1="0" y1="0" x2="100" y2="100" stroke="#529fca" />
</svg>
path
A path is a sequence of lines and curves. It’s the most powerful tool to draw using SVG, and as
such it’s the most complex.
d contains the directions commands. These commands start with the command name, and a
set of coordinates:
textPath
Adds a text along the shape of a path element.
Wo
w
su
ch
a
ni
ce
SV
G
tu
t
polygon
Draw any random polygon with polygon . `points represents a set of x, y coordinates the
polygon should link:
<svg>
<polygon points="9.9, 1.1, 3.3, 21.78, 19.8, 8.58, 0, 8.58, 16.5, 21.78" />
</svg>
g
Using the g element you can group multiple elements:
Generally “container” means the browser window, but a svg element can contain other svg
elements, in that case the container is the parent svg .
An important attribute is viewBox . It lets you de ne a new coordinates system inside the SVG
canvas.
By specifying a viewBox you can choose to only show a portion of this SVG. For example you
can start at point 0, 0 and only show a 100x100px canvas:
A great way to visualize this is to imagine Google Maps being a gigantic SVG image, and your
browser is a viewBox as big as the window size. When you move around, the viewBox changes
its starting point (x, y) coordinates, and when you resize the window, you change the width and
height of the viewBox.
<style>
.svg-background {
background-image: url(flag.svg);
height: 200px;
width: 300px;
}
</style>
<div class="svg-background"></div>
Using embed you have the option to get the SVG document from the parent document using
document.getElementById('my-svg-embed').getSVGDocument()
and from inside the SVG you can reference the parent document with:
window.parent.document
Inline SVG using a Data URL
You can use any of the above examples combined with Data URLs to inline the SVG in the
HTML:
.svg-background {
background-image: url("data:image/svg+xml;<DATA>");
}
Styling elements
Any SVG element can accept a style attribute, just like HTML tags. Not all CSS properties work
as you would expect, due to the SVG nature. For example to change the color of a text element,
use fill instead of color .
<svg>
<text x="5" y="30" style="fill: green">A nice text</text>
</svg>
<svg>
<text x="5" y="70" style="fill: green; font-family: Courier New">
A nice text
</text>
</svg>
You can use fill as an element attribute as well, as you saw before:
<svg>
<text x="5" y="70" fill="green">A nice text</text>
</svg>
CSS can target SVG elements like you would target HTML tags:
rect {
fill: red;
}
circle {
fill: blue;
}
but (⚠ depending on the browser implementation) they must be loaded from the same
domain (and protocol), due to the same-origin policy.
iframe needs to be explicitly sized, otherwise the content is cropped, while object and
embed resize to t their content.
If the SVG is loaded using a img tag, or through CSS as a background, independently of the
origin:
In details
Supports animations ✅ ✅ ✅
If you want to do any interaction with the SVG with your scripts, it must be loaded inline
in the HTML.
Loading an SVG in an img , object or embed works if you don’t need to interact with it, just
show it in the page, and it’s especially convenient if you reuse SVG images in di erent pages, or
the SVG size is quite big.
<svg>
<style>
<![CDATA[
#my-rect { fill: blue; }
]]>
</style>
<rect id="my-rect" x="0" y="0" width="10" height="10" />
</svg>
<svg>
<script>
<![CDATA[
window.addEventListener("load", () => {
//...
}, false)
]]>
</script>
<rect x="0" y="0" width="10" height="10" fill="blue" />
</svg>
or you can avoid adding an event listener if you put the JS at the end of the other SVG code, to
make sure the JavaScript runs when the SVG is present in the page:
<svg>
<rect x="0" y="0" width="10" height="10" fill="blue" />
<script>
<![CDATA[
//...
]]>
</script>
</svg>
SVG elements, just like html tags, can have id and class attributes, so we can use the
Selectors API to reference them:
<svg>
<rect x="0" y="0" width="10" height="10" fill="blue"
id="my-rect" class="a-rect" />
<script>
<![CDATA[
console.log(document.getElementsByTagName('rect'))
console.log(document.getElementById('my-rect'))
console.log(document.querySelector('.a-rect'))
console.log(document.querySelectorAll('.a-rect'))
]]>
</script>
</svg>
document.getElementById("my-svg-rect").setAttribute("fill", "black")
SVG attributes can be easily overwritten in CSS, and they have a lower priority over CSS. They
do not behave like inline CSS, which has higher priority.
<style>
#my-rect {
fill: red
}
</style>
<svg>
<rect x="0" y="0" width="10" height="10" fill="blue"
id="my-rect" />
</svg>
it has the same scaling issues as pixel-based PNG, JPG and GIF image formats
it makes it impossible to edit a Canvas image using CSS or JavaScript like you can do with
SVG
SVG Symbols
Symbols let you de ne an SVG image once, and reuse it in multiple places. This is a great help if
you need to reuse an image, and maybe just change a bit some of its properties.
<svg class="hidden">
<symbol id="rectangle" viewBox="0 0 20 20">
<rect x="0" y="0" width="300" height="300" fill="rgb(255,159,0)" />
</symbol>
</svg>
<svg>
<use xlink:href="#rectangle" href="#rectangle" />
</svg>
<svg>
<use xlink:href="#rectangle" href="#rectangle" />
</svg>
If you want to style those 2 rectangles di erently, for example using a di erent color for each?
You can use CSS Variables.
<svg class="hidden">
<symbol id="rectangle" viewBox="0 0 20 20">
<rect x="0" y="0" width="300" height="300" fill="var(--color)" />
</symbol>
</svg>
<svg class="blue">
<use xlink:href="#rectangle" href="#rectangle" />
</svg>
<svg class="red">
<use xlink:href="#rectangle" href="#rectangle" />
</svg>
<style>
svg.red {
--color: red;
}
svg.blue {
--color: blue;
}
</style>
Validate an SVG
An SVG le, being XML, can be written in an invalid format, and some services or apps might
not accept an invalid SVG le.
sometimes as
This second form is XHTML. It can also be used with HTML5 (documents with <!DOCTYPE
html> ) but in this case the rst form is simpler.
You can still check for missing support using libraries like Modernizr (https://modernizr.com/) ,
and provide a fallback:
if (!Modernizr.svg) {
$(".my-svg").attr("src", "images/logo.png");
}
DATA URLS
A Data URL is a URI scheme that provides a way to inline data in a
document, and it's commonly used to embed images in HTML and CSS
Introduction
How does a Data URL look
Browser support
Security
Introduction
A Data URL is a URI scheme that provides a way to inline data in an HTML document.
Say you want to embed a small image. You could go the usual way, upload it to a folder and use
the img tag to make the browser reference it from the network:
Data URLs might save some time for small les, but for bigger les there are downsides in the
increased HTML le size, and they are especially a problem if the image reloads on all your
pages, as you can’t take advantage of the browser caching capabilities.
Also, an image encoded as Data URL is generally a bit bigger than the same image in binary
format.
They aren’t much practical if your images are frequently edited, since every time the image is
changed, it must be encoded again.
Text is usually transferred in plain text, while binary data is usually base64 encoded.
And here is a small version of the banner image of this article encoded in a link
Here is how a base64 encoded Data URL looks like. Notice it starts with
data:image/png;base64 :
And here is a small version of the banner image of this article base64 encoded in a link
(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJYAAABjCAMAAABOgAl6AAADAFBMVEX///
.
This site has a very nice Data URL generator: https://dopiaza.org/tools/datauri/index.php which
you can use to transform any image sitting in your desktop to a Data URL.
Data URLs can be used anywhere a URL can be used, as you saw you can use it for links, but it’s
also common to use them in CSS:
.main {
background-image url('data:image/png;base64,iVBORw0KGgoAA...');
}
Browser support
They are supported in all modern browsers (https://caniuse.com/#feat=datauri) .
Security
Data URLs can encode any kind of information, not just images, and so they come with their set
of security implications.
The data URI can be utilized to construct attack pages that attempt to obtain usernames and
passwords from unsuspecting web users. It can also be used to get around cross-site scripting
(XSS) restrictions, embedding the attack payload fully inside the address bar, and hosted via
URL shortening services rather than needing a full website that is controlled by a third party.
A JavaScript application running in the browser can usually only access HTTP resources on the
same domain (origin) that serves it.
Loading images or scripts/styles always works, but XHR and Fetch calls to another server will
fail, unless that server implements a way to allow that connection.
Also loading Web Fonts using @font-face has same-origin policy by default, and other less
popular things (like WebGL textures and drawImage resources loaded in the Canvas API).
One very important thing that needs CORS is ES Modules, recently introduced in modern
browsers.
If you don’t set up a CORS policy on the server that allows to serve 3rd part origins, the
request will fail.
Fetch example:
XHR example:
to a di erent domain
to a di erent subdomain
to a di erent port
to a di erent protocol
and it’s there for your security, to prevent malicious users to exploit the Web Platform.
But if you control both the server and the client, you have all the good reasons to allow them to
talk to each other.
How?
Browser support
Pretty good (basically all except IE<10):
Example with Express
If you are using Node.js and Express as a framework, use the CORS middleware package
(https://github.com/expressjs/cors) .
If you hit /without-cors with a fetch request from a di erent origin, it’s going to raise the
CORS issue.
All you need to do to make things work out is to require the cors package linked above, and
pass it in as a middleware function to an endpoint request handler:
I made a simple Glitch example. Here is the client (https:// avio-cors-client.glitch.me/) working,
and here’s its code: https://glitch.com/edit/#!/ avio-cors-client.
Note how the request that fails because it does not handle the CORS headings correctly is still
received, as you can see in the Network panel, where you nd the message the server sent:
As you can see in the Network panel, the request that passed has a response header access-
control-allow-origin: * :
You need to con gure the server to only allow one origin to serve, and block all the others.
Using the same cors Node library, here’s how you would do it:
const corsOptions = {
origin: 'https://yourdomain.com',
}
Preflight
There are some requests that are handled in a “simple” way. All GET requests belong to this
group.
POST requests are also in this group, if they satisfy the requirement of using a Content-Type of
application/x-www-form-urlencoded
multipart/form-data
text/plain
All other requests must run through a pre-approval phase, called pre ight. The browser does
this to determine if it has the permission to perform an action, by issuing an OPTIONS request.
A pre ight request contains a few headers that the server will use to check permissions
(irrelevant elds omitted):
OPTIONS /the/resource/you/request
Access-Control-Request-Method: POST
Access-Control-Request-Headers: origin, x-requested-with, accept
Origin: https://your-origin.com
The server will respond with something like this(irrelevant elds omitted):
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://your-origin.com
Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE
We checked for POST, but the server tells us we can also issue other HTTP request types for
that particular resource.
Following the Node.js Express example above, the server must also handle the OPTIONS
request:
Introduction
Browser support for Web Workers
Create a Web Worker
Communication with a Web Worker
Introduction
JavaScript is single threaded. Nothing can run in parallel at the same time.
This is great because we don’t need to worry about a whole set of issues that would happen
with concurrent programming.
With this limitation, JavaScript code is forced to be e cient from the start, otherwise the user
would have a bad experience. Expensive operations should be asynchronous to avoid blocking
the thread.
As the needs of JavaScript applications grew, this started to become a problem in some
scenarios.
Web Workers introduce the possibility of parallel execution inside the browser.
no access to the DOM: the Window object and the Document object
they can communicate back with the main JavaScript program using messaging
they need to be loaded from the same origin (domain, port and protocol)
they don’t work if you serve the page using the le protocol ( file:// )
The global scope of a Web Worker, instead of Window which is in the main thread, is a
WorkerGlobalScope (https://developer.mozilla.org/en-
US/docs/Web/API/WorkerGlobalScope) object.
main.js
worker.js
worker.js
main.js
worker.js
main.js
main.js
worker.js
self.addEventListener('message', (e) => {
console.log(e.data)
})
A Web Worker can be stopped using its terminate() method from the main thread, and
inside the worker itself using the global method close() :
main.js
worker.js
importScripts('../utils/file.js', './something.js')
It’s not an API speci c to animations, but that’s where it is used the most.
or
You can stop an animation by getting the timeout or interval reference, and clearing it:
let timer
//...
clearTimeout(timer)
The problem with this approach is that even though we specify this precision accurately, the
browser might be busy performing other operations, and our setTimeout calls might not make
it in time for the repaint, and it’s going to be delayed to the next cycle.
This is bad because we lose one frame, and in the next the animation is performed 2 times,
causing the eye to notice the clunky animation.
Check this example on Glitch of an animation built using of setTimeout() (https:// avio-
settimeout-animation.glitch.me/) .
let request
const performAnimation = () => {
request = requestAnimationFrame(performAnimation)
//animate something
}
requestAnimationFrame(performAnimation)
//...
Optimization
requestAnimationFrame() since its introduction was very CPU friendly, causing animations
to stop if the current window or tab is not visible.
Using requestAnimationFrame the browser can further optimize the resource consumption
and make the animations smoother.
Timeline examples
This is the perfect timeline if you use setTimeout or setInterval:
you have a set of paint (green) and render (purple) events, and your code is in the yellow box -
by the way, these are the colors used in the Browser DevTools as well to represent the timeline:
The illustration shows the perfect case. You have painting and rendering every 60ms, and your
animation happens in between, perfectly regular.
Notice how in each frame we call 4 animation steps, before any rendering happens, an this will
make the animation feel very choppy.
What if setTimeout cannot run on time due to other code blocking the event loop? We end up
with a missed frame:
What if an animation step takes a little bit more than you anticipate?
all the animation code runs before the rendering and painting events. This makes for a more
predictable code, and there’s a lot of time to do the animation without worrying about going
past the 16ms time we have at our disposal.
THE CONSOLE API
Every browser exposes a console that lets you interact with the Web
Platform APIs and also gives you an inside look at the code by printing
messages that are generated by your JavaScript code running in the page
Every browser exposes a console that lets you interact with the Web Platform APIs and also
gives you an inside look at the code by printing messages that are generated by your JavaScript
code running in the page.
Overview of the console
Use console.log formatting
Clear the console
Counting elements
Log more complex objects
Logging di erent error levels
Preserve logs during navigation
Grouping console messages
Print the stack trace
Calculate the time spent
Generate a CPU pro le
You can also choose to hide network-generated messages, and just focus on the JavaScript log
messages.
The console is not just a place where you can see messages, but also the best way to interact
with JavaScript code, and many times the DOM. Or, just get information from the page.
Let’s type our rst message. Notice the >, let’s click there and type
console.log('test')
The console acts as a REPL, which means read–eval–print loop. In short, it interprets our
JavaScript code and prints something.
Using console.log in your JavaScript code can help you debug for example by printing static
strings, but you can also pass it a variable, which can be a JavaScript native type (for example an
integer) or an object.
console.log('test1', 'test2')
We can also format pretty phrases by passing variables and a format speci er.
For example:
Example:
Another useful format speci er is %c , which allows to pass CSS to format a string. For example:
The rst way is to click the Clear Console Log button on the console toolbar.
The second method is to type console.clear() inside the console, or in your a JavaScript
function that runs in your app / site.
You can also just type clear() .
The third way is through a keyboard shortcut, and it’s cmd-k (mac) or ctrl + l (Win)
Counting elements
console.count() is a handy method.
const x = 1
const y = 2
const z = 3
console.count("The value of x is " + x + " and has been checked .. how many times?")
console.count("The value of x is " + x + " and has been checked .. how many times?")
console.count("The value of y is " + y + " and has been checked .. how many times?")
What happens is that count will count the number of times a string is printed, and print the
count next to it:
console.log([1, 2])
console.dir([1, 2])
As you can see this method prints the variable in a JSON-like representation, so you can inspect
all its properties.
console.log("%O", [1,2])
Which one to use depends on what you need to debug of course, and one of the two can do
the best job for you.
We just need to pass it an array of elements, and it will print each array item in a new row.
For example
or you can also set column names, by passing instead of an array, an Object Literal, so it will
use the object property as the column name
const shoppingCart = {}
shoppingCart.firstItem = {'color': 'black', 'size': 'L'}
shoppingCart.secondItem = {'color': 'red', 'size': 'L'}
shoppingCart.thirdItem = {'color': 'white', 'size': 'M'}
console.table(shoppingCart, ["color", "size"])
We’ll now discover three more handy methods that will help us debug, because they implicitly
indicate various levels of error.
First, console.info()
As you can see a little ‘i’ is printed beside it, making it clear the log message is just an
information.
Second, console.warn()
If you activate the Console ltering toolbar, you can see that the Console allows you to lter
messages based on the type, so it’s really convenient to di erentiate messages because for
example if we now click ‘Warnings’, all the printed messages that are not warnings will be
hidden.
this is a bit di erent than the others because in addition to printing a red X which clearly states
there’s an error, we have the full stack trace of the function that generated the error, so we can
go and try to x it.
To limit this problem the Console API o ers a handy feature: Grouping the Console messages.
As you can see the Console creates a group, and there we have the Log messages.
You can do the same, but output a collapsed message that you can open on demand, to further
limit the noise:
console.group("Main")
console.log("Test")
console.group("1")
console.log("1 text")
console.group("1a")
console.log("1a text")
console.groupEnd()
console.groupCollapsed("1b")
console.log("1b text")
console.groupEnd()
console.groupEnd()
You can start that manually, but the most accurate way to do so is to wrap what you want to
monitor between the profile() and profileEnd() commands. They are similar to
time() and timeEnd() , except they don’t just measure time, but create a more detailed
report.
const doSomething = () => console.log('test')
const measureDoingSomething = () => {
console.profile("doSomething()")
//do something, and measure its performance
doSomething()
console.profileEnd()
}
measureDoingSomething()
WEBSOCKETS
WebSockets are an alternative to HTTP communication in Web Applications.
They o er a long lived, bidirectional communication channel between client
and server. Learn how to use them to perform network interactions
They o er a long lived, bidirectional communication channel between client and server.
Once established, the channel is kept open, o ering a very fast connection with low latency and
overhead.
HTTP is a request/response protocol: the server returns some data when the client requests it.
With WebSockets:
the server can send a message to the client without the client explicitly requesting
something
the client and the server can talk to each other simultaneously
very little data overhead needs to be exchanged to send messages. This means a low
latency communication.
HTTP is great for occasional data exchange and interactions initiated by the client.
HTTP is much simpler to implement, while WebSockets require a bit more overhead.
Secured WebSockets
Always use the secure, encrypted protocol for WebSockets, wss:// .
ws:// refers to the unsafe WebSockets version (the http:// of WebSockets), and should be
avoided for obvious reasons.
Listen for it by assigning a callback function to the onopen property of the connection
object:
connection.onopen = () => {
//...
}
If there’s any error, the onerror function callback is red:
connection.onopen = () => {
connection.send('hey')
}
We’ll use it to build a WebSockets server. It can also be used to implement a client, and use
WebSockets to communicate between two backend services.
yarn init
yarn add ws
This code creates a new server on port 8080 (the default port for WebSockets), and adds a
callback function when a connection is established, sending ho! to the client, and logging the
messages it receives.
Here is a WebSockets client that interacts with the server: https://glitch.com/edit/#!/ avio-
websockets-client-example
THE SPEECH SYNTHESIS API
The Speech Synthesis API is an awesome API, great to experiment new kind
of interfaces and let the browser talk to you
The Speech Synthesis API is an awesome tool provided by modern browsers. Introduced in
2014, it’s now widely adopted (https://caniuse.com/#feat=speech-synthesis) and available in
Chrome, Firefox, Safari and Edge. IE is not supported.
It’s part of the Web Speech API, along with the Speech Recognition API.
I used it recently to provide an alert on a page that monitored some parameters. When one of
the numbers went up, I was alerted thought the computer speakers.
Getting started
The most simple example of using the Speech Synthesis API stays on one line:
speechSynthesis.speak(new SpeechSynthesisUtterance('Hey'))
Copy and paste it in your browser console, and your computer should speak!
The API
The API exposes several objects to the window object.
SpeechSynthesisUtterance
SpeechSynthesisUtterance represents a speech request. In the example above we passed
it a string. That’s the message the browser should read aloud.
Once you got the utterance object, you can perform some tweaks to edit the speech properties:
Example:
Set a voice
The browser has a di erent number of voices available.
console.log(`Voices #: ${speechSynthesis.getVoices().length}`)
speechSynthesis.getVoices().forEach((voice) => {
console.log(voice.name, voice.lang)
})
Here is one of the cross browser issues. The above code works in Firefox, Safari (and possibly
Edge but I didn’t test it), but does not work in Chrome. Chrome requires the voices handling in
a di erent way, and requires a callback that is called when the voices have been loaded:
After the callback is called, we can access the list using speechSynthesis.getVoices() .
I believe this is because Chrome - if there is a network connection - checks additional languages
from the Google servers:
If there is no network connection, the number of languages available is the same as Firefox and
Safari. The additional languages are available where the network is enabled, but the API works
o ine as well.
printVoicesList()
You can use any language you want, by simply setting the utterance lang property:
speak('Ciao')
Mobile
On iOS the API works but must be triggered by a user action callback, like a response to a tap
event, to provide a better experience to users and avoid unexpected sounds out of your phone.
You can’t do like in the desktop where you can make your web pages speak something out of
the blue.