JavaScript尝试更新DOM之前运行的所有内嵌代码,因为后者是缓慢的。整个循环在页面更新一次之前运行。
我们可以强制页面更新:
for(var i =0; i<100000; i++){
notice.innerHTML = i;
notice.getBoundingClientRect(); // Force DOM update to get latest size
console.log(i);
}
然而,当DOM被更新它仍然径直回到JS继续运行循环 - 这是更新的速度比你可以看到和仍然似乎挂起。
你需要做的是暂停JS执行,以便页面有机会更新。
我们可以异步JS功能做到这一点 - 这是结束当前JS块但排队的回调函数后火(在这种情况下,用户看到后话):
var notice = document.getElementById("esperanto-notice");
var i = 0;
// Function to write the next value, increment, and queue up the next timeout
var nextFunc = function() {
console.log(i);
notice.innerHTML = i++;
if (i < 100000)
setTimeout(nextFunc, 16); // 16ms is 60FPS
else
console.log('done');
}
// Start it off
nextFunc();
<label id="esperanto-notice"></label>
现在整个JS运行并且nextFunc
执行一次。它也会在16ms后将它排队等待再次启动,但在此之前它会让浏览器更新页面。
每次nextFunc
都会触发它使用setTimeout
来排队下一次执行,然后页面有一帧要更新(所以用户看到它),然后它再次触发。
现代浏览器提供的功能尤其是等待下一帧:requestAnimationFrame
:
var notice = document.getElementById("esperanto-notice");
var i = 0;
// Function to write the next value, increment, and queue up the next timeout
var nextFunc = function() {
console.log(i);
notice.innerHTML = i++;
if (i < 100000)
// Request the next visible frame to continue
requestAnimationFrame(nextFunc);
else
console.log('done');
}
// Start it off
nextFunc();
<label id="esperanto-notice"></label>
这是最好的办法,除非你需要支持IE浏览器的旧版本(< = 9 ),因为可以处理任何帧的持续时间(如果你有很多的麻烦,则可以有setTimeout
问题)。
最后,这是新的语言关键字async
和await
可以使您的代码更容易。您可以保持循环并抽象等待DOM更新。接下来的这个片段只能运行在现代浏览器Chrome和FX(但可以使用巴贝尔或打字稿支持IE):
(async function() {
var notice = document.getElementById("esperanto-notice");
for (var i = 0; i < 100000; i++) {
console.log(i);
notice.innerHTML = i;
// Pass back to the DOM until the next frame
await new Promise(r => requestAnimationFrame(r));
}
console.log('done');
})();
<label id="esperanto-notice"></label>
哇。这是一个非常全面的答案。它非常适合我的需求。非常感谢你! – user3649135