Javascript 中的 img onload-event 时没有页面呈现

No page rendering while img onload-event in Javascript

我有一个 vanilla Javascript,它将 img 的 src 属性设置为 data-uri。 在分配给 onload 事件的函数中,我尝试在页面上显示一些可能需要一段时间的准备工作的进度(生成了多个 canvas 元素并设置了一些设置)。

这是应该呈现进度条的函数:

let progress = document.querySelector('.progress');

function showProgress(message, percent) {
    console.log(message + ' (' + (percent * 100).toFixed(0) + '%)');
    progress.querySelector('.progress--message').innerHTML = message;
    progress.querySelector('.progress--bar-container--bar').style.width = (percent * 100).toFixed(0) + '%';
}

代码是运行这样的:

img.onload = function() {
    showProgress('Preparing image...' .1);

    // Some preparations...

    showProgress('Preparing tools...', .2);

    // etc...

    showProgress('Done...', 1);
}

控制台日志记录按预期工作,但 DOM 元素的呈现停止直到函数结束,并在一切就绪后显示“完成”状态。

onload 事件处理中是否存在渲染阻塞,是否可以规避?或者我错过了什么?代码本身对结果没有任何影响。即使函数的第一行也有这种奇怪的行为。

除非你使用后台 Web Workers 按摩(更复杂,特别是在你的情况下),你的进度条 呈现 只能在完成后发生return 来自您的脚本;因此,在每次 showProgress 调用之后。现在,如何 return,渲染然后重新启动剩余的?好吧,这就是 0 毫秒的 setTimeout 派上用场的地方。有了它,以下应该起作用:

img.onload = function() {
    showProgress('Preparing image...' .1);
    setTimeout(function() {
        // Some preparations...
        showProgress('Preparing tools...', .2);
        setTimeout(function() {
            // etc...
            showProgress('Done...', 1);
        }, 0);
    }, 0);
}

另一种嵌套代码的方法是递归调用——但每次调用都将在以下步骤开始之前完成——在一系列步骤中,如下所示:

steps = [
  function() { // first step (img loading just completed so, show Preparing it right away)
      showProgress('Preparing image...', .1);
  },
  function() { // second step
      // Some preparations...
      showProgress('Preparing tools...', .2);
  }, // other steps...
  function() { // last step
      // Last things to do...
      showProgress('Done...', 1);
  }
];
// function that processes "steps" - array of at least 1 function - until empty
function displayAfterEachStep() {
    steps.shift()(); // Pull out the first step and run it
    if(steps.length > 0) // if there's still steps
         setTimeout(displayAfterEachStep, 0); // Recall the process after page rendering
}
img.onload = displayAfterEachStep;

here 及周围可以找到许多其他解决 HTML 中缺少渲染事件的好主意。

我在完成其他一些项目后回到了这个问题,并使用 Promises 解决了我的问题,如下所示:

showProgress("Loading image...", 0.0) // Reset progress bar

const img = document.createElement("img") // Get image element

const promise = new Promise((resolve, reject) => { // Instantiate a Promise
  img.onload = resolve // Set onload callback to resolve (this triggers the chain)
  img.onerror = reject

  img.src = "/path/to/image.jpg" // Set src attribute to start loading image
})
.then(() => { // When promise is resolved: Start step 2
  showProgress("Preparing image...", 0.3)
  // ...
})
.then(() => { // When promise is resolved: Start step 3
  showProgress("Preparing tools...", 0.6)
  // ...
})
.then(() => { // When promise is resolved: Finish up
  showProgress("Ready...", 1)
  // ...
})

使用 Promises,您可以将异步任务按顺序排列,同时仍保持可读性。

进一步阅读:Promises on MDN