Performance problems with HTML5 Canvas in some mobile browsers.
Asked Answered
E

2

15

Hi I have a Webapp that should be able to run on both Smartphone and Desktop Browsers alike. While I was expecting to get some curious behaviour on small devices like the Iphone, I was pretty confident that it would run well on an Android Galaxy Tab which is the Android Device that I can run tests with at the moment.

Now I have installed a bunch of Browsers on the Galaxy Tab to test things with:

  • Android Native Browser
  • Chrome for Android
  • Firefox for Android

On the Desktop I have used

  • Firefox
  • Google Chrome

and finally I have an Iphone to test with.

The website uses HTML5 canvas for pixel and sprite based drawing no fancy transformation, filters or effects, mostly simple paths and polygons. I do listen to touch events and use requestAnimationFrame for proper redrawing.

Overall the application runs well on Desktop Browsers, it is also running great on iOS Safari (iPhone) and Firefox-on-Android. Yet Androids Native Browser is giving me trouble. I have set it up so that the screen flushes red when the javascript is not responsive, and it does flash almost always when touching the screen.

So I wonder whether there are any known issues with Android Native App and HTML5. Due to the nonexistent Name of the native Browser its quite hard to google information about this. Any ideas for me where I can get more information? Any ideas what might cause the lagging of the native Android browser?

There are a few ideas about the issue:

  • iOS does not support requestAnimationFrame, therefore I replaced it with a timeout based replacement. If I use that replacement on Android's native browser, the problem persists.

  • I use AJAX (google clojure xhrio) quite regularly to retrieve data from the server. Could it be that data retrieval callbacks are cloggin my event pipeline?

  • Are log console messages (console.log) known to slow down applications? Could they trigger the browser to rerun through the DOM tree or anything related?

Essentiality answered 24/4, 2013 at 11:54 Comment(1)
I can't answer your actual question, but using console can consume a significant amount of memory depending on how you're using it. Especially if you're logging large objects or logging very frequently.Liddie
L
44

I've done a lot of experiments with canvas in many browsers. Some performance issues that I noticed:

First, about your guessing:

  • When requestAnimationFrame is supported by a browser, the drawing stuff and the app itself are more responsive. Use setTimeout or setInterval as a fallback are always possible but you need to be careful about the timing. This robust polyfill may help a little, but nothing compared to native requestAnimationFrame.

  • If console.log is called every frame (or almost), yes the performance goes down. Since native Android Browser doesn't have a console Object, every time it is called will generate an error that also contributes to slow down your application. You can do this:

    if(typeof console === "undefined"){ console = {}; }

  • For intense real-time applications web sockets are faster then http requests. Unfortunately this feature is not supported by old native android browsers. If it's not possible to use web sockets, you should minimize the http requests.

Note: Chrome for android support most of HTML5 features cited here, including requestAnimationFrame and websockets.

More information:

  • Drawing text using the context 2d fillText it's too expensive, but in some browsers this is even worse. Pre-render your texts in another canvas or use bitmap fonts. (In native Android Browser, after replace filltext drawing for pre-render stuff, the performance grew from 10-15 FPS to 30-45 FPS in some games I've made).

  • Avoid scale and rotate context because they also cause drop down in performance. If you need to scale or rotate a sprite only once, use pre-render either.

  • You need to minimize the real time drawing. Pre-render your stuff whenever you can. Redraw only stuff that changed and needs to be updated.

  • Try to write memory efficient and garbage collector friendly code.

There are a lot more things to do. I just cited a few.

TIP: make some stress tests for features you don't sure if they are performance killers and capture the benchmark results.

In mobile applications, specially real time apps, all optimizations are welcome no mater if it's just an over optimization or a bit of memory gain.

For more information follow the links below:

Also search for performance in Posts & Tutorials.

EDIT
This jsfiddle code snippet shows some stuff covered in this answer and provides a rough fps counter to benchmark. Edit this fiddle by yourself and check it out.

Limburg answered 25/4, 2013 at 6:47 Comment(2)
really helpful and insightful answer. Very Much appreciated ;)Essentiality
When using additional canvas els for pre render, make sure that their contents do not get clipped, e.g. when rotating text. in that case, you can translate the content on the pre render canvas, and then move it back when using the canvas using drawImage: preRenderCtx.translate(100, 100); preRenderCtx.rotate(rotation); preRenderCtx.fillText(char, 0, 0); context.drawImage(preRenderCanvas, x-100, y-100);Measures
W
0

Depending on what you are drawing, the most common performance improvement strategy with Html5 canvas is to utilize layers (i.e. multiple canvases) and only update the layers that need to be redrawn, rather than redrawing the whole thing on each animation frame. You can roll something like this yourself, or you could use something like http://www.concretejs.com/ which is a lightweight Html5 canvas framework that enables peripheral things like hit detection, layering, caching, pixel ratio support, downloads, etc. You would do something like this:

var wrapper = new Concrete.Wrapper({
  width: 500,
  height: 300,
  container: el
});

var layer1 = new Concrete.Layer();
var layer2 = new Concrete.Layer();

wrapper.add(layer1).add(layer2);

// something happens which requires you to redraw layer2, but not layer1...
layer2.sceneCanvas.context.fillStyle = 'red';
layer2.sceneCanvas.context.fillRect(0, 0, 200, 100);
Wealth answered 2/4, 2016 at 22:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.