Android 4+ html5 canvas not redrawing
Asked Answered
M

3

6

I am currently developing an android application using phonegap. I have an html5 canvas that I am drawing and animating objects on. It works great on android 2.3, but on android 4+ it is not redrawing the canvas. I tried using both kinetic.js and easel.js/tween.js for my animations and the problem with not clearing the canvas occurred for both of these libraries. I experienced some success showing and hiding a div over the canvas but it does not work all the time. I can only assume that this is an android 4+ specific bug or some type of feature to enhance the html5 canvas performance.

Does anyone know if there is some setting I can change or method I can call in android 4 or javascript which would allow me to force the redraw of my html5 canvas during animations?

It should also be noted that the animations seem to work with easel.js/tween.js in the 4.1 google api emulator (the canvas clears and redraws), but not on phones running 4.1.1.

I've done some further research into what is happening. Essentially it appears that the shape at the very beginning of an animation is leaving an artifact, which clearRect does not clear. I have a big circle that i am shrinking to a small circle. The animation still happens but the big circle is not affected by calling clearRect on the canvas.

Matsumoto answered 9/10, 2012 at 16:48 Comment(1)
So I figured out a fix for this. I still do not know the root cause or why it happens in only Android 4+. If I put a setTimeout for a second before i draw to the canvas for the first time. It doesn't leave artifacts anymore.Matsumoto
A
7

I don't have a fix for the root problem either, but I've come up with another imperfect workaround that doesn't add a delay to your code. Draw a a dummy object to the canvas first. Then draw your animation objects (or draggable objects. Since it happens on drag as well). It seems that the first object drawn is persistent (that is, can't be properly cleared). With KineticJs, I do the following... 1.) create the stage, 2.) draw an object (like a rectangle the size of the stage as a background. Note that the object can't be transparent), 3.) add the layer to the stage, and 4.) run layer.draw().

After that I can draw anything to the canvas and it behaves normally in Android. (see example below. Without the background the object is dubplicated on first drag. To test it, just set the opacity of the background to 0).

One caution: In my experience so far, this is happening to the first object in any given layer. So I have to tweak each layer in the stage. Depending on your application it might or might not be better than adding timing delays to the code.

This seems to be an Android bug starting with Android 4.1.x It does not occur in 4.0.x. And it has not been fixed in the recent upgrade to 4.1.2 sent out this week. Similar problems have been tied to the setting of the overflow-x property in CSS (see http://code.google.com/p/android/issues/detail?can=2&start=0&num=100&q=&colspec=ID%20Type%20Status%20Owner%20Summary%20Stars&groupby=&sort=&id=35474).

<script>
  window.onload = function() {
    var stage = new Kinetic.Stage({
      container: "container",
      width: 578,
      height: 200
    });

    var boxLayer = new Kinetic.Layer();
    stage.add(boxLayer);
        var background = new Kinetic.Rect({
          x: rectX,
          y: rectY,
          width: 578,
          height: 200,
          fill: "white",
          stroke: "white",
          strokeWidth: 4,
          draggable: false
        });
    boxLayer.add(background)
    boxLayer.draw();


    var rectX = stage.getWidth() / 2 - 50;
    var rectY = stage.getHeight() / 2 - 25;

    var box = new Kinetic.Rect({
      x: rectX,
      y: rectY,
      width: 100,
      height: 50,
      fill: "#00D2FF",
      stroke: "black",
      strokeWidth: 4,
      draggable: true,
      opacity: 1.0            
    });

    boxLayer.add(box);
    boxLayer.draw();


  };

</script>
Alethaalethea answered 13/10, 2012 at 17:48 Comment(0)
H
1

I ran into the same problem on Android 4.1.1 today, only that my issue was even more pesky. I tried the setTimeout and "draw dummy object" workarounds suggested in other answers but the problem still persists. After more trials and errors I found that if I draw the dummy object for the first several frames (first two frames was sufficient for me), the canvas can be successfully cleared afterwards.

Another related issue is that if I show a popover on top of the canvas, then dismiss it, the canvas clearing bug will happen again. And this time drawing the dummy objects for a few frames doesn't work any more, and what works for me is to resize the canvas first, then draw the dummy objects for a few frames.

Obviously these are ugly workarounds but may be useful to others who experience the same issue.

Hoke answered 30/10, 2012 at 16:55 Comment(1)
Wow thanks for posting this. In my case I was animating images, and setting the image after the animation had run a few frames prevented the "ghosting" issue.Adali
S
1
function drawImage(imageObj) {
    var stage = new Kinetic.Stage({
        container: "container",
        width: wW,
        height: wH
    }),
    timer = null,
    dummys = 1,
    layer = new Kinetic.Layer();

    timer = setInterval( function() {
        if( dummys >= 2 ) {
            clearInterval(timer);
            layer.clear();
            var darthVaderImg = new Kinetic.Image({
                image: imageObj,
                x: stage.getWidth() / 2 - 200 / 2,
                y: stage.getHeight() / 2 - 137 / 2,
                draggable: true
            });
            layer.add(darthVaderImg);
            stage.add(layer);   
        }else{
            var background = new Kinetic.Rect({
              x: 0,
              y: 0,
              width: wW,
              height: wH,
              fill: "white",
              draggable: false
            });
            layer.add(background)
            layer.draw();
            dummys++;
        }
    }, 10 );

}

var wH = window.innerHeight,
    wW = window.innerWidth,
    mCanvas = document.getElementById('container'),
    imageObj = new Image();

$(document).ready(function(){
    mCanvas.style.width = wW;
    mCanvas.style.height = wH;
    imageObj.src = 'img/darth-vader.jpg';
    imageObj.onload = function() {
        drawImage(this);
    };
});

works like a charm

Spoils answered 27/11, 2012 at 16:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.