Users spend a large proportion of their online time on mobile devices, and a significant fraction of the rest is users on untethered laptop computers. For both, battery life is critical. In this post, we’ll talk about factors that affect battery life, and how you, as a web developer, can make your pages more power efficient so that users can spend more time engaged with your content.
What Draws Power?
Most of the energy on mobile devices is consumed by a few major components:
- CPU (Main processor)
- GPU (Graphics processing)
- Networking (Wi-Fi and cellular radio chips)
Screen power consumption is relatively constant and mostly under the user’s control (via screen on-time and brightness), but the other components, the CPU, GPU and networking hardware have high dynamic range when it comes to power consumption.
The system adapts the CPU and GPU performance based on the current tasks being processed, including, of course, rendering web pages that the user is interacting with in their web browser and other apps using web content. This is done through turning some components on or off, and by changing their clock frequency. In broad terms, the more performance that is required from the chips, the lower their power-efficiency. The hardware can ramp up to high performance very quickly (but at a large power cost), then rapidly go back to a more efficient low-power state.
General Principles for Good Power Usage
To maximize battery life, you therefore want to reduce the amount of time spent in high-power states, and let the hardware go back to idle as much as possible.
For web developers, there are three states of interaction to think about:
- When the user is actively interacting with the content.
- When the page is the frontmost, but the user is not interacting with it.
- When the page is not the frontmost content.
Efficient user interaction
Drive idle power usage towards zero
When the user is not interacting with the page, try to make the page use as little power as possible. For example:
- Minimize the use of timers to avoid waking up the CPU. Try to coalesce timer-based work into a few, infrequent timers. Lots of uncoordinated timers which trigger frequent CPU wake-ups are much worse than gathering that work into fewer chunks.
- Minimize continually animating content, like animated images and auto-playing video. Be particularly vigilant to avoid “loading” spinner GIFs or CSS animations that continually trigger painting, even if you can’t see them. IntersectionObserver can be used to runs animations only when they are visible.
- Use declarative animations (CSS Animations and Transitions) where possible. The browser can optimize these away when the animating content is not visible, and they are more efficient than script-driven animation.
- Avoid network polling to obtain periodic updates from a server. Use WebSockets or Fetch with a persistent connection, instead of polling.
A page that is doing work when it should be idle will also be less responsive to user interaction, so minimizing background activity also improves responsiveness as well as battery life.
Zero CPU usage while in the background
There are various scenarios where a page becomes inactive (not the user’s primary focus), for instance:
- The user switches to a different tab.
- The user switches to a different app.
- The browser window is minimized.
- The browser window is visible but is not the focused window.
- The browser window is behind another window.
- The space the window is on is not the current space.
When a page becomes inactive, WebKit automatically takes steps to save power:
- CSS and SVG Animations are suspended.
- Timers are throttled.
In addition, WebKit takes advantage of features provided by the operating system to maximize efficiency:
- on iOS, tabs are completely suspended when possible.
- on macOS, tabs participate in App Nap, which means that the web process for a tab that is not visually updating gets lower priority and has its timers throttled.
However, pages can trigger CPU wake-ups via timers (
setInterval), messages, network events, etc. You should avoid these when in the background as much as possible. There are two APIs that are useful for this:
- Page Visibility API provides a way to respond to a page transitioning to be in the background or foreground. This is a good way to avoid updating the UI while the page is in the background, then using the
visibilitychangeevent to update the content when the page becomes visible.
blurevents are sent whenever the page is no longer focused. In that case, a page may still be visible but it is not the currently focused window. Depending on the page, it can be a good idea to stop animations.
The easiest way to find problems is Web Inspector’s Timelines. The recording should not show any event happening while the page is in the background.
Hunting Power Inefficiencies
Now that we know the main causes of power use by web pages and have given some general rules about creating power-efficient content, let’s talk about how to identify and fix issues that cause excessive power drain.
The best way to measure CPU usage is with Web Inspector. As we showed in a previous post, the timeline now shows the CPU activity for any selected time range:
To use the CPU efficiently, WebKit distributes work over multiple cores when possible (and pages using Workers will also be able to make use of multiple cores). Web Inspector provides a breakdown of the threads running concurrently with the page’s main thread. For example, the following screenshot shows the threads while scrolling a page with complex rendering and video playback:
Various other system frameworks invoked by WebKit make use of threads, so “Other threads” include work done by those; the largest contributor to “Other thread” activity is painting, which we’ll talk about next.
Main thread CPU usage can also be triggered by lots of layout and painting; these are usually triggered by script, but a CSS animation of a property other than
filter can also cause them. Looking at the “Layout and Rendering” timeline will help you understand what’s causing activity.
If the “Layout and Rendering” timeline shows painting but you can’t figure out what’s changing, turn on Paint Flashing:
This will cause those paints to be briefly highlighted with a red overlay; you might have to scroll the page to see them. Be aware that WebKit keeps some “overdraw” tiles to allow for smooth scrolling, so paints that are not visible in the viewport can still be doing work to keep offscreen tiles up-to-date. If a paint shows in the timeline, it’s doing actual work.
In addition to causing power usage by the CPU, painting usually also triggers GPU work. WebKit on macOS and iOS uses the GPU for painting, and so triggering painting can cause significant increases in power. The additional CPU usage will often show under “Other threads” in the CPU Usage timeline.
The GPU is also used for
<canvas> rendering, both 2D canvas and WebGL/WebGPU. To minimize drawing, don’t call into the
<canvas> APIs if the canvas content isn’t changing, and try to optimize your canvas drawing commands.
Many Mac laptops have two GPUs, an “integrated” GPU which is on the same die as the CPU, and is less powerful but more power-efficient, and a more powerful, but more power-hungry “discrete” GPU. WebKit will default to using the integrated GPU by default; you can request the discrete GPU using the
powerPreference context creation parameter, but only do this if you can justify the power cost.
Wireless networking can affect battery life in unexpected ways. Phones are the most affected due to their combination of powerful radios (the WiFi and cellular network chips) with a smaller battery. Unfortunately, measuring the power impact of networking is not easy outside of a lab, but can be reduced by following some simple rules.
The most direct way to reduce networking power usage is to maximize the use of the browser’s cache. All of the best practices for minimizing page load time also benefit the battery by reducing how long the radios need to be powered on.
Another important aspect is to group network requests together temporally. Any time a new request comes, the operating system needs to power on the radio, connect to the base station or cell tower, and transmit the bytes. After transmitting the packets, the radio remains powered for a small amount of time in case more packets are transmitted.
If a page transmits small amounts of data infrequently, the overhead can become larger than the energy required to transmit the data:
Such issues can be discovered from Web Inspector in the Network Requests Timeline. For example, the following screenshot shows four separate requests (probably analytics) being sent over several seconds:
Sending all the requests at the same time would improve network power efficiency.
Webpages can be good citizens of battery life.
It’s important to measure the battery impact in Web Inspector and drive those costs down. Doing so improves the user experience and battery life.
The most direct way to improve battery life is to minimize CPU usage. The new Web Inspector provides a tool to monitor that over time.
To achieve great battery life the goals are:
- Drive CPU usage to zero in idle
- Maximize performance during user interaction to quickly get back to idle