How Web Content Can Affect Power Usage
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
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
Obviously it’s good to expend power at times when the user is interacting with the page. You want the page to load fast and respond quickly to touch. In many cases, the same optimizations that reduce time to first paint and time to user interactive will also reduce power usage. However, be cautious about continuing to load resources and to run script after the initial page load. The goal should be to get back to idle as fast as possible. In general, the less JavaScript that runs, the more power-efficient the page will be, because script is work on top of what the browser has already done to layout and paint the page.
Once the page has loaded, user interactions like scrolling and tapping will also ramp up the hardware power (mainly the CPU and GPU), which again makes sense, but make sure to go back to idle as soon as the user stops interacting. Also, try to stay on the browser “fast paths” — for example, normal page scrolling will be much more power-efficient than custom scrolling implemented in JavaScript.
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:
requestAnimationFrame
is stopped.- 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 (setTimeout
and 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
visibilitychange
event to update the content when the page becomes visible. blur
events 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.
Scripting
As mentioned above, modern CPUs can ramp power use from very low, when the device is idle, to very high to meet the demands of user interaction and other tasks. Because of this, the CPU is a leading cause of battery life variance. CPU usage during page loading is a combination of work the browser engine does to load, parse and render resources, and in executing JavaScript. On many modern web pages, time spent executing JavaScript far exceeds the time spent by the browser in the rest of the loading process, so minimizing JavaScript execution time will have the biggest benefits for power.
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:
When looking for things to optimize, focus on the main thread, since that’s where your JavaScript is running (unless you’re using Workers), and use the “JavaScript and Events” timeline to understand what’s triggering your script. Perhaps you’re doing too much work in response to user or scroll events, or triggering updates of hidden elements from requestAnimationFrame. Be cognizant of work done by JavaScript libraries and third party scripts that you use on your page. To dig deeper, you can use Web Inspector’s JavaScript profiler to see where time is being spent.
Activity in “WebKit Threads” is mostly triggered by work related to JavaScript: JIT compilation and garbage collection, so reducing the amount of script that runs, and reducing the churn of JavaScript objects should lower this.
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.
Painting
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 transform
, opacity
and 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.
Networking
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.
Conclusion
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
If you have any questions, feel free to reach me, Joseph Pecoraro, Devin Rousso, and of course Jon Davis.