前言
经过两天的思考终于在这个问题上算是告一段落,于是写下这篇文章希望有缘人能够指点一二。
起因
闲来无事用原生js写了个静态的轮播图,想要用css给图片元素添加上transition:opacity
动画实现淡入淡出的效果,结果发现动画没有触发。
网上搜索一番用内联样式解决了问题,于是认为是图片上display:none
和display:block
的切换触发了reflow导致transition失效了。
经过
一天之后,又写了个hover改变伪元素border的width的特效。
写完之后细想,不好,width改变必然触发reflow1,为什么transition能正常工作?我昨天的思路有问题呀。
这个回答2里说,因为display:none
的元素转换为display:block
之后没有transition动画需要的初始值,所以不触发动画。建议使用height:0
与height:auto
的切换来达到效果。
我试了一下,很对,但是为什么呢?
大胆假设,小心求证。既然reflow没有问题,跟着浏览器渲染的路径上溯,可能问题出在了DOM Tree -> Render Tree这个过程中。
求助百度之后,看到这篇文章的解释3,对味了。display:none
的节点和head
节点不会被挂到render tree上。换句话说,attachment没做,在css文件中指定的css rules不会被附加到dom元素上。这就解释了为什么没有初始值,因为根本不存在css。而回过头来,内联样式因为在节点上,所以DOM parsing的时候就一并处理并附加了(推迟到attachment不合适吧?),这样transition动画才有了属性的初始值。
可以很轻松地通过代码验证。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>test none elements</title>
<style>
.main{
display: none;
background: #00AAFF;
width: 300px;
line-height: 36px;
box-sizing: border-box;
color: #FFF;
margin: 0 auto;
}
</style>
</head>
<body>
<div class="main" style="opacity: 1;">
<span>Hello</span>
</div>
<div class="main">
<span>Hello</span>
</div>
<script>
const [main, main1] = document.getElementsByClassName('main')
console.log(main)
console.log(main1)
</script>
</body>
</html>
在console中可以访问到这两个全局变量main
和main1
。
main.style.opacity //"1"
main1.style.opacity //""
效果如图。如果继续查看节点的style属性,就会发现,内部样式表中设置的属性都不存在,没有被附加到DOM节点上。
总结
适当的假设和变量的控制对于实验来说太重要了。很多时候搞不明白原因可能只是因为变量实在是太多了。
参考资料
- https://csstriggers.com/width
- https://*.com/questions/49269614/why-does-reflow-need-to-be-triggered-for-css-transitions
- https://blog.csdn.net/weixin_43294560/article/details/103394794