A couple of days ago, I revisited the awesome 2021 State of JS Survey. The state of JS is an online survey that collects data from developers around the world to see the most recent and upcoming trends in the JavaScript community. Among the data it collects, a section is dedicated to the native features JavaScript provides, listed by their usage and awareness. As you can imagine, among the most used features are popular ones like Optional chaining, Nullish coalescing, Websockets, and so on.
However, I wasn’t interested in the most used or known APIs. Instead, I was looking for the least known ones. I wanted to know which APIs we aren’t talking about enough, and among them, I found four pretty different APIs that are extremely useful:
In this article, we will see what they are, where we should use them, and how to use them.
Note: These APIs are all available in this demo.
Page Visibility API
This is a little-known web API that rates last fourth in awareness in the State of JS Survey. It lets you know when a user has left the page. To be precise, the API triggers an event whenever the page visibility status changes, either when the user minimizes or maximizes the window or switches the tab.
In the past, you had to use gimmicks to know if a user had switched tabs or minimized the window. The most popular was using the blur
and focus
browser events. Using these events would result in something like the following:
window.addEventListener("focus", function () { // User is back on the page // Do Something
}); window.addEventListener("blur", function () { // User left the page // Do Something
});
The previous code works but not as intended. Since the blur
event is triggered when the page loses focus, it can be triggered when the user clicks the search bar, an alert dialog, the console, or the window border. So, blur
and focus
only tell us if the page is active but not if the content of the page is hidden or visible.
Use Cases
In general, we want to use the Page Visibility API to stop unnecessary processes when the user doesn’t see the page or, on the other hand, to perform background actions. Some specific cases can be:
- to pause videos, image carousels, or animations when the user leaves the page;
- if the page displays live data from an API, stop this behavior temporarily while the user is away;
- to send user analytics.
How To Use It?
The Page Visibility API brings two properties and an event to access the page visibility status:
document.hidden
It is globally available and read-only. Try to avoid it since it is now deprecated, but when accessed, it returnstrue
if the page is hidden andfalse
if it is visible.document.visibilityState
It is the updated version ofdocument.hidden
, but when accessed, it returns four possible values depending on the page visibility status: –visible
The page is visible, or to be exact, it isn’t minimized nor in another tab. –hidden
The page isn’t visible; it is minimized or in another tab. –prerender
This is the initial state of a visible page when it is prerendering. A page’s visibility status may start atprerender
and then change to another state, but it can’t change from another state toprerender
. –unloaded
The page is being unloaded from memory.visibilitychange
It’s an event provided by thedocument
object that is triggered when the pagevisibilityState
changes.
document.addEventListener("visibilitychange", () => { if (document.visibilityState === "visible") { // page is visible } else { // page is hidden }
});
To see how to use the Page Visibility API, let’s use it to pause a video and stop fetching resources from an API when the user leaves the page. To start, I will be using vite.js, which is an amazing tool to start a new project quickly:
npm create vite@latest unknown-web-apis
When asked to select a framework, select vanilla to create a vanilla javascript project. And once finished, go to the new folder, install the necessary npm
packages and start the developer server:
cd unknown-web-apis npm install npm run dev
Go to localhost:3000/, and you will see your Vite project up and running!
Firstly, we will direct to the /main.js
file and delete all the boilerplate. Secondly, we will open /index.html
, and inside the #app
div tag, we will add a video element with any video file you want. I used a dancing Yoshi one. 🙂
<div id="app"> <video controls id="video"> <source src="./yoshi.mp4" /> </video>
</div>
Back to /main.js
, we will add an event listener to the document
object listening to the visibilitychange
event. We then can access the document.visibilityState
property to see when the page is visible or hidden.
document.addEventListener("visibilitychange", () => { console.log(document.visibilityState);
});
You can go to the page’s console and see the page visibility status change when you minimize the window or switch to another tab. Now, inside the event listener, we can check the document.visibilityState
property, pause the video when it is hidden
, and play it when visible
. (Of course, we first select the video
element using document.querySelector()
.)
const video = document.querySelector("#video"); document.addEventListener("visibilitychange", () => { if (document.visibilityState === "visible") { video.play(); } else { video.pause(); }
});
Now the video stops whenever the user leaves the page. Another use of the Page Visibility API is to stop fetching unnecessary resources when the user doesn’t see the page. To see this, we will write a function to constantly fetch a random quote from the quotable.io API and pause this behavior when the page is hidden. Firstly, we will create a new div tag to store the quote in /index.html
.
<div id="app"> <video controls id="video"> <source src="./yoshi.mp4" /> </video> <div id="quote"></div>
</div>
Back in /main.js
, we will use the Fetch API to make a call to the quotable.io
endpoint https://api.quotable.io/random and then insert it into the quote
div.
const quote = document.querySelector("#quote"); const getQuote = async () => {
try {
const response = await fetch("https://api.quotable.io/random");
const {content, author, dateAdded} = await response.json();
const parsedQuote = ` <q>${content}</q> <br> <p>- ${author}</p><br> <p>Added on ${dateAdded}</p>`;
quote.innerHTML = parsedQuote;
} catch (error) {
console.error(error);
}
}; getQuote();
Let’s shortly explain what is happening right here. We first select the quote
element from the DOM. We then declare the getQuote
function, which is an async
function that allows us to use the await
keyword to wait until we fetch the data from the API. The data fetched is in JSON format, so we use the await
keyword one more time to wait until the data is parsed into a JavaScript object. The quotable.io
API gives us—among other things—the content
, author
, and dateAdded
properties that we will inject and display into the quote
div. This works, but the quote is only fetched once, so we can use setInterval()
to call the function every 10 seconds.
const quote = document.querySelector("#quote"); const getQuote = async () => {
try {
const response = await fetch("https://api.quotable.io/random");
const {content, author, dateAdded} = await response.json();
const parsedQuote = ` <q>${content}</q> <br> <p>- ${author}</p><br> <p>Added on ${dateAdded}</p>`;
quote.innerHTML = parsedQuote;
} catch (error) {
console.error(error);
}
}; getQuote(); setInterval(getQuote, 10000);
If the user minimizes the window or switches the tab, the page would still fetch the quotes, creating an unnecessary network load. To solve this, we can check if the page is visible before fetching a quote.
const getQuote = async () => { if (document.visibilityState === "visible") { try { const response = await fetch("https://api.quotable.io/random"); const {content, author, dateAdded} = await response.json(); const parsedQuote = ` <q>${content}</q> <br> <p>- ${author}</p><br> <p>Added on ${dateAdded}</p>`; quote.innerHTML = parsedQuote; } catch (error) { console.error(error); } }
}; getQuote(); setInterval(getQuote, 10000);
Now, we will only fetch the quote if the page is visible to the user.
Support
Web Share API
What Is It?
The Web Share API is also among the least-known APIs but is extremely useful. It lets you access the operative system’s native sharing mechanism, which is especially useful to mobile users. With this API, you can share text, links, and files without the need to create your own sharing mechanisms or use third-party ones.
Use Cases
They are pretty self-explanatory. You can use it to share content from your page to social media or copy it to the user’s clipboard.
How To Use It?
The Web Share API gives us two interfaces to access the user’s sharing system:
navigator.canShare()
Accepts the data you want to share as an argument and returns a boolean argument depending on whether it is shareable.-
navigator.share()
Returns a promise that will resolve if the sharing is successful. It invokes the native sharing mechanism and accepts the data you want to share as an argument. Note that it can only be called if a user has pressed a link or button, i.e., it requires transient activation. The share data is an object that can have the following properties:url
: URL to be shared,text
: text to be shared,title
: title to be shared,files
: array ofFile
objects representing files to be shared.
To see how to use this API, we will recycle our prior example and make an option to share our quotes using the Web Sharing API. To start, we first have to make a share button in /index.html
:
<div id="app"> <video controls id="video"> <source src="./yoshi.mp4" /> </video> <div id="quote"></div> <button type="button" id="share-button">Share Quote</button>
</div>
We direct to /main.js
and select the share button from the DOM. Then, we create an async
function to share the data we want.
const shareButton = document.querySelector("#share-button"); const shareQuote = async (shareData) => { try { await navigator.share(shareData); } catch (error) { console.error(error); }
};
Now, we can add a click event listener to the shareButton
element to callback the shareQuote
function. Our shareData.text
value will be the quote.textContent
property and the shareData.url
will be the page URL i.e the location.href
property.
const shareButton = document.querySelector("#share-button"); const shareQuote = async (shareData) => { try { await navigator.share(shareData); } catch (error) { console.error(error); }
}; shareButton.addEventListener("click", () => { let shareData = { title: "A Beautiful Quote", text: quote.textContent, url: location.href, }; shareQuote(shareData);
});
Now you can share your quotes with anyone through your native operative system. However, it is important to note that the Web Share API will only work if the context is secure, i.e., if the page is served over https://
or wss://
URLs.
Support
Broadcast Channel API
What Is It?
Another API I want to talk about is the Broadcast Channel API. It allows browsing contexts to send and receive basic data from each other. Browsing contexts are elements like a tab, window, iframe, or anywhere a page can be displayed. Due to security reasons, communication between browsing contexts isn’t allowed unless they are of the same origin and use the Broadcast Channel API. For two browsing contexts to be of the same origin, they must share in their URL the same protocol (e.g. http/https
), domain (e.g. example.com
), and port (e.g. :8080
).
Use Cases
The Broadcast Channel API is generally used to keep a page’s state synced across different tabs and windows to enhance user experience or for security reasons. It can also be used to know when a service is finished in another tab or window. Some examples are:
- Log a user in or out across all tabs.
- Detect when an asset is uploaded and show it across all pages.
- Instruct a service worker to do some background work.
How To Use It?
The Broadcast Channel API involves a BroadcastChannel
object that can be used to send messages to other contexts. Its constructor has only one argument: a string that will work as an identifier to connect to the channel from other contexts.
const broadcast = new BroadcastChannel("new_channel");
Once we have created a BroadcastChannel
object with the same identifier across two contexts, the new BroadcastChannel
object will have two available methods to start communicating:
BroadcastChannel.postMessage()
to send a message across all connected contexts. It takes any kind of object as its only argument so that you can send a wide variety of data.
broadcast.postMessage("Example message");
BroadcastChannel.close()
to close the channel and indicate to the browser that it won’t receive any more messages so it can collect them into the garbage.
To receive a message, the BroadcastChannel
has a message
event that we can listen to using an addEventListener
or its onmessage
property. The message
event has a data
property with the data sent and other properties to identify the context that sent the message, such as origin
, lastEventId
, source
, and ports
.
broadcast.onmessage = ({data, origin}) => { console.log(`${origin} says ${data}`);
};
Let’s see how to use the Broadcast Channel API by using our prior example. Our goal would be to make another browsing context with the same origin and display the same quote in both contexts. To do this, we will create a new folder named new-origin
with a new /index.html
and /main.js
files inside.
The /new-origin/index.html
will be a new HTML boilerplate with a #quote
div inside:
<!DOCTYPE html>
<html lang="en"> <head> <meta charset="UTF-8" /> <link rel="icon" type="image/svg+xml" href="../favicon.svg" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Vite App</title> </head> <body> <div id="quote"></div> <script type="module" src="./main.js"></script> </body>
</html>
In the /new-origin/main.js
file, we will create a new broadcast channel and select the #quote
element from the DOM:
const broadcast = new BroadcastChannel("quote_channel");
const quote = document.querySelector("#quote");
And in our prior /main.js
file, we will create a new BroadcastChannel
object and connect it to the "quote_channel"
. We will also modify the getQuote
function to send the quote as a message to other contexts.
const broadcast = new BroadcastChannel("quote_channel"); //... const getQuote = async () => {
try {
const response = await fetch("https://api.quotable.io/random");
const {content, author, dateAdded} = await response.json();
const parsedQuote = ` <q>${content}</q> <br> <p>- ${author}</p><br> <p>Added on ${dateAdded}</p>`;
quote.innerHTML = parsedQuote;
broadcast.postMessage(parsedQuote);
} catch (error) {
console.error(error);
}
};
Back in the /new-origin/main.js
file, we will listen to the message
event and change the quote.innerHTML
each time a new quote is sent.
const broadcast = new BroadcastChannel("quote_channel");
const quote = document.querySelector("#quote"); broadcast.onmessage = ({data}) => { quote.innerHTML = data;
};
Now you can see how the quote in http://localhost:3000/new-origin/ changes to the quote in http://localhost:3000. You can also notice how the quote doesn’t change when the http://localhost:3000 tab is hidden since it only fetches a quote when its page visibility status is visible.
Support
Internationalization API
What It Ss?
When developing a web page or app, it’s extremely common to need to translate its content across other languages to reach a wider audience. However, just translating your page’s text to whichever language you need isn’t enough to make your content available to speakers of that language since things like dates, numbers, units, and so on are different across countries and may cause confusion to your users.
Let’s say you want to display the date “November 8, 2022” on your webpage like “11/8/22”. This data can be read in three distinct ways depending on the reader’s country:
- “November 8, 2022” or MM/DD/YY by people from the US.
- “August 11, 2022” or DD/MM/YY by people from Europe and Latam.
- “August 22, 2011” or YY/MM/DD by people from Japan, China, and Canada.
This is where the Internationalization API (Or I18n API) comes to solve formatting issues across different languages and regions. The I18n API is an amazing tool that has several uses, but we won’t delve into them to not overcomplicate this article.
How To Use It?
The I18n API uses locale identifiers to work. A locale identifier is a string that expresses the user’s language, country, region, dialect, and other preferences. To be precise, a locale identifier is a string that consists of subtags separated by hyphens. Subtags represent user preferences like language, country, region, or script and are formatted in the following way:
- “zh”: Chinese (language);
- “zh-Hant”: Chinese (language) written in traditional characters (script);
- “zh-Hant-TW”: Chinese (language) written in traditional characters (script) as used in Taiwan (region).
There are more subtags to address more users’ preferences (if you want to learn more, you can check the RFC definition of language tags), but to keep it short, the I18n API uses these locale identifiers to know how to format all the language-sensitive data.
To be more precise, the I18n API provides an Intl
object that brings a bunch of specialized constructors to work with language-sensitive data. In my opinion, some of the most useful Intl
constructors for internationalization are:
Intl.DateTimeFormat()
Used to format dates and times.Intl.DisplayNames()
Used to format language, region, and script display names.Intl.Locale()
Used to construct and manipulate locale identifier tags.Intl.NumberFormat()
Used to format numbers.Intl.RelativeTimeFormat()
Used to format relative time descriptions.
For our example, we will focus on the Intl.DateTimeFormat()
constructor to format the quote’s dateAdded
property depending on the user locale. The Intl.DateTimeFormat()
constructor takes two arguments: the locale
string that defines the date formatting convention and the options
objects to customize how to format the dates.
The Intl.DateTimeFormat()
created object has a format()
method that takes two arguments: the Date
object we want to format and the options
object to customize how to display the formatted date.
const logDate = (locale) => { const newDate = new Date("2022-10-24"); // YY/MM/DD const dateTime = new Intl.DateTimeFormat(locale, {timeZone: "UTC"}); const formatedDate = dateTime.format(newDate); console.log(formatedDate);
}; logDate("en-US"); // 10/24/2022
logDate("de-DE"); // 24.10.2022
logDate("zh-TW"); // 2022/10/24
Note: On the Intl.DateTimeFormat
constructor’s options
argument, we set the timeZone
property to "UTC"
so the date isn’t formatted to the user’s local time. In my case, the date is parsed to “10/23/2022” without the timeZone
option.
As you can see, the dateTime.format()
changes the date depending on the locale’s date formatting convention. We can implement this behavior on the quotes’ date using the navigator.language
global property, which holds the user’s preferred locale. To do this, we will create a new function that takes a date string (in YYYY-MM-DD format) and returns the date formatted depending on the user’s locale.
const formatDate = (dateString) => { const date = new Date(dateString); const locale = navigator.language; const dateTimeFormat = new Intl.DateTimeFormat(locale, {timeZone: "UTC"}); return dateTimeFormat.format(date); };
We can add this function inside the getQuote()
function to parse the dateAdded
date.
const getQuote = async () => { if (document.visibilityState === "visible") { try { const response = await fetch("https://api.quotable.io/random"); const {content, author, dateAdded} = await response.json(); const parsedQuote = ` <q>${content}</q> <br> <p>- ${author}</p><br> <p>Added on ${formatDate(dateAdded)}</p>`; quote.innerHTML = parsedQuote; broadcast.postMessage(parsedQuote); } catch (error) { console.error(error); } }
};
With this, our quotes are localized to the user’s preferred language! In my case, my navigator.language
value is "en"
, so my dates are formatted to MM/DD/YY.
Support
Conclusion
After reading this article, you can now flex about knowing the existence of these APIs and how to use them. Even though they were ranked last in awareness in the State of JS Survey, they are extremely useful, and knowing how to use them will definitely enhance your developing experience. The fact that these powerful APIs aren’t very known means that there are still useful APIs you and I still don’t know about, so it’s the perfect time to explore and find that API that will simplify your code and save you a ton of time developing.
I hope you liked this article and until the next time!
(vf, yk, il)