【问题标题】:Changing transition property with javascript too quickly after DOM insert skips CSS transition animation? [duplicate]在 DOM 插入跳过 CSS 过渡动画后过快地使用 javascript 更改过渡属性? [复制]
【发布时间】: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


【解决方案1】:

为什么不制作纯 HTML 和 CSS 进度条并使用 js 控制其状态 - 更改类

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-04-20
    • 1970-01-01
    • 1970-01-01
    • 2021-03-12
    • 1970-01-01
    • 2017-10-09
    • 2020-03-20
    相关资源
    最近更新 更多