首页 > 解决方案 > 使用 context.drawImage() 时奇怪的 700 毫秒延迟;

问题描述

我正在制作一个小画布动画,它需要我逐步浏览一个大的精灵表 png,所以我从 drawImage() 中获得了很多里程。我过去从未遇到过使用它的问题,但今天我在触发 drawImage 后遇到了奇怪的阻塞延迟。

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

window.addEventListener('load', e => {
    console.log("page loaded");

    let canvas = document.getElementById('pcb');
    let context = canvas.getContext("2d");

    let img = new Image();

    img.onload = function() {
        context.drawImage(
            img,
            800, 0,
            800, 800,
            0, 0,
            800, 800
        );

        console.log("drawImage fired!");
    };

    img.src = "/i/sprite-comp.png";
});

在此处输入图像描述

在更大的上下文中,此代码在 requestAnimationFrame 循环中运行,我仅在第一次执行 drawImage 期间遇到此延迟。

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

编辑:这是 rAF 帧之间的时间(毫秒)的打印输出。除非我删除 drawImage 函数,否则我会始终如一地得到这个结果。

在此处输入图像描述

标签: javascriptanimationcanvas

解决方案


那是因为加载事件只是一个网络事件。它只告诉浏览器已经获取了媒体,解析了元数据,并且已经识别出它是一个可以解码的有效媒体文件。
但是,当这个事件触发时,渲染部分可能还没有完成,这就是为什么第一次渲染需要这么多时间。(虽然它曾经是 FF 唯一的行为..)

因为 yesdrawImage()是同步的,所以它也会使解码+渲染成为一个同步操作。确实如此,您甚至可以使用 drawImage 来判断图像何时真正准备就绪。.

请注意,现在decode()HTMLImageElement 接口上有一个方法可以以非阻塞方式准确地告诉我们这一点,因此最好在可用时使用它,并且无论如何在运行之前在屏幕外执行所有函数的预热轮一个广泛的图形应用程序。


但是由于您的源图像是一个 sprite-sheet,您实际上可能对createImageBitmap()方法更感兴趣,该方法将从您的源图像生成一个ImageBitmap,可以选择切断。这些 ImageBitmaps 已经解码,可以毫不延迟地绘制到画布上。这应该是您的首选方式,因为它还可以避免您每次都绘制整个精灵表。对于不支持此方法的浏览器,您可以通过返回 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>


推荐阅读