js执行阻塞transition过渡效果

前言

最近在做echarts换肤的过程中遇到了切换主题之后,transition过渡产生了延迟,原因是js异步任务阻塞了页面的渲染。

正文

image.png

图上可以看出,切换了主题之后其他部分没有过渡效果的组件颜色已经切换,中间白色的部分是element组件,饿了么自带的过渡效果要等到js任务执行完才会触发页面重绘。


elementui transition

用例

<html>
    <head>
        <meta charset="utf-8" />
    </head>
    <body>
        <style>
            :root {
                --bg-color: rgb(67, 69, 73);
            }
            :root[theme='light'] {
                --bg-color: rgb(17, 199, 153);
            }
            body {
                width: 1000px;
                margin: 0;
                margin: 0 auto;
                transition: all 0.3s;
                background: var(--bg-color);
                padding: 120px;
            }
        </style>
        <button class="changeStyle">切换样式</button>
        <script>
            document.querySelector('.changeStyle').onclick = async function onclick(e) {
                document.querySelector('html').setAttribute('theme', 'light');
                const time = new Date().getTime();
                while (new Date().getTime() - time < 1500) {}
                console.log('over');
            };
        </script>
    </body>
</html>


解决方法

  • 可以通过全局去除过渡效果,去除背景色和边框过渡
* {
   transition: background-color 0s !important;
}
  • 如果js任务不需要立即执行的话,可以加上setTimeout延时执行
setTimeout(() => {}, 1000)

这里试了下延迟0ms不行,按理说settimeout异步宏任务,也不会阻塞页面渲染的啊,具体原因还不清楚。知道的老铁可以介绍下。


更新

之前的分析存在问题,最近阅读了一篇关于浏览器渲染的文章,其实是因为布局树是依赖dom树和cssom树的,只有当js执行完成之后才会构建布局树,页面才会触发重绘。

image.png


第二次更新

  • 先上dome示例代码:
<html>
    <head>
        <meta charset="utf-8" />
    </head>
    <body>
        <style>
            :root {
                --bg-color: rgb(0, 0, 0);
                --box-color: pink;
            }
            :root[theme='light'] {
                --bg-color: rgb(246, 246, 246);
                --box-color: yellow;
            }
            body {
                width: 1000px;
                margin: 0;
                margin: 0 auto;
                transition: all 0.3s;
                background: var(--bg-color);
                padding: 120px;
            }
            .color-box {
                width: 300px;
                height: 300px;
                background: var(--box-color);
            }
        </style>
        <button class="changeStyle">切换样式</button>
        <div class="color-box"></div>
        <script>
            document.querySelector('.changeStyle').onclick = async function onclick(e) {
                const theme = document.querySelector('html').getAttribute('theme');
                document.querySelector('html').setAttribute('theme', theme === 'light' ? '' : 'light');
                setTimeout(() => {
                    const time = new Date().getTime();
                    while (new Date().getTime() - time < 3000) {}
                    console.log('over');
                }, 150);
            };
        </script>
    </body>
</html>

这次加上了settimeout延时150ms执行一个长耗时的任务,transition的过渡时间是300s,可以看到颜色过渡卡在了中间,等耗时任务执行完了才会继续颜色过渡。

所以js确实会阻塞transition过渡效果。

promise微任务

如果把上述的settimeout 换成promise微任务,又会怎么样呢?

new Promise((resolve, reject) => {
              const time = new Date().getTime();
                    while (new Date().getTime() - time < 3000) {}
                    resolve();
                    console.log('over');
});

此时,html元素上的theme属性会在耗时任务3s执行完了,才会被加上去,当然颜色肯定也是在3s之后才会变化。

  • 也就是说微任务阻断了结构树的生成过程,微任务执行完之后才触发结构树的生成。
  • 而settimeout属于宏任务,延时执行不会阻断结构树的生成。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容