Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

make canvas scroll-based image sequences animation more smooth

I wanted a Scroll-Based Animation with image sequences like this:

http://www.clearmotion.com/technology

This animation seems scroll smoothly

I've tried with this fiddle

var images = new Array();
var currentLocation = 0;
var totalImages = 200;

for (var i = 1; i < totalImages; i++) {
    var img = new Image;
   
    var slug = '000' + i;
    
     img.src = 'https://s3.amazonaws.com/clearmotion/hero/high-min/frame'+ slug.slice(-3) +'-low.jpg'
    images.push(img);
}

var c = document.getElementById("background");
var ctx = c.getContext("2d");

var mouseWheel = function () {
    window.addEventListener('mousewheel', function (e) {
        e.preventDefault(); // No scroll

        // The following equation will return either a 1 for scroll down
        // or -1 for a scroll up
        var delta = Math.max(-1, Math.min(1, e.wheelDelta));

        // This code mostly keeps us from going too far in either direction
        if (delta == -1) currentLocation += 1;
        if (delta == 1) currentLocation -= 1;
        if (currentLocation < 0) currentLocation = 0;
        if (currentLocation >= (totalImages - 1)) currentLocation = (totalImages - 1);
        console.log("Current location " + currentLocation);

        // See below for the details of this function
        setImage(currentLocation);
    });
}

var setImage = function (newLocation) {
    // drawImage takes 5 arguments: image, x, y, width, height
    ctx.fillRect(0, 0, c.width, c.height);
    ctx.drawImage(images[newLocation], 0, 0, 1000, 1000);
}

images[0].onload = function () {
    ctx.fillRect(0, 0, c.width, c.height);
    ctx.drawImage(images[currentLocation], 0, 0, 1000, 1000);
    mouseWheel();
};
  <canvas id="background" width="1000" height="1000"></canvas>

`

however, the effect looks terrible.

when my mouse stop scrolling, the animation stop immediately

how can I make it more smooth?

like image 242
Deryckxie Avatar asked Oct 16 '22 19:10

Deryckxie


1 Answers

This happens because the wheel event might fire at a very high rate, higher than your screen refresh rate.
This means that for every frame painted to the screen, you actually painted several frames on the canvas. The browser then has some issues to handle all these requests, and end up creating lag.

To avoid it, you can throttle your event: execute your callback conditionally, only if it's been more than some predefined time it didn't already fired.

When dealing with visual animations, a good base for such throttling time is the one of the screen refresh. And we've got a timing method that does exactly this: requestAnimationFrame.

var images = new Array();
var currentLocation = 0;
var totalImages = 200;

for (var i = 1; i < totalImages; i++) {
  var img = new Image;
  var slug = '000' + i;
  img.src = 'https://s3.amazonaws.com/clearmotion/hero/high-min/frame' + slug.slice(-3) + '-low.jpg'
  images.push(img);
}

var c = document.getElementById("background");
var ctx = c.getContext("2d");

var mouseWheel = function() {
  var evt = null; // avoids a new 'draw' function generation
  window.addEventListener('wheel', function(e) {
    e.preventDefault(); // No scroll

    if (!evt) { // if set, we already are waiting
      evt = e; // store our event
      requestAnimationFrame(draw); // wait next screen refresh to fire
    }
  });
  // the throttled funtion
  function draw() {
    var e = evt;
    var delta = Math.max(-1, Math.min(1, e.deltaY));
    if (delta == -1) currentLocation += 1;
    if (delta == 1) currentLocation -= 1;
    if (currentLocation < 0) currentLocation = 0;
    if (currentLocation >= (totalImages - 1)) currentLocation = (totalImages - 1);
    setImage(currentLocation);
    evt = null; // so the throttler knows we can kick again
  }
}

var setImage = function(newLocation) {
  if (!images[newLocation]) return;
  ctx.fillRect(0, 0, c.width, c.height);
  ctx.drawImage(images[newLocation], 0, 0, 1000, 1000);
}

images[0].onload = function() {
  ctx.fillRect(0, 0, c.width, c.height);
  ctx.drawImage(images[currentLocation], 0, 0, 1000, 1000);
  mouseWheel();
};
<canvas id="background" width="1000" height="1000"></canvas>

Now, since we are blocking the whole event handling, your animation might also look like being slower (even if smoother). To circumvent this, you can still update your variables as fast as you can, and only throttle the actual drawing.

var images = new Array();
var currentLocation = 0;
var totalImages = 200;

for (var i = 1; i < totalImages; i++) {
  var img = new Image;
  var slug = '000' + i;
  img.src = 'https://s3.amazonaws.com/clearmotion/hero/high-min/frame' + slug.slice(-3) + '-low.jpg'
  images.push(img);
}

var c = document.getElementById("background");
var ctx = c.getContext("2d");

var mouseWheel = function() {
  var newLocation = null;
  window.addEventListener('wheel', function(e) {
    e.preventDefault(); // No scroll

    // update our variable at high frequency
    var delta = Math.max(-1, Math.min(1, e.deltaY));
    if (delta == -1) currentLocation += 1;
    if (delta == 1) currentLocation -= 1;
    if (currentLocation < 0) currentLocation = 0;
    if (currentLocation >= (totalImages - 1)) currentLocation = (totalImages - 1);

    if (newLocation === null) { // if set, we already are waiting to draw
      requestAnimationFrame(setImage);
    }
    newLocation = currentLocation;
  });

  function setImage() {
    if (images[newLocation]) {
      ctx.fillRect(0, 0, c.width, c.height);
      ctx.drawImage(images[newLocation], 0, 0, 1000, 1000);
    }
    newLocation = null; // so the throttler knows we can draw again
  }

}


images[0].onload = function() {
  ctx.fillRect(0, 0, c.width, c.height);
  ctx.drawImage(images[currentLocation], 0, 0, 1000, 1000);
  mouseWheel();
};
<canvas id="background" width="1000" height="1000"></canvas>

Note that I did update your event to use the only specs compliant one: WheelEvent.
Also note that you might be better loading a video instead of that many still images. Or even, convert your original 3D animation to webGL.

like image 78
Kaiido Avatar answered Oct 19 '22 23:10

Kaiido