【发布时间】:2020-08-09 01:26:51
【问题描述】:
我有一个动态创建的进度条 (Bootstrap),它在页面加载时尽快创建,插入到 DOM 中,然后立即开始进行(进度条开始填满)。此进度条有一个过渡动画:当条的宽度增加(通过 JavaScript)时,它会平滑地进行。
进度以离散的步骤出现:比如异步检索 4 个资源,当每个资源加载时,进度条增加 25%。
问题: 进度条表示的资源加载有时非常很快(我怀疑可能涉及一些缓存)。有时,这会导致栏立即改变宽度,完全跳过过渡动画和随后的 ontransitionend 事件(因为没有过渡)。为了获得良好的视觉效果,我的代码等待 ontransitionend 事件,这意味着发生这种情况时代码会卡住。
经过艰苦的调试,我将其范围缩小到以下原因:进度条的创建、插入和几乎立即的完整进程是如此之快,以至于进度条甚至还没有被渲染就结束了。因此,当渲染确实发生时,条已经“满”,这意味着动画被跳过,因为条直接被渲染满了。
一些伪代码示例(因为代码很大且分散)。
main.js:
// Global variable
progressBar = createProgressBar();
await new Promise(function(resolve) {
// Await progress bar to fill up
progressBar.oncomplete(resolve);
// Let's say 4 resources
loadResources(function() {
// Callback function, invoked for each resource when it has loaded
// Additive 25 % increases for a total of 100 %
progressBar.progress(25);
});
});
// Finally, hide progressBar, show content
progress.js:
function createProgressBar() {
var completeCallback = function() {};
var percentage = 0;
var progressBar = ...; // Create element for progress bar
var parent = document.getElementById("progressBarContainer");
// Insert progress bar into DOM
parent.appendChild(progressBar);
// Register element transition event handler
progressBar.ontransitionend = function() {
if(percentage === 100)
completeCallback();
};
function oncomplete(callback) {
completeCallback = callback;
}
function progress(inc) {
percentage += inc;
progressBar.style.width = ( percentage + "%" );
}
return {
oncomplete: oncomplete,
progress: progress
};
}
本质上就是这样,progress() 将在渲染之前被调用 4 次,因此栏开始满并且没有调用 transitionend 事件。
然后我使用了一些谷歌魔法并找到了this article,它展示了一个如何等待动画帧的示例。我将以下代码添加到 progress.js createProgressBar() 正文中:
var loopCount = 0;
var observer = new MutationObserver(async function(mutations) {
// Await for two animation frames
await new Promise(function(resolve) {
function loop(){
window.requestAnimationFrame(function() {
loopCount++;
if(loopCount === 2) {
resolve();
} else {
loop();
}
});
}
// Start loop
loop();
});
// Trial and error, this code will now yield transition animation
progressBar.style.width = "100%";
observer.disconnect();
});
observer.observe(components.wrapper, { attributes: false, childList: true, characterData: false, subtree: true });
我尝试等待一帧,但我仍然遇到同样的问题。在示例中,我等待两帧,这似乎已经完成了技巧,因为我无法再重现错误。 (最后一种代码破坏了它之前的代码,因为我直接设置了宽度以产生错误。)
问题:
我在这里吗,是不是渲染来得太晚了?
其次,除了使用 MutationObserver 和 requestAnimationFrame 或超时之外,还有另一种更简洁的方法来解决这个问题吗?不幸的是,我无法为此找到活动。虽然这个解决方案有效,但我不喜欢“挑选”一个数字,以防错误再次出现。
【问题讨论】:
-
在 CSS 或构造函数 (
progressBar.style.width = 0) 中设置初始宽度,然后在parent.appendChild(progressBar);强制回流之后,在下一个动画帧中(就在下一次绘制之前)放置它 ->parent.appendChild(progressBar); requestAnimationFrame( () => parent.offsetWidth );这样您就可以确定对它的下一次更改style.width将触发转换。 -
哦,男孩,提出的回答问题就像是我自己写的,我之前尝试过它的两个答案所暗示的,但我将其效果视为一种巧合......非常感谢!跨度>
标签: javascript html css twitter-bootstrap