js执行阻塞transition过渡效果
2021-11-24 本文已影响0人
阿明先森
前言
最近在做echarts换肤的过程中遇到了切换主题之后,transition过渡产生了延迟,原因是js异步任务阻塞了页面的渲染。
正文

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

用例
<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执行完成之后才会构建布局树,页面才会触发重绘。

第二次更新
- 先上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属于宏任务,延时执行不会阻断结构树的生成。