Search code examples
javascriptkonvajs

Konva.js is very laggy on mobile devices


I want to start using Konva.js library. It looks really great in my browser (chrome) on my PC and has awesome functionality. I created code to make some really easy animations (move and rotate). But when I tried to run my webpage on a mobile device or even in Safari browser, it started to be really laggy. Moreover, when I set four objects(Images) into a movement, the browser crashed.

I did some tests and realized that even draggable objects are laggy on mobile devices. (When I tried to move them, they had really slow and jerky movement).

Is there some way to optimize it. (I have tried the recommended batchDraw() function, but it didn't help)? How can I make the movement fluent? If this is not currently possible, will there be some optimization possibilities in the future?

Here is my code... but obviously you have to run it on a mobile phone to see the effect. To activate the effect click(touch) on images.

http://bannerteam.tode.cz/konvaAnim/

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
    <title>Konva animace</title>
    <style>
        body {
      margin: 0;
      padding: 0;
      overflow: hidden;
      background-color: #F0F0F0;
    }
  </style>
  <script src="https://cdn.rawgit.com/konvajs/konva/2.0.3/konva.min.js"></script>
</head>
<body>
<div id="container"></div>
<script>
    var width = window.innerWidth;
    var height = window.innerHeight;
    var lengthOfAnim = 60;
    var pos = [{x: 50, y: 50, p: 'A'}, {x: 250, y: 50, p: 'B'}, {x: 450, y: 50, p: 'C'},
                {x: 50, y: 250, p: 'D'}, {x: 250, y: 250, p: 'E'}, {x: 450, y: 250, p: 'F'},
                {x: 50, y: 450, p: 'G'}, {x: 250, y: 450, p: 'I'}, {x: 450, y: 450, p: 'J'},
                {x: 50, y: 650, p: 'K'}, {x: 250, y: 650, p: 'F'}];

    var stage = new Konva.Stage({
      container: 'container',
      width: width,
      height: height
    });

    var imageObj = [];
    var img = new Image();

    var doAnimations = function(i){
        switch(i){
            case 0:
                imageObj[i].img.opacity(1 - imageObj[i].time/lengthOfAnim);
                break;
            case 1:
                if(imageObj[i].time === lengthOfAnim -1)
                    imageObj[i].img.y(imageObj[i].img.y() + (lengthOfAnim-1)*2);
                else
                    imageObj[i].img.y(imageObj[i].img.y() - 2);
                break;
            case 2:
                imageObj[i].img.opacity(1 - imageObj[i].time/lengthOfAnim);
                if(imageObj[i].time === lengthOfAnim -1)
                    imageObj[i].img.y(imageObj[i].img.y() + (lengthOfAnim-1)*2);
                else
                    imageObj[i].img.y(imageObj[i].img.y() - 2);
                break;
            case 3:
                var parent = imageObj[i].img.getParent();
                parent.clipFunc(function(ctx) {
                    ctx.rect(imageObj[i].img.x() - 75, imageObj[i].img.y() - 75, 75*(1 - imageObj[i].time/lengthOfAnim), 150);
                    ctx.rect(imageObj[i].img.x() + 75 - 75*(1 - imageObj[i].time/lengthOfAnim) , imageObj[i].img.y() - 75, 75*(1 - imageObj[i].time/lengthOfAnim), 150);
                });
                break;
            case 4:
                var parent = imageObj[i].img.getParent();
                parent.clipHeight(150*(1 - imageObj[i].time/lengthOfAnim));
                break;
            case 5:
                imageObj[i].img.opacity(1 - imageObj[i].time/lengthOfAnim);
                imageObj[i].img.rotation(90*(imageObj[i].time/lengthOfAnim));
                break;
            case 6:
                imageObj[i].img.opacity(1 - imageObj[i].time/lengthOfAnim);
                var pom = (1 - imageObj[i].time/lengthOfAnim);
                imageObj[i].img.scale({x: pom, y: pom});
                break;
            case 7:
                if(imageObj[i].time === lengthOfAnim -1)
                    imageObj[i].img.x(imageObj[i].img.x() - (lengthOfAnim-1)*2);
                else
                    imageObj[i].img.x(imageObj[i].img.x() + 2);
                imageObj[i].img.move({x: 0, y: 10*(Math.sin(6*Math.PI*(imageObj[i].time/lengthOfAnim)))});
                break;
            case 8:
                imageObj[i].img.rotation(20*Math.sin(6*Math.PI*(imageObj[i].time/lengthOfAnim)));
                break;
            case 9:
                imageObj[i].img.opacity(0.5 + Math.abs((imageObj[i].time/lengthOfAnim-0.5)));
                break;
            case 10:
                imageObj[i].img.draggable(true);
                break;
        }

    }

    img.onload = function() {
        for(let i = 0; i < pos.length; i++){
            var layer = new Konva.Layer();

            var yoda = new Konva.Image({
                x: pos[i].x + 75,
                y: pos[i].y + 75,
                image: img,
                width: 150,
                height: 150,
                offset: {
                    x: 75,
                    y: 75
                }
                });

            imageObj.push({img: yoda, layer: layer, time: 0});

            var charac = new Konva.Text({
                x: pos[i].x + 50,
                y: pos[i].y + 160,
                text: pos[i].p,
                fontSize: 30,
                fontFamily: 'Calibri',
                fill: 'black'
            });

            if(i === 3){
                var group = new Konva.Group({clipFunc:function(ctx) {
                        ctx.rect(pos[i].x, pos[i].y, 150, 150)
                    },});
                group.add(yoda);
                layer.add(group);
            }else if(i === 4){
                var group = new Konva.Group({clip: {
                    x : pos[i].x,
                    y : pos[i].y,
                    width : 150,
                    height : 150
                    },});
                group.add(yoda);
                layer.add(group);
            }else
                layer.add(yoda);
            layer.add(charac);

            stage.add(layer);

            yoda.on('click tap', function() {
                if(imageObj[i].time === 0)
                    imageObj[i].time = lengthOfAnim;
            });
        }
        setInterval(function(){
            for(var i = 0; i < pos.length; i++){
                if(imageObj[i].time > 0){
                    imageObj[i].time--;
                    doAnimations(i);
                    imageObj[i].layer.draw();
                }
            }
        }, 40);
    }

    img.src = './castle.png';

</script>
<body>
</html>

Solution

  • There are a lot of ways to improve the performance.

    You can read a lot of tips here: https://konvajs.github.io/docs/performance/All_Performance_Tips.html

    Your stage looks simple, so performance should be very good on mobile.

    Some tips:

    1. Do not use setInterval. Use requestAnimationFrame. The animation will be much smoother
    2. use layer.batchDraw()
    3. If possible try to move animated objects into another layer, so you don't need to redraw ALL nodes