Browser APIs
Internationalization API
new Intl.DateTimeFormat('en-GB', {
dateStyle: 'full',
timeStyle: 'long',
timeZone: 'Australia/Sydney',
}).format(new Date())
Intl.DateTimeFormat("de-AT", {
weekday: "short",
day: "numeric",
month: "long",
}).format(new Date())
Intl.DateTimeFormat("de-AT", {
day: "2-digit",
month: "2-digit",
year: "numeric"
}).format(new Date());
History API
History
const historyLength = window.history.length;
window.history.back();
window.history.forward();
window.history.go(-2);
history.state
Events
window.addEventListener("hashchange", function() {
e.newURL
e.oldURL
history.state
location.hash
});
window.addEventListener("popstate", function() {
e.state
});
Location API
window.location
href
= entire URL
window.location.href // https://css-tricks.com/example/index.html?s=flexbox
window.location.protocol // https:
window.location.host // css-tricks.com
window.location.pathname // example/index.html
window.location.search // ?s=flexbox
let newURL = window.location.protocol + "//" + window.location.host + "/" + window.location.pathname + window.location.search
https:// developer.markusdoppler.at :80 /path/filename.html#hash?query=string&num=1
protocol subdomain domain superdomain port pathname hash query
window.location.reload();
window.location.replace()
window.location.replace('http://www.ecosia.com')
URL
const url = new URL('../cats', 'http://www.example.com/dogs');
console.log(url.hostname); // "www.example.com"
console.log(url.pathname); // "/cats"
url.hash = 'tabby';
console.log(url.href); // "http://www.example.com/cats#tabby"
url.pathname = 'démonstration.html';
console.log(url.href); // "http://www.example.com/d%C3%A9monstration.html"
// search parameters
// https://some.site/?id=123
var parsedUrl = new URL(window.location.href);
console.log(parsedUrl.searchParams.get("id")); // "123"
Do not track
- If these do not hold true, one could e.g. instantiate Google analytics.
window.doNotTrack === "1"
navigator.doNotTrack === "1"
navigator.doNotTrack === "yes"
navigator.msDoNotTrack === "1"
JSON
let string = JSON.stringify(history.state)
let data = JSON.parse(historyState)
Drag and Drop API
-
<img>
and<a>
elements are draggable by default -
other elements
<div draggable="true"></div>
Events
-
dragstart
-
drag
-
dragend
-
dragover
-
dragenter
-
dragleave
-
dragexit
-
drop
event.dataTransfer.types
in dragenter
, dragover
// needed for Firefox
event.dataTransfer.dropEffect = 'copy'
event.dataTransfer.dropEffect = 'link'
in dragstart
event.dataTransfer.effectAllowed = 'copyMove';
// needed for Chrome
event.dataTransfer.setData(type, data);
event.dataTransfer.setData('Text', "Test");
in drop
event.dataTransfer.getData('Text')
event.dataTransfer.setDragImage(dragIcon, -10, -10);
Intersection Observer API
Defining the observer
const observer = new IntersectionObserver(callback, options);
// Observing an element
observer.observe(document.querySelector("section"));
// Unobserving an element
observer.unobserve(document.querySelector("section"));
The callback function takes the two arguments entries
and observer
.
new IntersectionObserver((entries, observer) => {
entries.forEach((entry, i) => {
// perform intersection action
if (entry.isIntersecting) {
observer.unobserve(entry.target)
}
})
}, {
threshold: 0,
rootMargin: "0px 0px 300px 0px"
})
Options
const options = {
root: null, // it is the viewport
threshold: 0,
rootMargin: "-150px"
};
Intersection Observer v2
Resize Observer API
const resizeObserver = new ResizeObserver((entries) => {
entries.forEach((entry) => {
console.log("resize observed on container", entry.target);
console.log("borderBoxSize", entry.borderBoxSize[0].inlineSize, entry.borderBoxSize[0].blockSize);
console.log("contentRect", entry.contentRect.width, entry.contentRect.height);
});
});
resizeObserver.observe(document.getElementById("format-panel"));
Mutation Observer API
const mutationObserver = new MutationObserver(entries => {
console.log(entries)
})
const parent = document.querySelector(".parent")
const mutationOptions = { children: true };
mutationObserver.observe(parent, mutationOptions)
mutationObserver.disconnect()
parent.children[0].remove()
setTimeout(() => {
parent.appendChild(document.createElement("div"))
}, 100)
Children
const mutationOptions = {
children: true
}
// evoke observer
parent.children[0].remove()
parent.appendChild(document.createElement("div"))
Attributes
const mutationOptions = {
attributes: true,
attributesOldValue: true,
attributesFilter: ["id"]
}
// evoke observer
parent.id = "New Id"
Character data
const mutationOptions = {
subtree: true,
characterData: true,
characterDataOldValue: true
}
// evoke observer
parent.parent.innerText = "Hallo"
Local Storage, Session Storage and Cookies
Local Storage
persists locally on this machine (for all browsers)
localStorage.setItem('name', 'Melanie')
localStorage.getItem('name')
localStorage.removeItem('name')
Session Storage
persists only for this session (i.e. as long as this tab is open)
sessionStorage.setItem('name', 'Jacob')
sessionStorage.getItem('name')
sessionStorage.removeItem('name')
sessionStorage.clear()
Cookies
document.cookie = `name=Karl; expires=${new Date(9999,0,1).toUTCString()}`
document.cookie = `lastname=Müller; expires=${new Date(9999,0,1).toUTCString()}`
Clipboard API
Programmatic copy
navigator.clipboard.writeText("Hello clipboard!").then(() => {
// Successful copy
}, () => {
// Copy failed
});
Programmatic paste
navigator.clipboard.readText().then((clipboardText) => {
// paste clipboardText
});
Rich text paste
let data = [];
let clipboardItems = await navigator.clipboard.read();
for (let clipboardItem of clipboardItems) {
if (!clipboardItem.types.includes("text/html"))
return;
let blob = await clipboardItem.getType("text/html");
let html = await new Promise((resolve) => {
let reader = new FileReader;
reader.addEventListener("load", () => {
resolve(reader.result);
});
reader.readAsText(blob);
});
data.push(html);
}
Clipboard events
document.addEventListener("cut", function(e) { console.log(e); });
document.addEventListener("copy", function(e) { console.log(e); });
document.addEventListener("pase", function(e) { console.log(e); });
Web Animations API
needle.animate({
transform: [
"rotateX(35deg) rotateZ(13deg)",
"rotateX(35deg) rotateZ(733deg)",
],
easing: ["ease-out"],
}, 800);
Composed animations
element.animate(
[
{ transform: "rotate(O) translate3D(10px, 0px, 100px)" },
{ color: "#431236", offset: 0.3 },
{ transform: "rotate(360deg) translateX(25px)" }
],
{ duration: 3000, iterations: Infinity, fill: 'forwards' }
);
Web Components API
Using the component via the custom HTML Tag
<recipe-text>
<span slot="vegetable">Broccoli</span>
<span slot="grain">Rice</span>
</recipe-text>
Create class and define custom element
class RecipeText extends HTMLElement { ... }
customElements.define("recipe-text", RecipeText);
Template
The template can contain any markup and styles pertaining to the custom element.
Define the component via HTML5's <template>
and <slot>
tags.
<template id="recipe-text-template">
<p>
<slot name="vegetable" part="veg">Tomato</slot> and <slot name="grain" part="veg">Noodles</slot>
</p>
</template>
Slots
Slots can be used to add custom text.
Single slot
<radio-button>
Slot text
</radio-button>
Usage in template
<button>
<slot />
</button>
Multiple slots
<emoji-card name="Fruits" emoji="🥝🍇">
<div slot="sour">Kiwi</div>
<div slot="sweet">Grape</div>
</emoji-card>
Usage in template
<div>
<h3><slot name="sour" /></h3>
<p><slot name="sweet" /></p>
</div>
Shadow DOM
Encapsulates e.g. styles pertaining to the custom component from the rest of the DOM.
customElements.define("recipe-text",
class extends HTMLElement {
constructor() {
super();
let recipeTextTemplate = document.getElementById("recipe-text-template");
let recipeText = recipeTextTemplate.content;
const shadowRoot = this.attachShadow({ mode: "open" }).appendChild(recipeText.cloneNode(true));
}
});
mode: open
: JavaScript from outside the :root
can access and manipulate the elements within the shadow DOM
Styling the component
Styling from within the <template>
definition
<template id="vegetable-text-template">
<style>
p { background-color: pink; padding: 0.5em; border: 1px solid red; }
</style>
<p>The <slot name="vegetable">Tomato</slot> is my favourite Vegetable!</p>
</template>
Note: These styles are scoped directly to the component and nothing leaks out to other elements on the same page, thanks to the shadow DOM.
Styling from the page's CSS
vegetable-text span {
color: blue;
}
Note: Styles in the main CSS file cannot access elements in the <template>
or shadow DOM.
Styling CSS Shadow parts from the page's CSS
Web Component Template
<template id="format-button">
<style>
.format-button { border: 2px solid navy; }
</style>
<button class="format-button">
<span part="icon" class="icon"></span>
<span part="label" class="label"></span>
</button>
</template>
Registering the Web component
let template = document.getElementById("format-button");
window.customElements.define(template.id, class extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
let newButtonElement = template.content.cloneNode(true);
let parts = newButtonElement.querySelectorAll("span");
parts[0].textContent = this.getAttribute("data-icon");
parts[1].textContent = this.textContent;
this.shadowRoot.appendChild(newButtonElement);
this.addEventListener("click", this.handleClick.bind(this));
}
});
CSS Shadow Part Styles
#bold::part(icon) {
color: red;
font-weight: bold;
}
#italic::part(icon) {
color: blue;
font-style: italic;
}
Web Component Usage
<format-button id="bold" data-icon="B">Bold</format-button>
<format-button id="italic" data-icon="I">Italic</format-button>
Life Cycle Methods
Lifecycle Hooks in a web component:
constructor()
connectedCallback()
disconnectedCallback()
attributeChangedCallback(name, oldValue, newValue)
static get observedAttributes()
class RadioButton extends HTMLElement {
// called when an instance of the element is created or upgraded
constructor() {
super();
}
// called every time the element is inserted into the DOM
connectedCallback() { }
// called every time the element is removed into the DOM
disconnectedCallback() { }
// called when an attribute is added, removed, updated, or replaced
attributeChangedCallback(attributeName, oldValue, newValue) { }
// Invoked each time the custom element is moved to a new document.
adoptedCallback() { }
// observing the following attributes, i.e. attributeChangedCallback() called when attribute modified
static get observedAttributes() {
return ['name', 'data'];
}
}
Fullscreen API
const requestFullscreen = () => {
if (document.documentElement.requestFullscreen) document.documentElement.requestFullscreen();
else if (document.documentElement.webkitRequestFullscreen) document.documentElement.webkitRequestFullscreen();
};
const exitFullscreen = (document) => {
if (!is_fullscreen()) return;
if (document.exitFullscreen) document.exitFullscreen();
else if (document.webkitExitFullscreen) document.webkitExitFullscreen();
};
const is_fullscreen = () => document.fullscreenElement || document.webkitFullscreenElement;
// if supported, create event listener
const fullscreen_support = document.documentElement.requestFullscreen || document.documentElement.webkitRequestFullscreen;
const fullscreen_change = document.onfullscreenchange !== undefined ? 'fullscreenchange' : 'webkitfullscreenchange';
if (fullscreen_support) {
document.documentElement.addEventListener(fullscreen_change, () => {
if (!is_fullscreen()) {
end_presentation_mode(window);
}
});
}
if (fullscreen_support) requestFullscreen();
if (fullscreen_support) exitFullscreen(window.document);
Service Workers
Make sure Service Workers are supported
if ('serviceWorker' in navigator) {
// ...
}
Register Service Worker
window.addEventListener('load', function() {
navigator.serviceWorker
.register('caching-worker.js')
.then(reg => console.log("Service Worker registered."))
.catch(err => console.log(`Service Worker error: ${err}.`));
});
Using Service Worker for Caching
caching-worker.js
const cacheName = "v1";
const cacheAssets = [
'index.html',
'about.html',
'/css/style.css',
'/js/main.js'
];
self.addEventListener("install", e => {
console.log("Service Worker installed.");
e.waitUntil(
caches
.open(cacheName)
.then(cache => {
console.log("Service Worker caching files.");
cache.addAll(cacheAssets);
})
.then(() => self.skipWaiting())
);
});
self.addEventListener("activate", e => {
console.log("Service Worker activated.");
// Remove unwanted caches
e.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cache => {
if (cache !== cacheName) {
console.log("Service Worker clearing old cache.");
return caches.delete(cache);
}
})
);
})
);
});
self.addEventListener("fetch", e => {
console.log("Service Worker fetching.");
e.respondWith(
fetch(e.request)
.then(res => {
// Make copy/clone of response
const resClone = res.clone();
// Open cache
caches
.open(cacheName)
.then(cache => {
// Add response to cache
cache.put(e.request, resClone);
});
return res;
}).catch(err => caches.match(e.request).then(res => res));
);
});
Caching with Service Workers
self.addEventListener('install', (e) => {
e.waitUntil(
caches.open('butterfly-store').then((cache) => cache.addAll([
'',
'index.html',
'index.js',
'src/style.css',
'images/peacock-300.png',
])),
);
});
self.addEventListener('fetch', (e) => {
console.log(e.request.url);
e.respondWith(
caches.match(e.request).then((response) => response || fetch(e.request)),
);
});
Offline Page with Service Workers
const OFFLINE_VERSION = 1;
const CACHE_NAME = 'offline';
const CACHE_URL = 'offline.html';
self.addEventListener("install", (event) => {
event.waitUntil(
(async () => {
const cache = await caches.open(CACHE_NAME);
await cache.add(new Request(OFFLINE_URL, { chache: 'reload' }))
})();
);
self.skipWaiting();
});
self.addEventListener("fetch", (event) => {
if (event.request.mode !== 'navigate') {
return;
}
event.respondWith((async () => {
try {
const networkResponse = await fetch(event.request);
return networkResponse;
} catch () {
const cache = await caches.open(CACHE_NAME);
const cachedResponse = await cache.match(OFFLINE_URL);
return cachedResponse;
}
})());
});
Service Worker
Reload app, once back online
window.addEventListener("online", () => {
window.location.reload()
})
Fallback function
async function checkNetworkAndReload() {
try {
const response = await fetch('.');
if (response.status >= 200 & response.status < 500) {
window.location.reload();
return
}
} catch {
// Do nothing, still offline
}
window.setTimeout(checkNetworkAndReload, 2500);
}
Web Sharing API
const overlay = document.querySelector("overlay");
const shareModal = document.querySelector("share-modal");
const shareButton = document.querySelector("share-button");
const title = window.document.title;
const url = window.document.location.href;
shareButton.addEventListener("click", () => {
if (navigator.share) {
navigator.share({
title: `${title}`,
url: `${url}`
})
.then(() => {
console.log("Thanks for sharing!");
})
.catch(console.error);
} else {
overlay.classList.add("show-share");
shareModal.classList.add("show-share");
}
});
overlay.addEventListener("click", () => {
overlay.classList.remove("show-share");
shareModal.classList.remove("show-share");
});
const shareButton = document.getElementById("share-button")
shareButton.onclick = async (filesArray) => {
if (navigator.canShare) {
navigator.share({
url: "https://yourapp.com",
title: "PWAs are awesome",
text: "I learned to build a PWA today."
})
}
}
// use files array
shareButton.onclick = async (filesArray) => {
if (navigator.canShare) {
navigator.share({
files: filesArray,
title: "PWAs are awesome",
text: "I learned to build a PWA today."
})
}
}
Geolocation API
window.navigator.geolocation.getCurrentPosition(console.log)
Device Motion API
Chrome Dev-Tools: Sensors Tab
window.addEventListener("devicemotion", (event) => {
console.log(event)
event.acceleration
event.acceleration.x
event.rotationRate.beta
event.rotationRate.gamma
})
Screen Capture API
const options = {
video: {
cursor: "always", // show the cursor
},
audio: false, // don't record audio
};
videoElement.srcObject = await navigator.mediaDevices.getDisplayMedia({
video: {
cursor: "always",
audio: false,
});
Gamepad API
let frameId;
window.addEventListener("gamepadconnected", (event) => {
console.log("A gamepad connected:", event.gamepad);
frameId = window.requestAnimationFrame(animationFrame);
});
window.addEventListener("gamepaddisconnected", (event) => {
console.log("A gamepad disconnected:", event.gamepad);
window.cancelAnimationFrame(frameId);
});
function animationFrame() {
var gamepads = navigator.getGamepads();
console.log(gamepads);
}