使用context.drawImage()时会有700ms的奇怪延迟;

时间:2019-07-01 20:46:18

标签: javascript animation canvas

我正在制作一个小的画布动画,这需要我逐步浏览大的Sprite Sheet PNG,因此我从drawImage()中获得了很多收益。过去我从来没有遇到过麻烦,但是今天我在触发drawImage之后遇到了一个奇怪的阻塞延迟。

我的理解是drawImage是同步的,但是当我运行此代码时, drawImage已触发!大约在图像实际出现之前700毫秒。值得注意的是,在Chrome中为700毫秒,在Firefox中为1100毫秒。

def other_server():
    s = eventlet.listen(('0.0.0.0', 6000))
    pool = eventlet.GreenPool(5)
    while True:
        c, address = s.accept()
        pool.spawn_n(function, c)

if __name__ == '__main__':
    eventlet.spawn(other_server)
    eventlet.wsgi.server(eventlet.listen(('0.0.0.0', 4000)), app)

enter image description here

在较大的上下文中,此代码在requestAnimationFrame循环中运行,我只在第一次执行drawImage时遇到这种延迟。

我认为这与我的Sprite Sheet的大尺寸(28000×3200)@ 600kb有关,尽管onload事件似乎可以正确触发。

edit:这是rAF帧之间的时间(毫秒)的打印输出。除非删除drawImage函数,否则我将始终获得此结果。

enter image description here

1 个答案:

答案 0 :(得分:2)

这是因为加载事件仅是网络事件。它仅表明浏览器已获取媒体,解析元数据,并确认它是可以解码的有效媒体文件。
但是,触发此事件时可能仍未制作渲染部分,因此这就是为什么您要进行第一次渲染需要花费大量时间的原因。 (尽管以前它只是一种FF行为。)

由于是drawImage()是同步的,因此也将使解码+呈现同步操作。的确如此,您甚至可以使用drawImage来告诉when an image really is ready.

请注意,HTMLImageElement接口上现在有一个decode()方法,它将以非阻塞方式准确地告诉我们这一点,因此最好在可用时使用它,并且无论如何都要进行预热在运行扩展的图形应用程序之前,请在屏幕上关闭所有功能。


但是,由于您的源图像是一张Sprite-sheet,因此实际上您可能对createImageBitmap()方法更感兴趣,该方法将从您的源图像生成ImageBitmap,并且可以选择将其切除。这些ImageBitmap已被解码,可以立即拖到画布上。这应该是您的首选方式,因为这样也可以避免每次绘制整个Sprite-sheet。对于不支持该方法的浏览器,您可以通过返回HTMLCanvasElement以及在其上绘制的图像部分来猴子修补它:

if (typeof window.createImageBitmap !== "function") {
  window.createImageBitmap = monkeyPatch;
}

var img = new Image();
img.crossOrigin = "anonymous";
img.src = "https://upload.wikimedia.org/wikipedia/commons/b/be/SpriteSheet.png";
img.onload = function() {
  makeSprites()
    .then(draw);
};


function makeSprites() {
  var coords = [],
    x, y;
  for (y = 0; y < 3; y++) {
    for (x = 0; x < 4; x++) {
      coords.push([x * 132, y * 97, 132, 97]);
    }
  }
  return Promise.all(coords.map(function(opts) {
      return createImageBitmap.apply(window, [img].concat(opts));
    })
  );
}

function draw(sprites) {
  var delay = 96;
  var current = 0,
    lastTime = performance.now(),
    ctx = document.getElementById('canvas').getContext('2d');
  anim();

  function anim(t) {
    requestAnimationFrame(anim);
    if (t - lastTime < delay) return;
    lastTime = t;
    current = (current + 1) % sprites.length;
    ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height)
    ctx.drawImage(sprites[current], 0, 0);
  }

}

function monkeyPatch(source, sx, sy, sw, sh) {
  return Promise.resolve()
    .then(drawImage);

  function drawImage() {
    var canvas = document.createElement('canvas');
    canvas.width = sw || source.naturalWidth || source.videoWidth || source.width;
    canvas.height = sh || source.naturalHeight || source.videoHeight || source.height;
    canvas.getContext('2d').drawImage(source,
      sx || 0, sy || 0, canvas.width, canvas.height,
      0, 0, canvas.width, canvas.height
    );
    return canvas;
  }
}
<canvas id="canvas" width="132" height="97"></canvas>