5 ways to improve battery life in your app

Tips and tricks to squeeze the most out of your mobile UI

mobile_html5 Editor’s Note: Mobile HTML5 is a book by front-end engineer and frequent speaker Estelle Weyl. It is packed with hands-on examples to make you a stronger web developer–including best practices for SVG, Canvas, and CSS3 tailored to fit mobile devices. In the excerpt below, Estelle walks you through five easy things you can do to improve battery life in your mobile web apps. As throughout the book, the tips she provides come from her own real-life experience with these technologies.

Unlike desktop computers that are tethered to the wall at all times, and even laptop computers that are generally used by stationary users, mobile users do not recharge their devices throughout the day. Mobile users expect their devices to last, at a minimum, 24 hours between recharging.

Your users do realize that calls and GPS usage consume battery power. However, if they think they’re just using their browser to surf the Web, they don’t consider that different websites will drain their battery faster than other sites. It is our job, as developers, to manage the power consumption of our code.

You have likely noticed that CPU usage drains the battery on your laptop when unplugged. CPU usage drains the battery on mobile devices just as effectively. Anything that makes your laptop churn, warm up, or turns your computer’s fan on will also drain the battery of mobile devices (if they’re not plugged in). Code defensively: expect that your mobile device users are not plugged in.

To manage the energy consumption of our code, we need to manage CPU usage. Avoid reflows. Minimize both size and activity of your JavaScript. Don’t continuously reawaken the radio with unnecessary AJAX calls. Always use CSS, rather than JavaScript, for animations. And even though the marketing teams of devices that support WebGL insist that their devices are optimized, don’t serve WebGL to mobile devices. At least, not yet. WebGL battery performance is improving.

Use dark colors

The brighter the colors in your design, the brighter the screen has to be. For phones with AMOLED screens, the brighter the screen, the more energy is consumed, draining the battery. AMOLED, or active-matrix organic light-emitting diode, screens are made of a thin layer of organic polymers that light up. Because there is no backlight, they can be very thin. Black pixels are actually turned off, saving battery life. For these non-LCD screen devices, lighter shades consume more energy during display than darker shades.

Obviously, there are issues other than battery consumption affecting the decision on what colors are used in application design. Just note that the amount of energy consumed by websites can differ significantly depending on the colors used in the design on certain devices. Colors are by far not the only feature affecting battery consumptions. Media elements like background images, foreground images, video, audio, animations, and JavaScript all contribute to battery drainage. If you can, pick darker colors. If you can’t, optimize energy in your other features.

Use JPEGs

Use JPEG images instead of PNG. JPEG compresses images better and is faster to render, and is therefore more energy efficient.

Rendering images consumes energy. Depending on the number, size, and type of images in your site, rendering images can be responsible for a significant percentage of the energy used. The energy required to render images is proportional to number and size of the images rendered. JPEGs use less energy to render than GIFs and PNGs: according to the study “Who Killed My Battery: Analyzing Mobile Browser Energy Consumption,” JPEG is the most energy efficient format for all image sizes.

By using JPEGs, you’re not only saving battery life, you’re also reducing memory and speeding up repaints. The type of image format you use affects energy consumption during rendering of the image. This impact is replayed when the image is redrawn to a different size. As we noted earlier, lighter colors consume more energy during extended display. When we are talking about image rendering costs, we are talking about the device decoding, resizing, and drawing out the image, not the energy costs once a static image is displayed.

Reduce JavaScript

While raster images are the biggest bandwidth hogs and all images are memory hogs, they’re not the only culprit in memory consumption and battery drainage. JavaScript is too! To conserve battery power and memory usage, minimize both the size and activity of your JavaScript.

When the browser hits a <script> tag, the browser ceases downloading additional assets and rendering the assets it has already downloaded until the JavaScript is downloaded, parsed, and executed. The browser also does not start parsing and executing the script file until it is fully downloaded. Which you already know.

What you may have never thought about is the memory and energy used by JavaScript. Every time an AJAX call is made, the device’s radio reawakens to make the request, draining the battery. Every time JavaScript is parsed, energy is consumed. While a site may cache the JavaScript file, it still parses and executes the JavaScript on every page load. Dynamic JavaScript, like XMLHttpRequest, increases rendering cost and can’t be cached. Every time an event handler handles an event, JavaScript gets executed. Every time a setTimeout iterates, JavaScript gets executed. These all consume energy.

The download, parsing, and execution of JavaScript can be the most energy-consuming web page component. Sometimes the JavaScript isn’t necessary! Only include JavaScript frameworks if you actually need them.

I have seen sites include jQuery just to simply select an element, and other similar things that are easy to do with selectors and/or native JavaScript. For example, to add the class of first to the first list item in every unordered list, you could use jQuery, but you don’t have to:

$('ul li:first').addClass('first');

Which is almost the same as:

var firstLIs = document.querySelectorAll('ul li:first-of-type');

for (var i = 0; i < firstLIs.length; i++) {
  firstLIs[i].classList.add('first');
}

… but the latter doesn’t add 34 KB or an extra HTTP request to your site. And while 34 KB is not a huge amount of bytes, especially in comparison to the image size that people are adding to their sites, if you include jQuery, while the jQuery file may be cached, it is still parsed and executed with every page load. While a single page load won’t drain all the power your user has left, wasting four joules of energy with each page load adds up fast. And unlike when your user is using GPS or playing a movie, they aren’t expecting that a website will drain their battery.

I am not saying that you shouldn’t use JavaScript frameworks. I am just arguing that you should make sure you really need to include the framework before doing so because you are not only wasting memory and bandwidth, but you’re also helping drain your user’s battery.

Don’t import a library just to target an element with CSS selectors. We have querySelector() and querySelectorAll() for that. Don’t import a library just to bind events: addEventListener() works fine in all modern browsers. Don’t write a script just to make scrolling work better. Try -webkit-overflow-scrolling: touch instead. And if you must have scrolling down perfect, along with the little bounce, use a script. Don’t reinvent the wheel. You won’t get the physics right. Use a library when you must, but think long and hard about whether you really need the extra bytes, HTTP request, memory usage, and battery drain before doing so.

Eliminate network requests

Obviously you need to download the files required to load your web application. This uses battery, but is necessary. However, polling the Facebook, Twitter, and Pinterest servers every 15 seconds to see if your page received more likes is not necessary and a waste of both bandwidth and battery power. In fact, it’s the worst possible waste of both.

Determine if your application needs to poll all the time, or rarely to never. If your application needs to be real time, such as a chat or a sports game, you will want to spend battery power by keeping the connection alive at all times. If your application is not polling for a necessary purpose (Facebook like counts are not necessary, and annoying), let your mobile device terminate the connection to the cell tower.

Establishing and maintaining radio links to cell towers consumes battery power. When the device is not making requests, it shuts down connectivity processes to save battery. This is a good thing.

While most performance arguments revolve around input and output of data, the number one battery drain in a mobile phone is radio. To preserve battery life, mobile devices put the radio in a preserving power mode when the transmissions are complete and into a deep sleep state after a few seconds of network inactivity. After the radio link is idle for 5 seconds, it drops to a state of half power consumption and significantly lowers bandwidth. After another 12 seconds of inactivity it drops to the idle state.

From the idle state it takes time to reach full power and bandwidth. If you are polling your server every 15 seconds, you are waking the radio from a deep sleep. Waking the radio can take up to 2 to 3 seconds, taking multiple round trips just to get to a state where your application can transmit.

If your application needs to keep the connection alive, do so. Realize you’re draining the battery and let your user know this. If you don’t need to poll at regular intervals, to conserve battery power, keep messages as small as possible and limit the number and frequency of network requests after page load.

Hardware acceleration

Usually when people
think of managing CPU usage, they’re thinking of their server. Yes, you should be doing that, too. But when it comes to limited battery life, you want to manage browser CPU usage caused by your web application. Whatever makes your laptop fan turn on will also drain the battery of any device.

One solution is to hardware accelerate all animations. Hardware acceleration means rendering your animations on the GPU instead of the CPU. The graphics
chip requires less power than the device’s CPU, resulting in improved battery life. Hardware acceleration carries out all drawing operations that are performed on a View’s canvas using the GPU. Hardware-accelerated images are composited, using four times the memory of the original. Because of the increased resources required to enable hardware acceleration, your application will consume more RAM, but less battery power. With constrained memory and battery life, always consider battery and memory consumption when designing and developing your applications.

Hardware acceleration has both benefits and drawbacks. Your animation will appear less janky on the GPU, and you will lose less battery. However, your memory is limited.

In other words, transform: translatez(0); is not a panacea. Do not do this:

* {
  transform: translatez(0);
}

… as you may run out of GPU memory, especially on devices with limited memory. However, don’t be afraid to force hardware acceleration on the elements you are animating. In fact, to reduce the traffic between CPU and GPU, it is recommended that you put all elements that are going to be animated on the GPU on load:

.spinner {
  transform: translatez(0);
  animation: spin 1s linear infinite;
}
@keyframes spin {
    100% {
        transform: translatez(0) rotate(360deg);
    }
}

Note that in the preceding example, we add the 3D transform even when we are not animating. If you are going to hardware accelerate an element at any time, keep that element hardware-accelerated at all times. You don’t want to have a brief moment where the element disappears as the device moves it to and from the CPU to GPU.

Avoid repaints and reflows

Repaints and reflows are some of the main causes of sluggish JavaScript, and a main cause of janky animation.

A repaint is a redrawing of the screen when an element’s appearance has been altered without affect on layout. Changing an element’s color, visibility, or background image will cause a repaint. Repaints are generally cheap, but can be expensive, as the visibility of all the nodes in the DOM tree and all the layers of each node must be measured. Repaints can be costly when alpha transparency is involved.

Rendering alpha transparent blurs such as shadows or alpha transparent gradients will always take more time to render, as the browser needs to calculate the resulting color of every pixel based on the transparency over the color underneath it. This occurs even if the color, in the end, is not visible because of a design element on top of it, as CSS properties like background image and shadows are drawn from back to front.

The time to paint is really ridiculously fast. Generally, optimizing other areas will give you more bang for your buck. However, if you are repainting repeatedly, such as a non-hardware-accelerated transition or animation, minimizing repaint time is vital. In animating, the browser must repaint the nodes being animated in generally less than 16.67 ms for the animation to not appear janky. These overdrawn pixels can waste a lot of processing time for CPU-based rasterizers.

A reflow is even more critical to performance because it involves changes that affect the layout of a portion of the page (or the whole page). Reflow of an element causes the subsequent reflow of all child and ancestor elements as well as any elements following it in the DOM. A reflow is the browser process for recalculating the size and positions of all the DOM nodes when the browser needs to calculate the size of an element or when it re-renders a part of or an entire document.

When the browser needs to measure or reflow a single element in the document, unless absolutely positioned or in its own render layer, it generally reflows not just that relevant node, but the node’s ancestral elements and all elements that come after it.

Note

Some of the nodes that have their own render layer include the document itself, explicitly CSS positioned (relative, absolute, or a transformed) nodes, transparent nodes, nodes with overflow, alpha mask or reflection, WebGL, hardware-accelerated content, and <video> elements.

During a reflow, users are blocked from interacting with the page. It’s therefore important to prevent reflows, and minimize reflow time when they do occur. Scripts and even some CSS can cause a reflow. The DOM tree, styles, and assets can impact reflow speed.

There are many things that can cause a reflow, including adding, removing, updating, or moving DOM nodes, changing the display or box model properties of a node, adding a stylesheet or inline styles, resizing the window or changing the orientation, scrolling, and querying style information via JavaScript.

To reduce the number of reflows, batch your style queries, change styles via changing a CSS class rather than adding inline styles.

Instead of changing individual styles, change the class name. If the styles are dynamic, edit the cssText property rather than the style property:

myNode.style.cssText += "; left: 50p%; top: 0;";

Batch DOM changes and do them off of the live DOM tree. Don’t ask for computed styles unless necessary. And if you do so, batch the queries and cache the results into local variables, working with the copy. Make your updates in a clone of the content, make all your changes offline, then add back when complete.

This can be done in a documentFragment or a copy of the document section you’re editing. If you need to, you can even hide the element with display: none, make your plethora of changes, then reset the element to its default display. This method reflows exactly twice: when you hide and when you show again. This may sound like a lot, but this may be less than would otherwise occur if you’re making hundreds of changes causing reflows on a live node.

To make the reflows that do occur happen faster, you should minimize the number of DOM nodes, eliminate overly complex CSS selectors, and ensure that all animations are hardware-accelerated.

The deeper the DOM, the more time it takes for every reflow. Changes at one level in the DOM tree can cause changes at every level of the DOM tree, from the last of the nodes descendants all the way up to the document root. The more nodes you have, the longer it takes to reflow them all.

If you make complex rendering changes such as animations, do so out of the flow. Create a separate rendering layer with position: absolute;, position: fixed;, or transform: translatez(0); to accomplish this.

tags: , , ,