For decades developers that have been building resource intensive apps and games had to think about how their products would work in older devices.
Triple-A games like Call of Duty allow their users to customize the graphics quality level. You might have seen games running on "Max Settings" with some $1000 GPU card, which is amazing, but most likely it would fry your computer and not even work.
When building complex apps or games for the web, we face the same problem. Luckily for us, the web includes very convenient APIs that allow us to know how fast or how much memory the user's machine has, as well as how fast their internet connection is!
The idea is to use these settings to automatically adjust certain levels of quality in your app.
navigator.hardwareConcurrency
is a property in the JavaScript Navigator
interface that indicates the number of logical processors available on the user's device.
The hardwareConcurrency
property is useful for web developers who want to optimize the performance of their web applications by running different parts of the application in parallel. For example, if a web application performs a lot of calculations or intensive tasks, it can use the value of hardwareConcurrency
to determine how many threads it can run in parallel to utilize the available resources efficiently.
It's important to note that the value of navigator.hardwareConcurrency
is not always accurate, as it depends on the browser implementation and the operating system. Some browsers may return a higher or lower value than the actual number of logical processors available.
const concurrency = navigator.hardwareConcurrency;
let quality;
if (concurrency < 2) {
quality = 'low';
} else if (concurrency < 4) {
quality = 'normal';
} else {
quality = 'high';
}
navigator.deviceMemory
is a property that is part of the JavaScript Navigator interface. It provides information about the device's memory in gigabytes (GB).
The deviceMemory
property is a read-only property that returns an approximation of the device's total RAM in GB, rounded to the nearest power of 2.
For example, if the device has 4 GB of RAM, navigator.deviceMemory
will return 4. If the device has 6 GB of RAM, it will return 8 (which is the nearest power of 2).
Developers can use this property to make decisions about how much memory their web applications should consume or allocate. This can help improve the overall performance of the application by avoiding excessive memory usage, which can slow down the user's device.
It's worth noting that navigator.deviceMemory
is not supported by all browsers and platforms. Additionally, the value returned by this property is an approximation and may not be accurate in all cases, however we will not depend on it for a critical part of our app.
Similarly with the previous example, we could use this property to decide an adequate texture quality:
const memory = navigator.deviceMemory;
let textureQuality;
if (memory < 2) {
textureQuality = 'low';
} else if (memory < 4) {
textureQuality = 'normal';
} else {
textureQuality = 'high';
}
One example of using this API in the wild is for determining the resolution of the normal’s map in order to generate shadows in many games. This texture can be extremely big to provide perfectly accurate reflexions, but it can safely be reduced without causing an equivalent drop in visual quality.
Sometimes, it’s not about the raw CPU and memory capabilities, but about the internet connection. It might be a bad idea to make them wait until the 4K textures download.
One API that we can use is navigator.connection
, it will provide us with useful information about the internet quality of our user.
const downlink = navigator.connection?.downlink;
const effectiveType = navigator.connection?.effectiveType;
let lowMode = false;
if (effectiveType !== 'wifi') {
lowMode = true;
}
if (downlink < 5) {
lowMode = true;
}
This API is highly experimental, but still useful because your application will still work regardless this API is available not.
From the MDN docs, here’s all the information you can get out of NetworkInformation.
NetworkInformation.downlink
: Returns the effective bandwidth estimate in megabits per second, rounded to the nearest multiple of 25 kilobits per seconds.NetworkInformation.downlinkMax
: Returns the maximum downlink speed, in megabits per second (Mbps), for the underlying connection technology.NetworkInformation.effectiveType]
: Returns the effective type of the connection meaning one of 'slow-2g', '2g', '3g', or '4g'. This value is determined using a combination of recently observed round-trip time and downlink values.NetworkInformation.rtt
: Returns the estimated effective round-trip time of the current connection, rounded to the nearest multiple of 25 milliseconds.NetworkInformation.saveData
: Returnstrue
if the user has set a reduced data usage option on the user agent.NetworkInformation.type
: Returns the type of connection a device is using to communicate with the network. It will be one of the following values:bluetooth
,cellular
,ethernet
,none
,wifi
,wimax
,other
,unknown
.
While this API is not about performance, it’s important to keep it mind, when building very applications with lots of effects and animations. For accessibility reasons, some people can experience extreme discomfort and even epilepsy.
The easiest way to use this API is directly in CSS, using a media query.
@media (prefers-reduced-motion: reduced) {
.header {
transition: none;
}
}
In the example belong, we disable the transition animation of the header, only when the user selected reduced
motion.
If you are building stuff in 3D, maybe Three.js, using CSS might not suite you, likely for us, there is a API to evaluate media queries directly from JavaScript: window.matchMedia()
We can use it like this:
const reducedAnimation = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (reducedAnimation) {
disableBigFlashingAnimations();
}
We also wrote a long blog post about other CSS tips that will boost your web skills, check it out!
Introducing Visual Copilot: convert Figma designs to high quality code in a single click.