We know that scrolling responsiveness is critical to the user's engagement with
a website on mobile, yet touch event listeners often cause serious scrolling
performance problems. Chrome has been addressing this by allowing touch event
listeners to be
passive
(passing the {passive: true} option to addEventListener()) and shipping the
pointer
events API.
These are great features to drive new content into models that don't block
scrolling, but developers sometimes find them hard to understand and adopt.
We believe the web should be fast by default without developers needing to
understand arcane details of browser behavior. In Chrome 56 we are defaulting
touch listeners to passive by
default in cases where
that most often matches the developer's intention. We believe that by doing this
we can greatly improve the user's experience whilst having minimal negative
impact on sites.
In rare cases this change can result in unintended scrolling. This is usually
easily addressed by applying a
touch-action:
nonestyle to the element where scrolling shouldn't occur. Read on for
details, how to know if you are impacted, and what you can do about it.
Background: Cancelable Events slow your page down
If you call
preventDefault()
in the touchstart or first touchmove events then you will prevent scrolling.
The problem is that most often listeners will not call preventDefault(), but
the browser needs to wait for the event to finish to be sure of that. Developer
defined "passive event listeners" solve this. When you add a touch event with a
{passive: true} object as the third parameter in your event handler then you
are telling the browser that the "touchstart" listener will not call
preventDefault() and the browser can safely perform the scroll without
blocking on the listener. For example:
Our main motivation is to reduce the time it takes to update the display after
the user touches the screen. To understand the usage of touchstart and touchmove
we added metrics to determine how frequently scroll blocking behaviour occurred.
We looked at the percentage of cancelable touch events that were sent to a root
target (window, document, or body) and determined that about 80% of these
listeners are conceptually passive but were not registered as such. Given the
scale of this problem we noticed a great opportunity to improve scrolling
without any developer action by making these events automatically "passive".
This drove us to define our intervention as: if the target of a touchstart or
touchmove listener is the window, document or body we default
passive to true. This means that code like:
Now calls to preventDefault() inside the listener will be ignored.
The graph below shows the time taken by the top 1% of scrolls from the time a
user touches the screen to scroll to the time the display is updated. This data
is for all websites in Chrome for Android. Before the intervention was enabled
1% of scrolls took just over 400ms. That has now been reduced to just over 250ms
in Chrome 56 Beta; a reduction of about 38%. In the future we hope to make
passive true the default for alltouchstart and touchmove listeners,
reducing this to below 50ms.
Breakage and Guidance
In the vast majority of cases, no breakage will be observed. But when breakage
does occur, the most common symptom is that scrolling happens when you don't
want it. In rare cases, developers may also notice unexpected click events
(when preventDefault() was missing from a touchend listener).
In Chrome 56 and later, DevTools will log a warning when you call
preventDefault() in an event where the intervention is active.
touch-passive.html:19 Unable to preventDefault inside passive event listener due to target being treated as passive. See https://www.chromestatus.com/features/5093566007214080
Your application can determine whether it may be hitting this in the wild by
checking if calling preventDefault had any effect via the
defaultPrevented
property.
We've found that a large majority of impacted pages are fixed relatively easily
by applying the
touch-action
CSS property whenever possible. If you wish to prevent all browser scrolling and
zooming within an element apply touch-action: none to it. If you have a
horizontal carousel consider applying touch-action: pan-y pinch-zoom to it so
that the user can still scroll vertically and zoom as normal. Applying
touch-action correctly is already necessary on browsers such as desktop Edge
that support Pointer Events and not Touch Events. For mobile Safari and older
mobile browsers that don't support touch-action your touch listeners must
continue calling preventDefault even when it will be ignored by Chrome.
In more complex cases it may be necessary to also rely on one of the following:
If your touchstart listener calls preventDefault(), ensure
preventDefault() is also called from associated touchend listeners to continue
suppressing the generation of click events and other default tap behavior.
Last (and discouraged) pass {passive: false} to addEventListener() to
override the default behavior. Be aware you will have to feature detect if the
User Agent supports
EventListenerOptions.
Conclusion
In Chrome 56 scrolling starts substantially faster on many websites. This is the
only impact that most developers will notice as a result of this change. In some
cases developers may notice unintended scrolling.
Although it's still necessary to do so for mobile Safari, websites should not
rely on calling preventDefault() inside of touchstart and touchmove
listeners as this is no longer guaranteed to be honored in Chrome. Developers
should apply the touch-action CSS property on elements where scrolling and
zooming should be disabled to notify the browser before any touch events occur.
To suppress the default behavior of a tap (such as the generation of a click
event), call preventDefault() inside of a touchend listener.
Ever wanted to build your own self-contained JavaScript component, that you can
easily use across multiple projects or share with other developers regardless of
what JavaScript framework they use? Web Components may be for you.
Web Components are a set of new web platform features that let you create your
own HTML elements. Each new custom element can have a custom tag like
<my-button>, and have all the goodness of built-in elements - custom elements
can have properties and methods, fire and respond to events, and even have an
encapsulated style and DOM trees to bring along their own look and feel.
By providing a platform-based, low-level component model, Web Components let you
build and share re-usable elements for everything from specialized buttons to
complex views like datepickers. Because Web Components are built with platform
API’s which include powerful encapsulation primitives, you can even use these
components within other JavaScript libraries or frameworks as if they were
standard DOM elements.
You may have heard of Web Components before - an early version of the Web
Components spec - v0 - was shipped in Chrome
33.
Today, thanks to broad collaboration between browser vendors, the
next-generation of the Web Components spec - v1 - is gaining wide support.
Chrome supports the two major specs that make up Web Components - Shadow
DOM and Custom
Elements
To define a new custom element using the v1 implementation, you simply create a
new class that extends HTMLElement using ES6 syntax and register it with the
browser:
class MyElement extends HTMLElement {...}
window.customElements.define('my-element', MyElement);
The new v1 specs are extremely powerful - we’ve put together tutorials on using
Custom Elements v1
and Shadow DOM v1 to help
you get started.
webcomponents.org
The Web Component community is also growing in leaps and bounds. We’re excited
to see the launch of an updated
webcomponents.org site - the focal point of
the web components community - including an integrated catalog for developers to
share their elements.
You can get started building your first element using a library like Google’s
Polymer, or just use the low-level Web
Component API’s directly. Then publish your
element on webcomponents.org.
I’m Pete LePage, let’s dive in and see what’s new for developers in Chrome 56.
Web Bluetooth API
Until now, the ability to interact with bluetooth devices has only been
possible for native apps. With Chrome 56, your web apps can communicate
with nearby Bluetooth Low Energy devices in a secure and private manner
using the Web Bluetooth API.
The Web Bluetooth API uses the GATT protocol,
which enables your app to connect to devices such as light bulbs, toys,
heart-rate monitors, LED displays and more with just a
few lines of JavaScript.
Web Bluetooth can also be combined with physical web beacons to discover and control nearby devices.
Previously, building content headers that scrolled normally until sticking
to the top of the viewport required listening to scroll events and
switching an element’s position from relative to fixed at a specified threshold.
It was difficult to synchronize, and often results in small visual jumps.
Chrome now supports CSS
position: sticky;,
a new way to position elements.
An element that is position sticky, starts relative; but becomes fixed,
after the element reaches a certain scroll position.
Simply set position: sticky, and set a threshold for it to become sticky.
h3 {
/* Element will be 'fixed' when it ... */
position: sticky;
/* ... is 10px from the top of the viewport */
top: 10px;
}
Last August, we announced that we’d be
moving to HTML5 By Default
to offer a safer, more power-efficient experience. This change disables Adobe
Flash Player unless there’s a user indication that they want Flash content on
specific sites, and eventually all websites will require the user’s permission
to run Flash.
If you want to stay up to date with Chrome and know what’s coming, be sure to
subscribe, follow
@ChromiumDev on Twitter and be sure to check
out the videos from the Chrome Dev Summit
for a deeper dive into some of the awesome things the Chrome team is working on.
I’m Pete LePage, and as soon as Chrome 57 is released, I’ll be right here to tell you -- what’s new in Chrome!
If you read this far, you deserve to see the blooper reel from
New in Chrome 52! I felt like every
time I opened my mouth, a truck would drive by, a helicopter would fly
over, a car would honk it's horn.
Oh, and a big thanks to Andrew for lending me his shirt! I had a bit of a
wardrobe malfunction.
CSS Grid – Table layout is back. Be there and be square.
TL;DR: If you are familiar with Flexbox, Grid should feel familiar.
Rachel Andrew maintains a great
website dedicated to CSS Grid to help you get
started. Grid is now available in Google Chrome.
Flexbox? Grid?
Over the past few years, CSS Flexbox has become widely used and
browser support is looking really good
(unless you are one of the poor souls that have to support IE9 and below).
Flexbox made a lot of complex layout tasks easier, like equi-distant spacing
between elements, top-to-bottom layouts or the holy grail of CSS wizardry:
vertical centering.
But alas, screens commonly have a second dimension we need to worry about.
Short of taking care of sizing the elements yourself, sadly, you can't have
both a vertical and horizontal rhythm by just using flexbox alone. This is
where CSS Grid comes to the rescue.
CSS Grid has been in development, behind a flag in most browsers for over
5 years and extra time has been spent on interoperability to avoid a
buggy launch like Flexbox had. So if you use Grid to implement your layout in
Chrome, chances are you'll get the same result in Firefox and Safari. At the
time of writing, Microsoft's Edge implementation of Grid is out of date (the
same as was already present in IE11.) and the update is
"under consideration".
Despite the similarities in concept and syntax, don't think of Flexbox and Grid
as competing layout techniques. Grid arranges in two dimensions, while Flexbox
lays out in one. There is synergy when using the two together.
Defining a grid
To get familiar with the individual properties of Grid I heartily recommend
Rachel Andrew’s Grid By Example or CSS Tricks'
Cheat Sheet. If you
are familiar with Flexbox, a lot of the properties and their meaning should be
familiar.
Let's take a look at a standard 12-column grid layout. The classic 12-column
layout is popular as the number 12 is divisible by 2, 3, 4 and 6, and is
therefore useful for many designs. Let's implement this layout:
In our stylesheet we start by expanding our body so it covers the entire
viewport and turning it into a grid container:
html, body {
width: 100vw;
min-height: 100vh;
margin: 0;
padding: 0;
}
body {
display: grid;
}
Now we are using CSS Grid. Hooray!
The next step is to implement the rows and columns of our grid. We could
implement all 12 columns in our mockup, but as we are not using every column,
doing this would make our CSS unnecessarily messy. For the sake of simplicity,
we'll implement the layout this way:
The header and the footer are variable in width and the content is variable in
both dimensions. The nav will be variable in both dimensions as well, but we are
going to impose a minimum width of 200px on it. (Why? To show off the features of
CSS Grid, of course.)
In CSS Grid, the set of columns and rows are called tracks. Let's start with
defining our first set of tracks, the rows:
body {
display: grid;
grid-template-rows: 150px auto 100px;
}
grid-template-rows takes a sequence of sizes that define the individual rows.
In this case, we give the first row a height of 150px and the last one of 100px.
The middle row is set to auto which means it will adjust to the necessary
height to accommodate the grid items (the children of the grid container) in
that row. Since our body is stretched across the entire viewport, the track
containing the content (yellow in the picture above) will at least fill all
available space, but will grow (and make the document scroll) if that's
necessary.
For the columns we want to take a more dynamic approach: we want both nav and
content to grow (and shrink), but we want nav to never shrink below 200px
and we want content to be larger than nav. In Flexbox we would use flex-grow and
flex-shrink, but in Grid it's a little different:
body {
display: grid;
grid-template-rows: 150px auto 100px;
grid-template-columns: minmax(200px, 3fr) 9fr;
}
We are defining 2 columns. The first column is defined using the minmax()
function, which takes 2 values: The minimum and the maximum size of that track.
(It's like min-width and max-width in one.) The minimum width is, as we
discussed before, 200px. The maximum width is 3fr. fr is a grid-specific
unit that allows you distribute available space to the grid elements.
(fr probably stands for "fraction unit", but might also mean "free unit" soon.)
Our values here mean that both columns will grow to fill the screen, but the
content column will always be 3 times as wide as the nav column (provided the
nav column stays wider than 200px).
While the placement of our grid items is not correct yet, the size of the
rows and columns behaves correctly and yields the behavior we were aiming for:
Placing the items
One of the most powerful features of Grid is to be able to place items without
regard to the DOM order. (Although, because screen readers navigate the DOM,
we highly recommend that to be properly accessible you should be mindful of how
you reorder elements.) If no manual placement is done, the elements are placed
in the Grid in DOM order, arranged left to right and top to bottom. Each element
occupies one cell. The order in which the grid is filled can be changed using
grid-auto-flow.
So, how do we place elements? Arguably the easiest way to place grid items is by
defining which columns and rows they cover. Grid offers two syntaxes to do this:
In the first syntax you define start and end points. In the second one you define
a start point and a span:
We want our header to start in the first column and end before the 3rd column.
Our nav should start in the second row and span 2 rows in total.
Technically, we are done implementing our layout, but I want to show you a few
convenience features that Grid provides for you to make placement easier. The
first feature is that you can name your track borders and use those names for
placement:
The code above will yield the same layout as the code before.
Even more powerful is the feature of naming entire regions in your grid:
body {
display: grid;
grid-template-rows: 150px auto 100px;
grid-template-columns: minmax(200px, 3fr) 9fr;
grid-template-areas: "header header"
"nav content"
"nav footer";
}
header {
grid-area: header;
}
nav {
grid-area: nav;
}
grid-template-areas takes a string of space-separated names, allowing the
developer to give each cell a name. If two adjacent cells have the same name,
they are going to be coalesced to the same area. This way you can provide more
semantics to your layout code and make media queries more intuitive. Again, this
code will generate the same layout as before.
Is there more?
Yes, yes there is, way too much to cover in a single blog post.
Rachel Andrew, who is also a
GDE, is an Invited
Expert in the CSS Working Group and has been working with them from the very start
to make sure Grid simplifies web design. She even wrote a
book on it. Her
website Grid By Example is a valuable
resource for getting familiar with Grid. Many people think Grid is a
revolutionary
step for web design and it is now enabled by default in Chrome so you can start
using it today.
Since the launch of the Payment Request API in Chrome 53, a few changes have
been made to the API. These changes won't break the functionalities of your
working code, but we recommend you to add a
shim to your code so
that future changes won't break your product.
The first argument to PaymentRequest() constructor is an array of payment
method data. The PaymentMethodData format has been altered in this release.
Old - still works for the time being.
var methodData = [{
supportedMethods: ['visa', 'mastercard', 'amex', 'jcb']
}];
var request = new PaymentRequest(methodData, details, options);
New - the new structure.
var methodData = [{
supportedMethods: ['basic-card'],
data: {
supportedNetworks: ['visa', 'mastercard', 'amex', 'jcb',
'diners', 'discover', 'mir', 'unionpay']
}
}];
var request = new PaymentRequest(methodData, details, options);
The format of the data property depends on the value in supportedMethods and is
based on the Basic Card
specification. Note that the
spec includes
supportedTypes which accepts credit, debit or prepaid, but Chrome 57
ignores this property and treats any values in supoprtedNetworks as credit
cards.
var methodData = [{
supportedMethods: ['basic-card'],
data: {
supportedNetworks: ['visa', 'mastercard', 'amex', 'jcb',
'diners', 'discover', 'mir', 'unionpay'],
supportedTypes: ['credit'] <= not available
}
}];
Chrome 56
pending
As part of payment item
information,
developers can add pending to indicate that the price is not fully determined
yet. The pe nding fieldaccepts a boolean value.
{
label: "State tax",
amount: { currency: "USD", value : "5.00" },
pending: true
},
This is commonly used to show line items such as shipping or tax amounts that
depend on selection of shipping address or shipping options. Chrome indicates
pending fields in the UI for the payment request.
requestPayerName
As part of shipping
option
(third argument to PaymentRequest), developers can now add requestPayerName
to request the payer's name separate from shipping address information.
requestPayerName accepts a boolean value.
shippingType
As part of shipping
option
(third argument to PaymentRequest), developers can now add shippingType to
request that the UI show "delivery" or "pickup" instead of "shipping".
shippingType accepts the strings shipping (default), delivery, or
pickup.
Serializer functions available to PaymentResponse and PaymentAddress
PaymentResponse
object
and PaymentAddress object are now JSON-serializable. Developers can convert
one to a JSON object by calling the toJSON() function and avoid creating
cumbersome toDict() functions.
request.show().then(response => {
let res = response.toJSON();
});
canMakePayment
In addition to the API availability, you can check to see if a user has an active
payment method before invoking the Payment Request API. Remember that this is
optional as users can still add a new payment method on the payment UI.
let request = new PaymentRequest(methods, details, options);
if (request.canMakePayment) {
request.canMakePayment().then(result => {
if (result) {
// Payment methods are available.
} else {
// Payment methods are not available, but users can still add
// a new payment method in Payment UI.
}
}).catch(error => {
// Unable to determine.
});
}
In nearly every version of Chrome, we see a significant number of updates and
improvements to the product, its performance, and also capabilities of the Web
Platform. This article describes the deprecations and removals in Chrome 57,
which is in beta as of early February. This list is subject to change at any
time.
Deprecate case-insensitive matching for usemap attribute
The usemap attribute was formerly defined as caseless. Unfortunately
implementing this was complicated enough that no browsers implemented it
correctly. Research suggested that such complicated algorithm is unnecessary,
and even ASCII case-insensitive matching is unnecessary.
Consequently, the specification was updated so that case-sensitive matching is
applied. The old behavior is deprecated in Chrome 57, with removal expected in
Chrome 58.
Deprecate and remove webkitCancelRequestAnimationFrame
The webkitCancelRequestAnimationFrame() method is a an obsolete,
vendor-specific API and the standard cancelAnimationFrame() has long
been supported in Chromium. Therefore the webkit version is being removed.
Prefixed resource timing buffer-management API (removed)
Two methods and an event handler, webkitClearResourceTimings(),
webkitSetResourceTimingBufferSize(), and onwebkitresourcetimingbufferfull
are obsolete and vendor-specific. The
standard versions of these APIs
have been supported in since Chrome 46. These features were originally
implemented in WebKit, but Safari has not enabled them. Firefox, IE 10+, and
Edge have only unprefixed version of the API. Therefore the webkit versions
are being removed.
The BluetoothDevice.uuids attribute is being removed to bring the
Bluetooth API in
line with the current specification. You can retrieve a UUID by calling
device.watchAdvertisements().
Since Chrome 49, <keygen>'s default behaviour has been to return the empty
string, unless a permission was granted to this page. IE/Edge do not support
<keygen> and have not indicated public signals to support <keygen>.
Firefox already gates <keygen> behind a user gesture, but is publicly
supportive of removing it. Safari ships <keygen> and has not expressed
public views regarding its continued support. With Chrome 57, this element
is removed.
The IndexedDB entry point and global constructors were exposed with webkit
prefixes somewhere around Chrome 11. The non-prefixed versions were added in
Chrome 24 and the prefixed versions were deprecated in Chrome 38. The
following interfaces are affected:
webkitIndexedDB (main entry point)
webkitIDBKeyRange (non-callable global constructor, but has useful static methods)
webkitIDBCursor
webkitIDBDatabase
webkitIDBFactory
webkitIDBIndex
webkitIDBObjectStore
webkitIDBRequest
webkitIDBTransaction (non-callable global constructors)
WebAudio: Remove prefixed AudioContext and OfflineAudioContext
Chrome has supported WebAudio since mid 2011, including AudioContext.
OfflineAudioContext was added the following year. Given how long the
standard interfaces and Googles long-term goal of removing prefixed features,
the prefixed versions of these interfaces have been deprecated since late
2014 and are now being removed.
Customize Media Notifications and Handle Playlists
With the brand new Media Session API, you can now customize media
notifications by providing metadata for the media your web app is
playing. It also allows you to handle media related events such as seeking
or track changing which may come from notifications or media keys. Excited? Try
out the official Media Session sample.
The Media Session API is supported in Chrome 57 (beta in February 2017, stable
in March 2017).
Gimme what I want
You already know about the Media Session API and are simply coming back to
copy and paste with no shame some boilerplate code? So here it is.
Note: I could have used a <video> element instead to showcase the Media Session
API.
As you may know, autoplay is disabled for audio elements on Chrome
for Android which means we have to use the play() method of the audio
element. This method must be triggered by a user gesture such as a touch or a
mouse click.
That means listening to
pointerup, click, and touchend
events. In other words, the user must click a button before your web app can
actually make noise.
playButton.addEventListener('pointerup', function(event) {
let audio = document.querySelector('audio');
// User interacted with the page. Let's play audio...
audio.play()
.then(_ => { /* Set up media session... */ })
.catch(error => { console.log(error) });
});
Note: If the <audio> element has the controls attribute, you can simply set
up the media session in the audio play event listener instead which occurs
when user taps the "play" audio control.
If you don't want to play audio right after the first interaction, I recommend
you use the load() method of the audio element. This is one way for the
browser to keep track of whether the user interacted with the element. Note
that it may also help smooth the playback because the content will already
be loaded.
let audio = document.querySelector('audio');
welcomeButton.addEventListener('pointerup', function(event) {
// User interacted with the page. Let's load audio...
audio.load()
.then(_ => { /* Show play button for instance... */ })
.catch(error => { console.log(error) });
});
// Later...
playButton.addEventListener('pointerup', function(event) {
audio.play()
.then(_ => { /* Set up media session... */ })
.catch(error => { console.log(error) });
});
Customize the notification
When your web app is playing audio, you can already see a media notification
sitting in the notification tray. On Android, Chrome does its best to show
appropriate information by using the document's title and the largest icon
image it can find.
Set metadata
Let's see how to customize this media notification by setting some media
session metadata such as the title, artist, album name, and artwork with the
Media Session API.
Once playback is done, you don't have to "release" the media session as the
notification will automatically disappear. Keep in mind that current
navigator.mediaSession.metadata will be used when any playback starts. This
is why you need to update it to make sure you're always showing relevant
information in the media notification.
Previous track / next track
If your web app provides a playlist, you may want to allow the user to navigate
through your playlist directly from the media notification with some "Previous
Track" and "Next Track" icons.
let audio = document.createElement('audio');
let playlist = ['audio1.mp3', 'audio2.mp3', 'audio3.mp3'];
let index = 0;
navigator.mediaSession.setActionHandler('previoustrack', function() {
// User clicked "Previous Track" media notification icon.
index = (index - 1 + playlist.length) % playlist.length;
playAudio();
});
navigator.mediaSession.setActionHandler('nexttrack', function() {
// User clicked "Next Track" media notification icon.
index = (index + 1) % playlist.length;
playAudio();
});
function playAudio() {
audio.src = playlist[index];
audio.play()
.then(_ => { /* Set up media session... */ })
.catch(error => { console.log(error); });
}
playButton.addEventListener('pointerup', function(event) {
playAudio();
});
Note that media action handlers will persist. This is very similar to the event
listener pattern except that handling an event means that the browser stops
doing any default behaviour and uses this as a signal that your web app
supports the media action. Hence, media action controls won't be shown unless
you set the proper action handler.
By the way, unsetting a media action handler is as easy as assigning it to null.
Seek backward / seek forward
The Media Session API allows you to show "Seek Backward" and "Seek Forward"
media notification icons if you want to control the amount of time skipped.
let skipTime = 10; // Time to skip in seconds
navigator.mediaSession.setActionHandler('seekbackward', function() {
// User clicked "Seek Backward" media notification icon.
audio.currentTime = Math.max(audio.currentTime - skipTime, 0);
});
navigator.mediaSession.setActionHandler('seekforward', function() {
// User clicked "Seek Forward" media notification icon.
audio.currentTime = Math.min(audio.currentTime + skipTime, audio.duration);
});
Play / pause
The "Play/Pause" icon is always shown in the media notification and the related
events are handled automatically by the browser. If for some reason the default
behaviour doesn't work out, you can still handle "Play" and "Pause" media events.
navigator.mediaSession.setActionHandler('play', function() {
// User clicked "Play" media notification icon.
// Do something more than just playing current audio...
});
navigator.mediaSession.setActionHandler('pause', function() {
// User clicked "Pause" media notification icon.
// Do something more than just pausing current audio...
});
Note: The browser may consider that the web app is not playing media when files
are seeking or loading. You can override this behaviour by setting
navigator.mediaSession.playbackState to "playing" or "paused". This
comes in handy when you want to make sure your web app UI stays in sync with
the media notification controls.
Notifications everywhere
The cool thing about the Media Session API is that the notification tray is not
the only place where media metadata and controls are visible. The media
notification is synced automagically to any paired wearable device. And it also
shows up on lock screens.
Make it play nice offline
I know what you're thinking now... service worker to the rescue!
True but first and foremost, you want to make sure all items in this
checklist are checked:
All media and artwork files are served with the appropriate Cache-Control
HTTP header. This will allow the browser to cache and reuse previously fetched
resources. See the Caching checklist.
Make sure all media and artwork files are served with the
Allow-Control-Allow-Origin: * HTTP header. This will allow third-party web
apps to fetch and consume HTTP responses from your web server.
For artwork though, I'd be a little bit more specific and choose the approach
below:
If artwork is already in the cache, serve it from the cache
Else fetch artwork from the network
If fetch is successful, add network artwork to the cache and serve it
Else serve the fallback artwork from the cache
That way, media notifications will always have a nice artwork icon even when
browser can't fetch them. Here's how you could implement this:
const FALLBACK_ARTWORK_URL = 'fallbackArtwork.png';
self.addEventListener('install', event => {
self.skipWaiting();
event.waitUntil(initArtworkCache());
});
function initArtworkCache() {
caches.open('artwork-cache-v1')
.then(cache => cache.add(FALLBACK_ARTWORK_URL));
}
self.addEventListener('fetch', event => {
if (/artwork-[0-9]+\.png$/.test(event.request.url)) {
event.respondWith(handleFetchArtwork(event.request));
}
});
function handleFetchArtwork(request) {
// Return cache request if it's in the cache already, otherwise fetch
// network artwork.
return getCacheArtwork(request)
.then(cacheResponse => cacheResponse || getNetworkArtwork(request));
}
function getCacheArtwork(request) {
return caches.open('artwork-cache-v1')
.then(cache => cache.match(request));
}
function getNetworkArtwork(request) {
// Fetch network artwork.
return fetch(request)
.then(networkResponse => {
if (networkResponse.status !== 200) {
return Promise.reject('Network artwork response is not valid');
}
// Add artwork to the cache for later use and return network response.
addArtworkToCache(request, networkResponse.clone())
return networkResponse;
})
.catch(error => {
// Return cached fallback artwork.
return getCacheArtwork(new Request(FALLBACK_ARTWORK_URL))
});
}
function addArtworkToCache(request, response) {
return caches.open('artwork-cache-v1')
.then(cache => cache.put(request, response));
}
Caution: If you want your service worker to be able to intercept artwork
network requests on the very first page load, you may want to call
clients.claim() within your service worker once it's activated.
Let user control cache
As the user consumes content from your web app, media and artwork files may
take a lot of space on their device. It is your responsibility to show how
much cache is used and give users the ability to clear it. Thankfully for us,
doing so is pretty easy with the Cache API.
// Here's how I'd compute how much cache is used by artwork files...
caches.open('artwork-cache-v1')
.then(cache => cache.matchAll())
.then(responses => {
let cacheSize = 0;
let blobQueue = Promise.resolve();
responses.forEach(response => {
let responseSize = response.headers.get('content-length');
if (responseSize) {
// Use content-length HTTP header when possible.
cacheSize += Number(responseSize);
} else {
// Otherwise, use the uncompressed blob size.
blobQueue = blobQueue.then(_ => response.blob())
.then(blob => { cacheSize += blob.size; blob.close(); });
}
});
return blobQueue.then(_ => {
console.log('Artwork cache is about ' + cacheSize + ' Bytes.');
});
})
.catch(error => { console.log(error); });
// And here's how to delete some artwork files...
const artworkFilesToDelete = ['artwork1.png', 'artwork2.png', 'artwork3.png'];
caches.open('artwork-cache-v1')
.then(cache => Promise.all(artworkFilesToDelete.map(artwork => cache.delete(artwork))))
.catch(error => { console.log(error); });
Implementation notes
Chrome for Android requests "full" audio focus to show media notifications
only when the media file duration is at least 5 seconds.
If no artwork is defined and there is an icon image at a desirable size, media
notifications will use it.
Notification artwork size in Chrome for Android is 512x512. For
low-end devices, it is 256x256.
Dismiss media notifications with audio.src = ''.
As the Web Audio API doesn't request Android Audio Focus for historical
reasons, the only way to make it work with the Media Session API is to hook
up an <audio> element as the input source to the Web Audio API. Hopefully,
the proposed Web AudioFocus API will improve the situation in the
near future.
Support
At the time of writing, Chrome for Android is the only platform that supports
the Media Session API. More up-to-date information on browser implementation
status can be found on Chrome Platform Status.
Chrome first introduced the "Add to Home screen"
banners in Chrome 51.
This was a big step for the web as it provided users the ability to easily keep
a favorite site on their Home screen, much like native apps. We've heard from
developers like Alibaba that users re-engage 4 times more
often with their site added to Home screen. We've
also seen that tuning the heuristics for add to Home screen to prompt sooner
yields to 48% more installs.
We are happy to share that the team has worked on an improved add to Home
screen experience that makes web apps first-class citizens of Android.
Instead of simply being a shortcut icon, web apps will now be integrated with
Android. This means that users that add a PWA to their Home screen will be able
to find it anywhere they see other apps (e.g. in the app drawer or searching for
apps), and open the site from intents. We see this as the first step among a
number of improvements to come and intend to make it the default experience for
add to Home screen in the coming months.
The improved add to Home screen experience is already available in Chrome Canary
and will be rolling out to Chrome 57 beta over the next few weeks.
You can test your site by following these steps:
Install the latest Chrome
Dev or Chrome
Beta from the
Play Store if you don't have it already.
Enable improved add to Home screen. These steps are only needed during the
developer preview.
Open chrome://flags in Chrome dev and enable the flag
#enable-improved-a2hs ("Find in page" in the triple-dot menu is helpful
for finding it.) You'll be prompted to restart Chrome.
Once Chrome is restarted, you'll be prompted to go to settings to turn on
"Unknown sources" if it's not enabled already. (In general you shouldn't
have this enabled, so we recommend disabling it when you're done
testing.) If you don't see the prompt you can find it in Android Settings
> Security > Device Administration.
Visit your PWA. You can start install from the three dot menu > "Add to
Home screen" or through the Add to Home screen banner.
In the developer preview you'll additionally see a prompt from the
package installer to confirm.
This new experience is a huge improvement over the original version of Add to Home
screen, but there are some differences between these installed Progressive Web
Apps and Android Apps.
Updating your App's icon and name
You now have the ability to update your Progressive Web App's icon and name and
have it reflected to the user. Changing your icon or name in the manifest will
update the icon on the Home screen after the user has subsequently opened the
site.
Android Intent Filters
When a Progressive Web App is installed via this new Improved Add to Home screen
experience it will be registered with the system to be a target for the URL
space for its domain. This means that the when a user clicks on a link that is
contained within the scope of your Progressive Web App, your app will be opened
up instead of Chrome with your PWA running.
The scope is property in the Web App manifest and it defaults to the origin. You
can set it to an path that is relative to your origin and subsequently when a
user navigates to a URL contained by the scope your installed Progressive Web
App will be open.
Note: directly navigating to your site from the address bar will work exactly
the same as it does for native apps that have an intent filter, Chrome assumes
that the user intended to visit the site and will open the site.
Managing Permissions
By Installing your Progressive Web App it now becomes part of the system. Added
sites show up on the Home screen, app drawer and throughout the Android
System-UI as a user would expect. Permissions are handled differently, by
default your app can only have the same permissions surface as Chrome would
normally have when installed - you can't ask for Camera access at install time
for example. This means that as developer you must request permission for
sensitive API's such as Camera and Microphone access, notifications etc at
runtime as you would for any normal web site and the Chrome runtime will prompt
you for access.
Android normally gives instant access notifications, Installed Progressive Web
Apps do not have this permission granted by default and your user must
explicitly opt-in to receiving notifications
Storage and App State
When the user adds your Progressive Web App to their system Chrome will use the
same profile and will not segregate the data. This means your service worker
will already be installed, your cookies still active any client-side storage
will be still stored the next time that the user opens the App.
This can cause some issues because if the user clears the Chrome profile, then
your data in your app will also be cleared. To ensure that your user data is
held more permanently, please use the Persistent
Storage API.
Please let us know if you have any feedback or questions. If you encounter a
bug, you can file it on the Chromium bug tracker
here%0A(2)%0A(3)%0A%0AExpected%20result%3A%0A%0A%0AActual%20result%3A%0A).
Please also take a look at FAQs below that aim to answer any additional
questions you might have.
FAQs
What are the requirements for a site to use improved add to Home screen?
Note though that there is no engagement threshold for improved add to Home
screen from the menu.
Does this change the triggering of the add to Home screen banner?
Improved add to Home screen does not itself change the triggering or behavior of
the banner. Nevertheless, Chrome has recently lowered the site-engagement
threshold for the banner to trigger and is constantly experimenting with
improvements to this system. (See the
keynote from Chrome Dev
Summit.)
What happens to users who have already added a site to their Home screen?
Users will continue to get the existing add to Home screen experience, though if
they add it again manually via the menu button, the new icon will use improved
add to Home screen.
What will happen to the current add to Home screen experience?
Improved add to Home screen will replace add to Home screen for PWAs. There is
no change to the existing functionality of add to Home screen for non-PWAs.
What happens if the user has already installed the native app for the site?
Like add to Home screen today, users will be able to add a site independent of
any native apps. If you expect users to potentially install both, we recommend
differentiating the icon or name of your site from your native app.
When a user opens a site installed via improved add to Home screen, will Chrome be running?
Yes, once the site is opened from the Home screen the primary activity is still
Chrome. Cookies, permissions, and all other browser state will be shared.
Will my installed site's storage be cleared if the user clears Chrome's cache?
Yes.
Will I get auto granted Push Notifications permissions like I do in Android?
No, but we intend to experiment with this at a later date. (See the
keynote from Chrome Dev
Summit.)
Will I be able to register to handle custom URL schemes and protocols?
No.
Will I be able to register to open files of a specific type (i.e, PDF)?
How are permissions handled? Will I see the Chrome prompt or Android's?
Permissions will still be managed through Chrome. Users will see the Chrome
prompts to grant permissions and will be able to edit them in Chrome settings.
What versions of Android will this work on?
The feature is supported wherever Chrome is, back to Android Jelly Bean.
Does this use the WebView?
No, the site opens in the version of Chrome the user added the site from.
If I update my site's service worker, will this update automatically in the background even without the user visiting the site?
No. The update to the SW will be processed the next time that the user visits
the page.
Lighthouse is an
open-source, automated tool for
improving the quality of your web apps. You can install it as a
[Chrome Extension][crx] or run it as a Node command line tool. When you
give Lighthouse a URL, it runs a barrage of tests against the page and then
generates a report explaining how well the page did and indicating areas for
improvement.
Today, we're happy to announce the
1.5 release
of Lighthouse, a huge release, with over 128 PRs. Lighthouse 1.5 includes
a bunch of big new features, audits, and the usual bug fixes. You can install
it from npm (npm i -g lighthouse) or
download the extension
from the Chrome Web Store.
New Audits
The CSS Usage Audit reports the number of unused style rules in your page
and the cost/time savings of removing them:
The Optimized Images Audit reports images that are unoptimized and the
cost/time savings of optimizing them:
The Responsive Images Audit reports images that are too big and the
potential cost/time savings of sizing them correctly for the given device:
The Deprecations and Interventions Audit lists console warnings from Chrome
if your page is using deprecated APIs or features that have
interventions:
Report changes
As you've seen, we've focused on making the report less visually cluttered by
adding tabular data, hiding extraneous help text, and adding new features to
make it easier to navigate the data.
Emulation settings
It's easy to forget what throttling and emulation settings were used for a
particular Lighthouse run. Lighthouse reports now include the
runtime emulation settings that were used to create the report; a
long time feature request:
More useful trace data
Lighthouse metrics like "First meaningful paint", "Time to Interactive", etc are
mocked out as User Timing measures and injected back into the trace data saved
with the --save-assets flag.
If you use the --save-assets flag, you can now drop the trace into DevTools or
open it in the DevTools Timeline Viewer.
You'll be able to see your key metrics in context with the full trace of the
page load.
Lighthouse Viewer
In HTML reports, you'll notice a new button with options for exporting the
report in different formats. One of those options is "Open in Viewer". Clicking
this button will send the report to the online
Viewer, where you can
further share the report with GitHub users.
Behind the scenes, Viewer gets your permission via OAuth to create a GitHub
secret gist and
saves the report there. Since it's done as your Gist, you maintain full control
over the sharing of the report and you can delete it at any time. You can revoke
the Viewer's permission to create gists under your
GitHub settings.
Performance Experiment
The first version of the
Performance Experiment
project has landed in 1.5.0. This lets you experiment with your page load performance,
interactively testing the effects of blocking or delaying assets in your critical
path during development.
When Lighthouse is run with the --interactive flag, a special report is
generated that allows interactive selection of costly page resources. The
experiment server then reruns Lighthouse on that page with those resources
blocked.
Learn more
about the Performance Experiment in Lighthouse.
For all the details on the latest in Lighthouse, see the
full release notes
over on Github. As always,
hit us up
to report bugs, file
feature requests, or brainstorm
ideas
on what you'd like to see next.
A new experimental feature, navigation preload, fixes this by
allowing you to make the request in parallel with service worker boot-up.
You can distinguish preload requests from regular navigations using a header,
and serve different content.
Navigation preload is in Chrome 57 Canary behind a flag, and the API may change in
response to developer feedback.
It'll remain behind a flag in Chrome 57 stable (likely to be released in
March), but you can apply for an origin trial to test it with
real users.
The problem
When you navigate to a site that uses a service worker to handle fetch events,
the browser asks the service worker for a response. This involves booting up the
service worker (if it isn't already running), and dispatching the fetch event.
The bootup time depends on the device and conditions. It's usually around 50ms.
On mobile it's more like 250ms. In extreme cases (slow devices, CPU in
distress) it can be over 500ms. However, since the service worker stays awake
for a browser-determined time between events, you only get this delay
occasionally, such as when the user navigates to your site from a fresh tab, or
another site.
The boot-up time isn't a problem if you're responding from the cache, as the
benefit of skipping the network is greater than the boot-up delay. But if you're
responding using the network…
SW boot
Navigation request
…the network request is delayed by the service worker booting-up.
Facebook brought the impact of this problem to our attention, and asked for a
way to perform navigation requests in parallel:
SW boot
Navigation request
…and we said "yeah, seems fair".
"Navigation preload" to the rescue
Navigation preload is a new experimental feature that lets you say, "Hey, when
the user makes a GET navigation request, start the network request while
the service worker is booting up.
The startup delay is still there, but it doesn't block the network request, so
the user gets content sooner.
Here's a video of it in action, where the service worker is given a deliberate
500ms startup delay using a while-loop:
Here's the demo
itself.
To get the benefits of navigation preload, you'll need Chrome 57
Canary with
chrome://flags/#enable-service-worker-navigation-preload enabled.
You can call navigationPreload.enable() whenever you want, or disable it with
navigationPreload.disable(). However, since your fetch event needs to make
use of it, it's best to enable/disable it in your service worker's activate
event.
Using the preloaded response
Now the browser will be performing preloads for navigations, but you still need
to use the response:
addEventListener('fetch', event => {
event.respondWith(async function() {
// Respond from the cache if we can
const cachedResponse = await caches.match(event.request);
if (cachedResponse) return cachedResponse;
// Else, use the preloaded response, if it's there
const response = await event.preloadResponse;
if (response) return response;
// Else try the network.
return fetch(event.request);
}());
});
event.preloadResponse is a promise that resolves with a response, if:
Navigation preload is enabled.
The request is a GET request.
The request is a navigation request (which browsers generate when they're
loading pages, including iframes).
Otherwise event.preloadResponse is still there, but it resolves with
undefined.
Warning: Don't enable navigation preload then forget to use it. If you use
fetch(event.request) instead of event.preloadResponse, you'll end up with
double requests for navigations.
Custom responses for preloads
If your page needs data from the network, the quickest way is to request it in
the service worker and create a single streamed response containing parts from
the cache and parts from the network.
Say we wanted to display an article:
addEventListener('fetch', event => {
const url = new URL(event.request.url);
const includeURL = new URL(url);
includeURL.pathname += 'include';
if (isArticleURL(url)) {
event.respondWith(async function() {
// We're going to build a single request from multiple parts.
const parts = [
// The top of the page.
caches.match('/article-top.include'),
// The primary content
fetch(includeURL)
// A fallback if the network fails.
.catch(() => caches.match('/article-offline.include')),
// The bottom of the page
caches.match('/article-bottom.include')
];
// Merge them all together.
const {done, response} = await mergeResponses(parts);
// Wait until the stream is complete.
event.waitUntil(done);
// Return the merged response.
return response;
}());
}
});
In the above, mergeResponses is a little
function
that merges the streams of each request. This means we can display the cached
header while the network content streams in.
This is quicker than the "app shell" model as the network request is made along
with the page request, and the content can stream without major
hacks.
However, the request for includeURL will be delayed by the service worker's
startup time. We can use navigation preload to fix this too, but in this case we
don't want to preload the full page, we want to preload an include.
To support this, a header is sent with every preload request:
Service-Worker-Navigation-Preload: true
The server can use this to send different content for navigation preload
requests than it would for a regular navigation request. Just remember to add a
Vary: Service-Worker-Navigation-Preload header, so caches know that your
responses differ.
Now we can use the preload request:
// Try to use the preload
const networkContent = Promise.resolve(event.preloadResponse)
// Else do a normal fetch
.then(r => r || fetch(includeURL))
// A fallback if the network fails.
.catch(() => caches.match('/article-offline.include'));
const parts = [
caches.match('/article-top.include'),
networkContent,
caches.match('/article-bottom')
];
Note: Promise.resolve(event.preloadResponse) means we get a promise for
undefined if event.preloadResponse is undefined. It's a good way to normalise
behaviour with browsers that don't support event.preloadResponse.
Changing the header
By default, the value of the Service-Worker-Navigation-Preload header is
true, but you can set it to whatever you want:
We're still experimenting with this feature, but we're looking for real-world
feedback. To get feedback, we're making it available as an origin
trial
starting in Chrome 57. Origin trials allow you to temporarily enable the feature
for users of your website, so you can test its real-world impact. To do this,
you'll need to request a token for your
origin,
and include the token as a header on your pages and service worker:
Origin-Trial: token_obtained_from_signup
To avoid the problems we saw with vendor prefixes, origin trials are globally
shut off if usage exceeds 0.03% of all Chrome page loads, so large sites should
only enable the feature for a fraction of its users.
If you do experiment with this feature, you can send us feedback on our mailing
list,
or join the spec discussion.
If you find any bugs, please file an issue, including
the words "service worker navigation preload" in the issue Summary.
Many thanks to Matt Falkenhagen and Tsuyoshi Horo for their work on this
feature, and help with this article. And a huge thanks to everyone involved in
the standardisation
effort
I’m Pete LePage, let’s dive in and see what’s new for developers in Chrome 57!
CSS grid layout
Flexbox is a powerful layout tool. It makes many complex layouts possible,
but it can only do layout in one dimension. Chrome 57 adds support for
display: grid
the new CSS Grid Layout specification,
adding a powerful new tool for creating two-dimensional grid-based layout
systems, optimized for responsive user interface design.
Elements within the grid can span multiple columns or rows. Regions in a
CSS grid can also be named, making layout code easier to understand.
One of the missing features from web media applications has been the
ability to deeply integrate with the core media experience on mobile devices.
In Chrome for Android, you can now customize the lock screen and notifications
with media content using the new Media Session API.
By providing metadata to the
browser about the content being played, you can create rich lock screen
messaging that includes information such as title, artist, album name, and
artwork. You can also listen for and
respond to user actions
taken on the notification itself, such as seeking or skipping.
You can specify the color of the text input cursor with the
caret-color
property.
Visual effects such as line color and style can be specified with new
text-decoration
properties.
The Fetch APIResponse class
now supports the
.redirected
attribute to help avoid untrustworthy responses and reduce the risk of open
redirectors.
All -webkit- prefixed IndexedDB
global aliases have been removed, after
their deprecation in M38.
And one of my favorites — new
padStart
and
padEnd
formatting methods that simplify string padding when aligning console output
or printing numbers with a fixed number of digits.
These are just a few of the changes in Chrome 57 for developers.
Shout out to Igalia
Finally, a big shout out to the engineers and team at
Igalia for their awesome work on Blink. They
were instrumental on landing the new CSS Grid and caret-color features.
Stay up to date
If you want to stay up to date with Chrome and know what’s coming, be sure to
subscribe to our channel, or follow
@ChromiumDev on Twitter. And be sure to check out the
videos from the Chrome Dev Summit
for a deeper dive into some of the awesome things the Chrome team is working on.
I’m Pete LePage, and as soon as Chrome 58 is released, I’ll be right here
to tell you -- what’s new in Chrome!
Welcome to the first installment of the DevTools release notes! From here on
out, the first time you open a new version of Chrome, DevTools opens the
What's New drawer with a link to the release notes for that version.
Highlights
The Timeline panel has been renamed to the Performance panel.
The Profiles panel has been renamed to the Memory panel.
Cookie values are now editable.
DevTools now automatically pauses before out-of-memory errors.
New features
Editable cookies
Double-click on a cell in the Cookies tab to edit that value.
Inspectable and editable CSS variables in the Styles pane
You can now inspect and edit CSS variables in the Styles pane. See CSS
Variables Demo to try it out yourself.
Out-of-memory breakpoints
When an app allocates a lot of memory in a short amount of time, DevTools now
automatically pauses and increases the heap limit. This enables you to inspect
the heap, execute commands on the Console to free up memory, and continue
debugging the issue. See One Small Step For Chrome, One Giant Heap For
V8 for more information.
Breakpoints on canvas creation
You can now create event listener breakpoints
that are triggered whenever a new canvas context is created.
Start time stats in the Timing tab
At the top of the Timing tab, you can now see when a request was queued and
started.
Server stats in the Timing tab
You can now insert custom server statistics into the Timing tab. See
Demo of server timing values for an example.
Changes
The Timeline panel is now the Performance panel
The Timeline panel has been renamed to the Performance panel, to better
reflect its purpose.
The Profiles panel is now the Memory panel
The Profiles panel has been renamed to the Memory panel, to better
reflect its purpose.
The CPU Profiler is behind a hidden panel
Now that the Profiles panel is called the Memory panel, it doesn't really
make sense to have the CPU profiler on that panel anymore. Furthermore,
the long-term goal is to get all users profiling from the Performance panel.
In the meantime, you can still access the old CPU profiler from
Settings > More Tools > JavaScript Profiler.
The Console panel and drawer have undergone some UI changes. Some unpopular features
have been moved to more hidden locations, and popular features are now more
easily accessible.
Click Console Settings to
access settings for customizing the Console's behavior.
Preserve log is now hidden in Console Settings.
The Filters button and pane is gone. Use the dropdown menu instead.
The text box for filtering logs is now always shown. It was previously
hidden in the Filters pane.
The filtering text box automatically accepts RegEx, so the
Regex checkbox is gone.
The Hide violations checkbox is gone. Set the logging level dropdown to
Verbose to see violations.
Unchecking the Show all messages checkbox in the old UI is equivalent
to checking the Selected context only checkbox in Console Settings
in the new UI.
WebGL event listener breakpoints have moved
The WebGL event listener breakpoints
have moved from the WebGL category to the Canvas category. The
WebGL category has been removed.
Background tabs can have a dramatic negative effect on browser performance,
especially on battery life. To mitigate this, Chrome has been placing
various restrictions on background tabs for the last several years.
Recently there’s been a number of efforts to make further improvements,
and this document gives an overview of the Chrome policy.
This document focuses on describing current policies in Chrome 57.
Long-term strategy and further plans can be found in
this document.
Optimising an application for background
Web developers should be aware that users often have a lot of tabs open in the background
and it can have a serious effect on power usage and battery life. Work in the background
should be kept to a minimum unless it’s absolutely necessary to provide
a particular user experience. The
Page visibility API
should be used
to detect when page is the backgrounded and suspend all unnecessary work like visual updates.
For some sites,
this simple optimisation can reduce CPU usage by as much as 75%:
var doVisualUpdates = true;
document.addEventListener('visibilitychange', function(){
doVisualUpdates = !document.hidden;
});
function update() {
if (!doVisualUpdates) {
return;
}
doStuff();
}
Policies
requestAnimationFrame()
Per the documentation,
Chrome does not call requestAnimationFrame() when a page is in the background.
This behavior has been in place since 2011.
Background timer alignment
Since Chrome 11,
each independent timer is run no more than once per second. Chrome runs these timers in batches
once a second, ensuring that the number of process wakeups is kept to a minimum.
Pages playing audible audio are considered user-visible and exempted from background timer
throttling. Exemption lasts for several seconds after audio stops playing to allow
applications to queue the next audio track.
Note that audio is considered audible when and only when Chrome shows the audio icon.
Silent audio streams do not grant exemptions.
Budget-based background timer throttling
Shipping in Chrome 57,
budget-based timer throttling is a further extension of the timer alignment mechanism,
placing an additional limit on background timer CPU usage. It operates as follows:
Each background tab has a time budget (in seconds) for running timers in the background.
A page is subjected to time budget limitations after 10 seconds in the background.
A timer task is allowed to run only when the time budget is non-negative.
After a timer has executed, its run time is subtracted from the budget.
The budget continuously regenerates with time (currently set to a rate of
0.01 seconds per second). Note that this budget regeneration rate can be tweaked as
Chrome collects more data about throttling behavior.
There are a number of automatic exemptions from this throttling:
Applications playing audio are considered foreground and aren’t throttled.
Applications with real-time connections (WebSockets and WebRTC), to avoid closing
these connections by timeout. The run-timers-once-a-second rule is still
applied in these cases.
Note that this his mechanism uses wall time, not CPU time.
It’s a good approximation of CPU time and penalises blocking the main thread for a
long time.
Finally, remember that if you are using long tasks in the background, your application
can be throttled for a very long period of time (up to 100 times the duration of your task).
Split your work in to chunks of 50ms or less per
the performance guidelines
and use the visibilityChange listener to avoid doing unnecessary work in background.
Opt-outs
Chrome provides the --disable-background-timer-throttling flag for use cases like
running test suites and other user-sanctioned heavy computations.
In nearly every version of Chrome, we see a significant number of updates and
improvements to the product, its performance, and also capabilities of the Web
Platform. This article describes the deprecations and removals in Chrome 58,
which is in beta as of March 16. This list is subject to change at any time.
Mouse on Android stops firing TouchEvents
Until Chrome 57, Android low-level mouse events in Chrome primarily followed an
event path designed for touch interactions. For example, mouse drag motion while
a mouse button is pressed generates MotionEvents delivered through
View.onTouchEvent.
But since touch events cannot support hover, hovering mousemoves followed a
separate path. The design had many side-effects including mouse interactions
firing TouchEvents, all mouse buttons appearing as left mouse buttons, and
MouseEvents being suppressed by TouchEvents.
Starting with Chrome 58, a mouse on Android M or later will:
No longer fire TouchEvents.
Fire a consistent sequence of MouseEvents with appropriate buttons and
other properties.
Remove case-insensitive matching for usemap attribute
The usemap attribute was formerly defined as caseless. Unfortunately
implementing this was complicated enough that no browsers implemented it
correctly. Research suggested that such a complicated algorithm is unnecessary,
and even ASCII case-insensitive matching is unnecessary.
Consequently, the specification was updated so that case-sensitive matching is
applied. The old behavior was deprecated in Chrome 57, and is now removed.
Some usages of
Encrypted Media Extenions (EME)
expose digital rights management implementations that are not open source,
involve access to persistent unique identifiers, and/or run unsandboxed or with
privileged access. Security risks are increased for sites exposed via non-secure
HTTP because they can be attacked by anyone on the channel. Additionally, when
user consent is required, acceptance persisted for a non-secure HTTP site can be
exploited by such an attacker.
Support for non-secure contexts was removed from the
EME version 1 spec
and is not supported in the
proposed recommendation nor
anticipated in the subsequent final. will not be in the upcoming proposed
recommendation or subsequent final recommendation. The API has been showing a
deprecation message on non-secure origins since Chrome 44 (May 2015). In Chrome
58, it is now removed. This change is part of our broader effort to
remove powerful features from unsecure origins.
Remove legacy caller for HTMLEmbedElement and HTMLObjectElement
That an interface has a legacy caller means that an instance can be called as a
function. Currently, HTMLEmbedElement and HTMLObjectElement support this
functionality. In Chrome 57 this ability was deprecated. Starting in Chrome 58,
calling throws an exception.
This change brings Chrome in line with recent spec changes. The legacy behavior
is not supported in Edge or Safari, and it is being
removed from Firefox.
Remove deprecated names for motion path properties
Motion path CSS properties allow authors to animate any graphical object along
an author-specified path. In compliance with the spec, several properties were
implemented in Chrome 45.
The names of these properties were changed in the spec in mid 2016. Chrome
implemented the
new names in Chrome 55 and Chrome 56.
Console deprecation warnings were also implemented.
In Chrome 58, the old property names are being removed. The affected properties
and their new names are shown below.
Remove support for commonName matching in certificates
RFC 2818 describes two methods to match a
domain name against a certificate: using the available names within the
subjectAlternativeName extension, or, in the absence of a SAN extension,
falling back to the commonName. The fallback to the commonName was
deprecated in RFC 2818 (published in 2000), but support remains in a number of
TLS clients, often incorrectly.
The use of the subjectAlternativeName fields leaves it unambiguous whether a
certificate is expressing a binding to an IP address or a domain name, and is
fully defined in terms of its interaction with Name Constraints. However, the
commonName is ambiguous, and because of this, support for it has been a source
of security bugs in Chrome, the libraries it uses, and within the TLS ecosystem
at large.
The compatibility risk for removing commonName is low. RFC 2818 has
deprecated this for nearly two decades, and the
baseline requirements
(which all publicly trusted certificate authorities must abide by) has required
the presence of a subjectAltName since 2012. Firefox already requires the
subjectAltName for any newly issued publicly trusted certificates since
Firefox 48.
Note: Enterprises that need to support such certificates for internal purposes
may set the EnableCommonNameFallbackForLocalAnchors Enterprise policy.
The dropzone global attribute was introduced by the
HTML5 drag and drop specification
as a declarative method for specifying an HTML element's willingness to be the
target of a drag-and-drop operation, the content types that can be dropped onto
the element, and the drag-and-drop operation (copy/move/link).
The attribute failed to gain traction among browser vendors. Blink and WebKit
only implement a prefixed form of the attribute, webkitdropzone. Because the
dropzone attribute was removed from the spec in
early March 2017
the prefixed version is being removed from Chrome.
The interface elements regions, addRegion() and removeRegion(), have been
removed from the WebVTT spec and is removed in Chrome 58 to comply with
the latest spec. We expect little impact from
this removal since the feature was never enabled by default (meaning it was
behind a flag). Those needing an alternative can use the VTTCue.region
property which is being added in Chrome 58.
The AudioSourceNode interface is not part of the
Web Audio specification,
is not constructible, and has no attributes so it basically has no developer-
accessible functionality. Therefore it is being removed.
Today, when using
Media Source Extensions (MSE)
in Chrome, it's not possible to switch between encrypted and clear streams. This
is actually not prohibited by the MSE spec. Rather, this limitation is mostly in
how the media pipeline is setup to support Encrypted Media Extensions
(EME).
MSE requires that media streams start with an initialization segment which
includes information like codec initialization data, and encryption information.
Typically, the initialization segment is at the beginning of a media file.
Consequently, when media are attached to a media element via download or MSE,
they "just work".
The problem comes when you try to change media characteristics in mid stream.
Changing media characteristics requires passing a new initialization segment.
For most characteristics, this works. Playback continues. The exception is the
encryption settings. The encryption settings from the first initialization
segment only signal whether the stream segments may be encrypted, meaning
clear media segments can be inserted in the stream. Corolary to this is that an
unencrypted stream with even a single encrypted segment requires that encryption
information be included in the initialization segment. Because of this, ad
insertion requires workarounds that don't apply to other platforms.
Starting in Chrome 58, all this changes. You can now switch between encrypted
and unencrypted in the same stream. This improves compatibility by matching
behavior that already exists in Firefox and Edge.
This has a few caveats. First, if you anticipate any encrypted segments in your
media streams, you must set the
MediaKeys
up front. As before, you cannot mix HTTP and HTTPS in the same source.
Developers can now access the approximate range of colors supported by Chrome and
output devices using the color-gamut Media Query.
Media controls customization
Developers can now customize Chrome's native media controls such as the
download, fullscreen and remoteplayback buttons using the new ControlsList API.
This API offers a way to show or hide native media controls that do not make
sense or are not part of the expected user experience, or only whitelist a
limited set of features.
The current implementation for now is a blacklist mechanism on native controls
with the ability to set them directly from HTML content using the new
attribute controlsList. Check out the official
sample.
Autoplay for Progressive Web Apps added to home screen
Previously, Chrome used to block all autoplay with sound on Android without
exception. This is no longer true. From now on, sites installed using the
improved Add to Home Screen flow are allowed to autoplay audio and video
served from origins included in the web app manifest's scope without
restrictions.
{
"name": "My Web App",
"description": "An awesome app",
"scope": "/foo",
...
}
<!-- Audio will autoplay as /foo is in the scope. -->
<audio autoplay src="/foo/file.mp4"></audio>
<!-- Audio fails to autoplay as /bar is NOT in the scope. -->
<audio autoplay src="/bar/file.mp4"></audio>
As you may already know, Chrome on Android allows muted videos to begin playing
without user interaction. If a video is marked as muted and has the
autoplay attribute, Chrome starts playing the video when it becomes visible
to the user.
From Chrome 58, in order to reduce power usage, playback of videos with
the autoplay attribute will be paused when offscreen and resumed when back in
view, following Safari iOS behavior.'
Note: This only applies to videos that are declared as autoplay but not videos
that start playing with play().
As wide color gamut screens are more and more popular, sites can now access the
approximate range of colors supported by Chrome and output devices using the
color-gamut media query.
If you're not familiar yet with the definitions of color space, color profile,
gamut, wide-gamut and color depth, I highly recommend you read the
Improving Color on the Web WebKit blog post. It goes into much detail on how
to use the color-gamut media query to serve wide-gamut images when the user
is on wide-gamut displays and fallback to sRGB images otherwise.
The current implementation in Chrome accepts the srgb, p3 (gamut specified
by the DCI P3 Color Space), and rec2020 (gamut specified by the ITU-R
Recommendation BT.2020 Color Space) keywords. Check out the official
sample.
main {
background-image: url("photo-srgb.jpg");
}
@media (color-gamut: p3) {
main {
background-image: url("photo-p3.jpg");
}
}
@media (color-gamut: rec2020) {
main {
background-image: url("photo-rec2020.jpg");
}
}
Usage in JavaScript:
// It is expected that the majority of color displays will return true.
if (window.matchMedia("(color-gamut: srgb)").matches) {
document.querySelector('main').style.backgroundImage = 'url("photo-srgb.jpg")';
}
if (window.matchMedia("(color-gamut: p3)").matches) {
document.querySelector('main').style.backgroundImage = 'url("photo-p3.jpg")';
}
if (window.matchMedia("(color-gamut: rec2020)").matches) {
document.querySelector('main').style.backgroundImage = 'url("photo-rec2020.jpg")';
}
For info, this screen currently supports approximately:
the sRGB gamut or more.
the gamut specified by the DCI P3 Color Space or more.
the gamut specified by the ITU-R Recommendation BT.2020 Color Space or more.
Use scale transforms when animating clips. You can prevent the children from being
stretched and skewed during the animation by counter-scaling them.
Previously we’ve posted updates on how to create performant
parallax effects and
infinite scrollers. In this
post we’re going to look over what’s involved if you want performant clip animations. If you want
to see a demo, check out the Sample UI Elements GitHub repo.
Take, for example, an expanding menu:
Some options for building this are more performant than others.
Bad: Animating width and height on a container element
You could imagine using a bit of CSS to animate the width and height on the container element.
The immediate problem with this approach is that it requires animating width and height.
These properties require calculating layout and paint the results on every frame of the animation,
which can be very expensive, and will typically cause you to miss out on 60fps. If that’s news to
you then read our
Rendering Performance
guides, where you can get more information on how the rendering process works.
Bad: Use the CSS clip or clip-path properties.
An alternative to animating width and height might be to use the (now-deprecated) clip
property to animate the expand and collapse effect. Or, if you prefer, you could use clip-path
instead. Using clip-path, however, is less well supported
than clip. But clip is deprecated. Right. But don’t despair, this isn’t the solution you wanted
anyway!
While better than animating the width and height of the menu element, the downside of this
approach is that it still triggers paint. Also the clip property, should you go that route,
requires that the element it’s operating on is either absolutely or fixed positioned, which can
require a little extra wrangling.
Good: animating scales
Since this effect involves something getting bigger and smaller, you can use a scale transform.
This is great news because changing transforms is something that doesn’t require
layout or paint,
and which the browser can hand off to the GPU, meaning that the effect is accelerated and
significantly more likely to hit 60fps.
The downside to this approach, like most things in rendering performance, is that it requires a bit
of setting up. It’s totally worth it, though!
Step 1: Calculate the start and end states
With an approach that uses scale animations, the first step is to read elements that tell you the
size the menu needs to be both when it’s collapsed, and when it’s expanded. It may be that for some
situations you can’t get both of these bits of information in one go, and that you need
to — say — toggle some classes around to be able to read the various states of the component.
If you need to do that, however, be cautious: getBoundingClientRect() (or offsetWidth
and offsetHeight) forces the browser to run styles and layout passes if styles have changed
since they were last run.
function calculateCollapsedScale () {
// The menu title can act as the marker for the collapsed state.
const collapsed = menuTitle.getBoundingClientRect();
// Whereas the menu as a whole (title plus items) can act as
// a proxy for the expanded state.
const expanded = menu.getBoundingClientRect();
return {
x: collapsed.width / expanded.width,
y: collapsed.height / expanded.height
}
}
In the case of something like a menu, we can make the reasonable assumption that it will start out
being its natural scale (1, 1). This natural scale represents its expanded state meaning you will
need to animate from a scaled down version (which was calculated above) back up to that natural
scale.
But wait! Surely this would scale the contents of the menu as well, wouldn’t it? Well, as you can
see below, yes.
So what can you do about this? Well you can apply a counter-transform to the contents, so for
example if the container is scaled down to 1/5th of its normal size, you can scale the contents up
by 5x to prevent the contents being squashed. There are two things to notice about that:
The counter-transform is also a scale operation. This is good because it can also be
accelerated, just like the animation on the container. You may need to ensure that the elements
being animated get their own compositor layer (enabling the GPU to help out), and for that you can
add will-change: transform to the element or, if you need to support older
browsers, backface-visiblity: hidden.
The counter-transform must be calculated per frame. This is where things can get a little
trickier, because assuming that the animation is in CSS and uses an easing function, the easing
itself need to be countered when animating the counter-transform. However, calculating the inverse
curve for — say — cubic-bezier(0, 0, 0.3, 1) isn’t all that obvious.
It may be tempting, then, to consider animating the effect using JavaScript. After all, you could
then use an easing equation to calculate the scale and counter-scale values per frame. The downside
of any JavaScript-based animation is what happens when the main thread (where your JavaScript runs)
is busy with some other task. The short answer is that your animation can stutter or
halt altogether, which isn’t great for UX.
Step 2: Build CSS Animations on the fly
The solution, which may appear odd at first, is to create a keyframed animation with our own easing
function dynamically and inject it into the page for use by the menu. (Big thanks to Chrome engineer
Robert Flack for pointing this out!) The primary benefit of this is that a keyframed animation that
mutates transforms can be run on the compositor, meaning that it isn’t affected by tasks on the
main thread.
To make the keyframe animation we step from 0 to 100 and calculate what scale values would be
needed for the element and its contents. These can then be boiled down to a string, which can
be injected into the page as a style element. Injecting the styles will cause a Recalculate Styles
pass on the page, which is additional work that the browser has to do, but it will do it only
once when the component is booting up.
function createKeyframeAnimation () {
// Figure out the size of the element when collapsed.
let {x, y} = calculateCollapsedScale();
let animation = '';
let inverseAnimation = '';
for (let step = 0; step <= 100; step++) {
// Remap the step value to an eased one.
let easedStep = ease(step / 100);
// Calculate the scale of the element.
const xScale = x + (1 - x) * easedStep;
const yScale = y + (1 - y) * easedStep;
animation += `${step}% {
transform: scale(${xScale}, ${yScale});
}`;
// And now the inverse for the contents.
const invXScale = 1 / xScale;
const invYScale = 1 / yScale;
inverseAnimation += `${step}% {
transform: scale(${invXScale}, ${invYScale});
}`;
}
return `
@keyframes menuAnimation {
${animation}
}
@keyframes menuContentsAnimation {
${inverseAnimation}
}`;
}
The endlessly curious may be wondering about the ease() function inside the for-loop. You can use
something like this to map values from 0 to 1 to an eased equivalent.
This causes the animations to run that were created in the previous step. Because the baked
animations are already eased, the timing function needs to be set to linear otherwise you’ll
ease between each keyframe which will look very weird!
When it comes to collapsing the element back down there are two options: update the CSS animation
to run in reverse rather than forwards. This will work just fine, but the "feel" of the animation
will be reversed, so if you used an ease-out curve the reverse will feel eased in, which will
make it feel sluggish. A more appropriate solution is to create a second pair of animations for
collapsing the element. These can be created in exactly the same way as the expand keyframe
animations, but with swapped start and end values.
It’s also possible to use this technique to make circular expand and collapse animations.
The principles are largely the same as the previous version, where you scale an element, and
counter-scale its immediate children. In this case the element that’s scaling up has
a border-radius of 50%, making it circular, and is wrapped by another element that
has overflow: hidden, meaning that you don’t see the circle expand outside of the element bounds.
A word of warning on this particular variant: Chrome has blurry text on low DPI screens during the
animation because of rounding errors due to the scale and counter-scale of the text. If you’re
interested in the details for that
there’s a bug filed that you can star and follow.
The code for the circular expand effect can be found in
the GitHub repo.
Conclusions
So there you have it, a way to do performant clip animations using scale transforms. In a perfect
world it would be great to see clip animations be accelerated (there’s
a Chromium bug for that made by
Jake Archibald), but until we get there you should be cautious when animating clip or clip-path,
and definitely avoid animating width or height.
It would also be handy to use Web Animations for effects like this, because they have a JavaScript
API but can run on the compositor thread if you only animate transform and opacity.
Unfortunately support for Web Animations isn’t great,
though you could use progressive enhancement to use them if they’re available.
if ('animate' in HTMLElement.prototype) {
// Animate with Web Animations.
} else {
// Fall back to generated CSS Animations or JS.
}
Until that changes, while you can use JavaScript-based libraries to do the animation, you might
find that you get more reliable performance by baking a CSS animation and using that instead.
Equally, if your app already relies on JavaScript for its animations you may be better served by
being at least consistent with your existing codebase.
If you want to have a look through the code for this effect take a look at the
UI Element Samples Github repo and,
as always, let us know how you get on in the comments below.
CSS deep-dive: matrix3d() for a frame-perfect custom scrollbar
Custom scrollbars are extremely rare and that’s mostly due to the fact that
scrollbars are one of the remaining bits on the web that are pretty much
unstylable (I’m looking at you, date picker).
You can use JavaScript to build your own, but that’s expensive, low
fidelity and can feel laggy. In this article we will leverage some
unconventional CSS matrices to build a custom scroller that doesn’t require any
JavaScript while scrolling, just some setup code.
TL;DR:
You don’t care about the nitty gritty? You just want to look at the
Nyan cat demo
and get the library? You can find the demo’s code in our
GitHub repo.
LAM;WRA (Long and mathematical; will read anyways):
Note: This article does some weird stuff with homogeneous coordinates as well as
matrix calculations. It is good to have some basic understanding of these
concepts if you want to understand the intricate details of this trick. However,
we hope that – even if you don’t enjoy matrix math – you can still
follow along and see how we used them.
A while ago we built a parallax scroller (Did you read
that article?
It’s really good, well worth your time!). By pushing elements back using CSS 3D
transforms, elements moved slower than our actual scrolling speed.
Recap
Let’s start off with a recap of how the parallax scroller worked.
As shown in the animation, we achieved the parallax effect by pushing elements
“backwards” in 3D space, along the Z axis. Scrolling a document is effectively a
translation along the Y axis. So if we scroll down by, say 100px, every
element will be translated upwards by 100px. That applies to all elements,
even the ones that are “further back”. But because they are farther away from
the camera their observed on-screen movement will be less than 100px, yielding
the desired parallax effect.
Of course, moving an element back in space will also make it appear smaller,
which we correct by scaling the element back up. We figured out the exact math
when we built the
parallax scroller,
so I won’t repeat all the details.
Step 0: What do we wanna do?
Scrollbars. That’s what we are going to build. But have you ever really thought
about what they do? I certainly didn’t. Scrollbars are an indicator of
how much of the available content is currently visible and how much progress
you as the reader have made. If you scroll down, so does the scrollbar to
indicate that you are making progress towards the end. If all the content fits
into the viewport the scrollbar is usually hidden.
If the content has 2x the height of the viewport, the
scrollbar fills ½ of the height of the viewport. Content worth 3x the height of
the viewport scales the scrollbar to ⅓ of the viewport etc. You see the pattern.
Instead of scrolling you can also click-and-drag the scrollbar to move through
the site faster. That’s a surprising amount of behavior for an inconspicuous
element like that. Let’s fight one battle at a time.
Step 1: Putting it in reverse
Okay, we can make elements move slower than the scrolling speed with CSS 3D
transforms as outlined in the parallax scrolling article. Can we also reverse
the direction? It turns out we can and that’s our way in for building a
frame-perfect, custom scrollbar. To understand how this works we need to cover a
few CSS 3D basics first.
To get any kind of perspective projection in the mathematical sense, you’ll most
likely end up using
homogeneous coordinates.
I’m not going into detail what they are and why they work, but you can think of
them like 3D coordinates with an additional, fourth coordinate called w. This
coordinate should be 1 except if you want to have perspective distortion. We
don’t need to worry about the detials of w as we are not going to use any
other value than 1. Therefore all points are from now on 4-dimensional vectors
[x, y, z, w=1] and consequently matrices need
to be 4x4 as well.
One occasion where you can see that CSS uses homogeneous coordinates under the
hood is when you define your own 4x4 matrices in a transform property using the
matrix3d() function. matrix3d takes 16 arguments (because the matrix is
4x4), specifying one column after the other. So we can use this function to
manually specify rotations, translations etc.. But what it also allows us to do
is mess around with that w coordinate!
Before we can make use of matrix3d(), we need a 3D context – because without a
3D context there wouldn’t be any perspective distortion and no need for
homogeneous coordinates. To create a 3D context we need a container with a
perspective and some elements inside that we can transform in the newly
created 3D space. For
example:
The elements inside a perspective container are processed by the CSS engine
as follows:
Turn each corner (vertex) of an element into homogenous coordinates
[x,y,z,w], relative to the perspective container.
Apply all of the element’s transforms as matrices from right to left.
If the perspective element is scrollable, apply a scroll matrix.
Apply the perspective matrix.
The scroll matrix is a translation along the y axis. If we scroll down by
400px, all elements need to be moved up by 400px. The perspective matrix is a
matrix that “pulls” points closer to the vanishing point the further back in 3D
space they are. This achieves both effects of making things appear smaller when
they are farther back and also makes them “move slower” when being translated.
So if an element is pushed back, a translation of 400px will cause the element
to only move 300px on the screen.
If you want to know all the details, you should read the
spec on CSS’
transform rendering model, but for the sake of this article I simplified the
algorithm above.
Our box is inside a perspective container with value p for the perspective
attribute, and let’s assume the container is scrollable and is scrolled down by
n pixels.
The first matrix is the perspective matrix, the second matrix is the scroll
matrix. To recap: The scroll matrix’ job is to make an element move up when we
are scrolling down, hence the negative sign.
For our scrollbar however we want the opposite – we want our element to
move down when we are scrolling down. Here’s where we can use a trick:
Inverting the w coordinate of the corners of our box. If the w coordinate is
-1, all translations will take effect in the opposite direction. So how do we do
that? The CSS engine takes care of converting the corners of our box to
homogeneous coordinates, and sets w to 1. It’s time for matrix3d() to shine!
This matrix will do nothing else then negating w. So when the CSS engine has
turned each corner into a vector of the form [x,y,z,1], the matrix will
convert it into [x,y,z,-1].
I listed an intermediate step to show the effect of our element transform
matrix. If you are not comfortable with matrix math, that is okay. The Eureka
moment is that in the last line we end up adding the scroll offset n to our y
coordinate instead of subtracting it. The element will be translated downwards
if we scroll down.
However, if we just put this matrix in our
example,
the element will not be displayed. This is because the CSS spec requires that any
vertex with w < 0 blocks the element from being rendered. And since our z
coordinate is currently 0, and p is 1, w will be -1.
Luckily, we can choose the value of z! To make sure we end up with w=1, we need
to set z = -2.
Now our box is there and is looking the same way it would have without any
transforms. Right now the perspective container is not scrollable, so we can’t
see it, but we know that our element will go the other direction when
scrolled. So let’s make the container scroll, shall we? We can just add a
spacer element that takes up space:
<div class="container">
<div class="box"></div>
<span class="spacer"></span>
</div>
<style>
/* … all the styles from the previous example … */
.container {
overflow: scroll;
}
.spacer {
display: block;
height: 500px;
}
</style>
We have an element that moves down when the page scrolls down. That’s the hard
bit out of the way, really. Now we need to style it to look like a scrollbar and
make it a bit more interactive.
A scrollbar usually consists of a “thumb” and a “track”, while the track isn’t
always visible. The thumb’s height is directly proportional to how much of the
content is visible.
scrollerHeight is the height of the scrollable element, while
scroller.scrollHeight is the total height of the scrollable content.
scrollerHeight/scroller.scrollHeight is the fraction of the content that is
visible. The ratio of vertical space the thumb covers should be equal to the
ratio of content that is visible:
Note: We need to adjust right so our custom scrollbar is visible on systems
with permanent native scrollbars. We will completely hide the native scrollbars
later with a trick.
The size of the thumb is
looking good,
but it’s moving way too fast. This is where we can grab our technique from the
parallax scroller. If we move the element further back it will move slower while
scrolling. We can correct the size by scaling it up. But how much should we push
it back exactly? Let’s do some – you guessed it – math! This is the last time, I
promise.
The crucial bit of information is that we want the bottom edge of the thumb to
line up with the bottom edge of the scrollable element when scrolled all the way
down. In other words: If we have scrolled
scroller.scrollHeight - scroller.height pixels, we want our thumb to be
translated by scroller.height - thumb.height. For every pixel of scroller, we
want our thumb to move a fraction of a pixel:
That’s our scaling factor. Now we need to convert the scaling factor into a
translation along the z axis, which we already did in the parallax scrolling
article. According to the
relevant section in the spec:
The scaling factor is equal to p/(p − z). We can solve this equation for z to
figure out how much we we need to translate our thumb along the z axis. But keep
in mind that due to our w coordinate shenanigans we need to translate an
additional -2px along z. Also note that an element’s transforms are applied
right to left, meaning that all translations before our special matrix will not
be inverted, all translations after our special matrix, however, will! Let’s
codify this!
We have a
scrollbar!
And it’s just a DOM element that we can style however we like. One thing that is
important to do in terms of accessibility is to make the thumb respond to
click-and-drag, as many users are used to interacting with a scrollbar that way.
For the sake of not making this blog post even longer, I am not going explain
the details for that part. Take a look at the
library code
for details if you want to see how it’s done.
What about iOS?
Ah, my old friend iOS Safari. As with the parallax scrolling, we run into an
issue here. Because we are scrolling on an element, we need to specify
-webkit-overflow-scrolling: touch, but that causes 3D flattening and our entire
scrolling effect stops working. We solved this problem in the parallax scroller
by detecting iOS Safari and relying on position: sticky as a workaround, and
we’ll do exactly the same thing here. Take a look at the
parallaxing article
to refresh your memory.
What about the browser scrollbar?
On some systems we will have to deal with a permanent, native scrollbar.
Historically, the scrollbar can’t be hidden (except with a
non-standard pseudo-selector).
So to hide it we have to resort to some (math-free) hackery. We wrap our
scrolling element in a container with overflow-x: hidden and make the
scrolling element wider than the container. The browser’s native scrollbar is
now out of view.
Fin
Putting it all together, we can now build a frame-perfect custom
scrollbar – like the one in our
Nyan cat demo.
If you can’t see Nyan cat, you are experiencing
a bug that we found and filed
while building this demo (click the thumb to make Nyan cat appear).
Chrome is really good at avoiding unnecessary work
like painting or animating things that are off-screen. The bad news is that our
matrix shenanigans make Chrome think the Nyan cat gif is actually off-screen.
Hopefully this will get fixed soon.
There you have it. That was a lot of work. I applaud you for reading the entire
thing. This is some
real trickery to get this working and it’s probably rarely worth the effort,
except when a customized scrollbar is an essential part of the experience. But
good to know that it is possible, no? The fact that it is this hard to do a
custom scrollbar shows that there’s work to be done on CSS’s side. But fear not!
In the future,
Houdini’s
AnimationWorklet is going to make
frame-perfect scroll-linked effects like this a lot easier.
JavaScript was introduced in 1995, and in the very first version of JavaScript
were methods on the window object named
alert(),
confirm(),
and prompt().
While they fit into the JavaScript of the time, their synchronous API is
problematic for modern browsers. Because the JavaScript engine needs to pause
until a user response is obtained, the JavaScript dialogs are app-modal. And
because the dialogs are app-modal, they commonly (and unfortunately) are used to
harmourusers.
Because of this, the Chromium team highly recommends that you not use JavaScript dialogs.
Alternatives
There are many options for dialog replacement.
There are several choices for alert()/confirm()/prompt(). For notifying the
user of events (e.g. calendaring sites), the
Notifications API
should be used. For obtaining user input, the
HTML <dialog> element
should be used. For XSS proofs-of-concept, devtool’s
console.log(document.origin) can be used.
As for onbeforeunload, it should be noted that it is already unreliable. As
Ilya Grigorik points out,
“You cannot rely on pagehide, beforeunload, and unload events to fire on
mobile platforms.” If you need to save state, you should use the
Page Visibility API.
Changes
The ability for a page to specify the onbeforeunload string was
removed in Chrome 51.
(It was also removed by Safari starting with Safari 9.1 and in Firefox 4.)
alert()/confirm()/prompt() dialogs are being changed. Rather than being app-modal,
they will be dismissed when their tab is switched from.
(Safari 9.1 already does this.) This is fully enabled on the canary and dev channels
and partially enabled on the beta and stable channels, and will be enabled more in the
future.
The current plan for beforeunload dialogs is to require a user gesture
to allow them to show. (This would not change the dispatching of the beforeunload
event.) This aligns Chromium with Firefox, which made this change with
Firefox 44.
Because of these changes, if your site uses dialogs, it is highly recommended
that you move to using the earlier-mentioned alternatives so that this will not
affect you.
Unified Command Menu. Execute commands and open files
from the newly-unified Command Menu.
Workspaces 2.0. Check out the new UX for using DevTools
as your code editor.
New features
CSS and JS code coverage
Find unused CSS and JS code with the new Coverage tab. When you load or
run a page, the tab tells you how much code was used, versus how much was
loaded. You can reduce the size of your pages by only shipping the code
that you need.
Check out the video below to learn how to take a screenshot from the top
of the page, all the way to the bottom.
Block requests
Want to see how your page behaves when a particular script, stylesheet, or
other resource isn't available? Right-click on the request in the Network
panel and select Block Request URL. A new Request blocking tab
pops up in the Drawer, which lets you manage blocked requests.
Step over async await
Up until now, trying to step through code like the snippet below was a
headache. You'd be in the middle of test(), stepping over a line, and then
you'd get interrupted by the setInterval() code. Now, when you step through
async code like test(), DevTools steps from the first to last line with
consistency.
function wait(ms) {
return new Promise(r => setTimeout(r, ms)).then(() => "Yay");
}
// do some work in background.
setInterval(() => 42, 200);
async function test() {
debugger;
const hello = "world";
const response = await fetch('index.html');
const tmp = await wait(1000);
console.log(tmp);
return hello;
}
async function runTest() {
let result = await test();
console.log(result);
}
P.S. want to level up your debugging skills? Check out these new-ish docs:
The new UX for using DevTools as your code editor, also known as Workspaces
or Persistence, has been released from Experiments.
Go to the Sources panel.
Click the Filesystem tab.
Click Add folder to workspace.
Select the folder in your filesystem containing your source code.
Click Allow to grant DevTools read and write access to the folder.
DevTools automagically maps the files in your filesystem to the files being
served from the network. No more need to manually map one to the other.
Changes
Unified Command Menu
When you open the Command Menu now, notice that your command
is prepended with a greater-than character (>). This is because the Command
Menu has been unified with the Open File menu, which is
Command+O (Mac), or Control+O
(Windows, Linux).