使画布基于滚动的图像序列动画更加平滑

5

我想要一个基于滚动的动画,类似于这个网站上的图片序列:

http://www.clearmotion.com/technology

这个动画看起来滚动非常流畅。

我尝试了这个 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>

然而,效果看起来很糟糕。

当我的鼠标停止滚动时,动画立即停止。

我该如何使它更加流畅?


我有一只自由滚动的罗技鼠标。当我将其设置为自由滚动模式时,它非常平滑。动画和模型非常棒。您需要进行一些插值来渲染需要滚动的步骤之间的过渡。 - Tschallacka
1个回答

3
这种情况发生的原因是滚轮事件可能会以高于屏幕刷新率的非常高的频率触发。
这意味着每绘制到屏幕上的帧实际上在画布上绘制了几帧。浏览器难以处理所有这些请求,最终导致产生延迟。

为了避免这种情况,您可以对事件进行限制:只有在预定义时间内没有被触发时才有条件地执行回调。
在处理视觉动画时,这种限制时间的良好基础是屏幕刷新时间。我们还有一个定时方法可以完全做到这一点: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>

现在,由于我们正在阻止整个事件处理,你的动画可能看起来更慢(即使更平滑)。为了规避这种情况,您仍然可以尽可能快地更新变量,并且只限制实际绘图。

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>

请注意,我已更新您的事件以使用唯一符合规范的 WheelEvent
还要注意,您最好加载一个视频,而不是那么多静止图像。甚至可以将原始3D动画转换为webGL。

这个解决方案看起来更好,但是网站有一个效果我不知道如何访问。当鼠标停止滚动时,会出现尾随效果,它不会立即停止渲染。 - Deryckxie
@Deryckxie,这里有你想要的示例:http://inertia-momentum-scroll.webflow.io/。 - Hackinet

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接