Pairing with @Ester Martí
Everyone has experienced how visiting a web site over a slow network connection usually takes ages to load, making the experience very painful or completely impossible at all.
When it comes to web development, we usually tend to forget about load performance focusing more on adding new fancy features. But probably our users are not using the last brand new MacBook Pro connected to a 1Gps network. Is more likely that they are using a mid-range or low-end mobile device with a network connection that in the best case is a 3G connection.
In 2018, 52.2% of all global web pages were served to mobile phones.
So, taking care of performance is important and one of the most resource-consuming is media delivery. We are going to show how to adapt the media
delivery based on the network connection using the Network Information API. This is an improved version of an experiment I did with my coworker @Eduardo Aquiles as a React component, similar to what Max Böck explains in his article about connection-aware components but in this case, using service workers.
The Network Information API
The Network Information API is a draft specification that exposes an interface to JavaScript with information about the device connection.
The interface consists of a different set of attributes that gives us multiple information about the network. The most relevant for us in this article are:
- type: The connection type that the user agent is using. (e.g. ‘wifi’, ‘cellular’, ‘ethernet’, etc.)
- effectiveType The effective connection type that is determined using a combination of recently observed rtt and downlink values. (see table)
- saveData Indicates when the user requested a reduced data usage.
effectiveType values
ECT | Minimum RTT (ms) | Maximum downlink (Kbps) | Explanation |
---|---|---|---|
slow‑2g | 2000 | 50 | The network is suited for small transfers only such as text-only pages. |
2g | 1400 | 70 | The network is suited for transfers of small images. |
3g | 270 | 700 | The network is suited for transfers of large assets such as high resolution images, audio, and SD video. |
4g | 0 | ∞ | The network is suited for HD video, real-time video, etc. |
Browser support
The API does not have full browser support yet but is supported by the most popular mobile browsers
which are the ones where this technique will have more impact.
In fact, 70% of mobile users have this API enabled on their device.
Adaptive Media Serving
Our purpose will be to serve different media resources based on the information that we get from the effectiveType
attribute. When we talk about different media resources it could be a completely different media, like switching between HD video, HD image or low quality image, the approach suggested by Addy Osmani.
In this example, we are going to use different compression levels for the same image.
First, we need to get the proper quality based on network conditions. This is easily reachable using the next snippet:
function getMediaQuality() {
const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
if (!connection) {
return 'medium';
}
switch (connection.effectiveType) {
case 'slow-2g':
case '2g':
return 'low';
case '3g':
return 'medium';
case '4g':
return 'high';
default:
return 'low';
}
}
Imagine that we have an image server where we can specify the quality of the image that we want with a query parameter quality as low
, medium
or high
. Therefore we can set the quality on the src
attribute of the images tags as it follows:
<img src="http://images.magarcia.io/cute_cat?quality=low" alt="Cute cat" />
const images = document.querySelectorAll('img');
images.forEach(img => {
img.src = img.src.replace('low', getMediaQuality());
});
It is important to notice that the default quality set on the image is low
, which means that devices will load first the low quality image and then if it has a high-speed connection will load the better quality one.
Then the JavaScript snippet above will get all the images in the document and will replace the quality parameter to the appropriate one based on what the getMediaQuality
function returns. If the quality is low
is not going to do more requests, but if it changes it will do two requests: one with the low
quality image when the browsers parse the img
tag and another one with medium
or high
quality when the JavaScript code is executed.
This is not ideal but it will improve load times on slow networks. But for medium/high connection networks, as we mentioned before, it will make two requests for each image consuming more data than needed.
Using Service Workers
The problem mentioned regarding the two requests can be fixed using
service workers, intercepting the request made by the browser and replacing it with the appropriate quality for the image.
First, we need to register our service worker:
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/sw.js').then(
function(registration) {
console.log('ServiceWorker registration successful with scope: ', registration.scope);
},
function(err) {
console.log('ServiceWorker registration failed: ', err);
}
);
});
}
Next, we add a listener for the fetch event, which for all the images requested from the site will append the right quality parameter using the getMediaQuality
function created in the previous section.
self.addEventListener('fetch', function(event) {
if (/\.jpg$|.png$|.webp$/.test(event.request.url)) {
const url = event.request.url + `?quality=${getMediaQuality()}`;
event.respondWith(fetch(url));
}
});
And we don’t need to specify the quality parameter on the img
tag anymore since the service worker will be in charge of that.
<img src=“http://images.magarcia.io/cute_cat” alt=“Cute cat”/>
The code
You can find the code of this post (a more complete, clean and with fewer bugs) on this GitHub repo.
Further Reading
Originally published at https://magarcia.io on June 17, 2019.
Top comments (0)