Search code examples
javascriptcanvashtml5-canvasgsappixi.js

Divide whole canvas in equal columns


I am trying to divide my whole canvas in 20 equal columns and animate them individually on the y-axis. The goal is to create a similar wave scroll animation like here. First I tried to create the wave scroll effect with php generated images with the text on it and tried to animate them in single divs with the images as background-images. It technically worked but the performance and page load time was extremely bad. Now I want to create it with canvas: I already have the content with all the images and text in it and tried to animate it. I tried to save the whole content in columns (rectangles) with getImageData() then I created created rectangles with the ImageData and re-draw them in a loop but again the performance was terrible, especially on mobile devices. The animation loop looked as followed:

var animate = function(index, y) {
  // The calculations required for the step function
  var start = new Date().getTime();
  var end = start + duration;
  var current = rectangles[index].y;
  var distance = y - current;
  var step = function() {
    // get our current progress
    var timestamp = new Date().getTime();
    var progress = Math.min((duration - (end - timestamp)) / duration, 1);
    context.clearRect(rectangles[index].x, rectangles[index].y, columnWidth, canvas.height);
    // update the rectangles y property
    rectangles[index].y = current + (distance * progress);
    context.putImageData(rectangles[index].imgData, rectangles[index].x, rectangles[index].y);
    // if animation hasn't finished, repeat the step.
    if (progress < 1)
      requestAnimationFrame(step);
  };
  // start the animation
  return step();
};

Now the question: How can I divide the whole canvas in equal columns and animate them on the y-axis with a good performance? Any suggestions? Maybe with the help of Pixi.js / Greensock? Thanks in advance!


Solution

  • Do not use getImageData and setImageData to do animation. The canvas is an image and can be rendered just like any image.

    To do what you want

    Create a second copy of the canvas and use that canvas a source for the strips you want to render.

    Example.

    const slices = 20;
    var widthStep;
    var canvas = document.createElement("canvas");
    var canvas1 = document.createElement("canvas");
    canvas.style.position = "absolute";
    canvas.style.top = canvas.style.left = "0px";
    
    var ctx = canvas.getContext("2d");
    var ctx1 = canvas1.getContext("2d");
    var w = canvas.width;
    var h = canvas.height;
    function resize(){
        canvas.width = canvas1.width = innerWidth;
        canvas.height = canvas1.height = innerHeight;
        w = canvas.width;
        h = canvas.height;
        ctx1.font = "64px arial black";
        ctx1.textAlign = "center"
        ctx1.textBaseLine = "middle";
        ctx1.fillStyle = "blue";
        ctx1.fillRect(0,0,w,h);
        ctx1.fillStyle = "red";
        ctx1.fillRect(50,50,w-100,h-100);
        ctx1.fillStyle = "black";
        ctx1.strokeStyle = "white";
        ctx1.lineWidth = 5;
        ctx1.lineJoin = "round";
        ctx1.strokeText("Waves and canvas",w / 2, h / 2);
        ctx1.fillText("Waves and canvas",w / 2, h / 2);
        widthStep = Math.ceil(w / slices);
    }
    resize();
    window.addEventListener("resize",resize);
    document.body.appendChild(canvas);
    function update(time){
        var y;
        var x = 0;
        ctx.clearRect(0,0,canvas.width,canvas.height);
        for(var i = 0; i < slices; i ++){
            y = Math.sin(time / 500 + i / 5) * (w / 8);
            y += Math.sin(time / 700 + i / 7) * (w / 13);
            y += Math.sin(time / 300 + i / 3) * (w / 17);
            ctx.drawImage(canvas1,x,0,widthStep,h,x,y,widthStep,h);
            x += widthStep;
         }
         requestAnimationFrame(update);
    }
    requestAnimationFrame(update);