html/javascript 中的 onloadend 有好的替代品吗?

Is there a good replacement for onloadend in html/javascript?

我想知道是否有人知道 GlobalEventHandlers.onloadend 的良好补充剂。它完全符合我的目的,但 受 Firefox 支持,因此我想要一种更通用的方法。

具体来说,我有一个像这样的加载事件处理程序:

image.addEventListener('load', function(){
   ctxA.canvas.width = image.width;
   ctxA.canvas.height = image.height;
   ctxA.drawImage(image, 0, 0);

   let imageData = ctxA.getImageData(0, 0, image.width, image.height);
   filterData(imageData, image.height, image.width);
});

我的 filterData 函数需要一段时间才能执行。它不是永远的,但它是显而易见的。我想先把图像绘制到页面上,然后再使用数据。但是,在加载事件处理程序完全完成之前,浏览器似乎不会绘制图像。 我通过在加载事件处理程序中故意进行无限循环来确认这一点,并且从不打印图像。

这是有道理的,因为在满足所有负载要求之前,可能不应绘制图像。但是,除了onloadend之外,我不知道有什么好的方法来显示图像然后执行filterData函数。感谢任何帮助或建议。

异步调用 filterData(),这样您就不必在看到渲染图像时等待它。

image.addEventListener('load', function(){
   ctxA.canvas.width = image.width;
   ctxA.canvas.height = image.height;
   ctxA.drawImage(image, 0, 0);

   let imageData = ctxA.getImageData(0, 0, image.width, image.height);
   setTimeout(() => filterData(imageData, image.height, image.width), 0);
});

阻止您页面的不是图像的加载,而是您的 js 代码。
阻塞 JS 执行也会阻塞负责更新渲染的事件循环。所以推而广之,阻塞JS执行也意味着阻塞渲染。

这很糟糕。

所以您不需要其他事件,顺便说一下,它看起来像是在工作只是因为偶然地,您在 浏览器有时间之后阻止了呈现渲染一次,即使此时它还没有渲染,你也不想等待随机的时间,你也不能确定这是否足够(渲染发生 60 次每秒使用 60Hz 监视器,事件循环旋转得更快,因此在此类设备上任何低于 17ms 的东西都可能提前触发)。

那么你的情况最好的是什么?

将您的计算移至 Dedicated Worker。
并行线程中的工作人员 运行 并且不会阻塞 UI 线程,这意味着浏览器可以继续更新渲染,并在您的繁重计算仍在进行时获取用户的输入等。

要启动 Worker,您需要第二个脚本,该脚本将从主页监听“消息”事件,您将从 canvas 获得的图像数据传输到 Worker 的 [=14] =] 方法。 计算完成后,您将能够将生成的 ImageData 传输回主线程以执行任何您想要的操作。

const canvas = document.querySelector( "canvas" );
const ctx = canvas.getContext( "2d" );
const worker = new Worker( getWorkerURL() );

const img = new Image();
img.crossOrigin = "anonymous";
img.src = "https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png";
img.onload = (evt) => {
  canvas.width = img.width;
  canvas.height = img.height;
  ctx.drawImage( img, 0, 0 );
  const imgData = ctx.getImageData( 0, 0, canvas.width, canvas.height );
  // transfer the ImageData to the worker
  worker.postMessage( imgData, [ imgData.data.buffer ] );
};

worker.onmessage = ({ data }) => {
  // draw back on the canvas
  ctx.putImageData( data, 0, 0 );
};


function getWorkerURL() {
  const script = `
  onmessage = ({ data }) => {
    const { width, height } = data;
    const arr = data.data;
    // a stoopid filter
    for( let y = 0; y < height; y++ ) {
      for( let x = 0; x < width; x++ ) {
        const index = ((y * width) + x) * 4;
        const r = arr[ index ];
        const g = arr[ index + 1 ];
        const b = arr[ index + 2 ];
        arr[ index ] = g ^ b;
        arr[ index + 1 ] = r ^ g
        arr[ index + 2 ] = b ^ r;
      }
    }
    // make it last longer
    const now = performance.now();
    while( performance.now() - now < 2500 ) {
    
    }
    postMessage( data );
  }
 `;
 const blob = new Blob( [ script ], { type: "text/javascript" } );
 return URL.createObjectURL( blob );

}
canvas { background: white }
<canvas></canvas>


如果您不能将计算移至 Worker(但在这里我非常有信心您可以),请在阻止 UI 之前正确等待浏览器呈现,等待下一个事件-循环迭代一个动画帧之后。

const performAfterRendering = (task) => {
  requestAnimationFrame( () => setTimeout( task, 0 ) );
};