起因
之前看了一个问题(如何实现一个进度条),也看了其他人的文章,觉得还有很多的坑没有讲到,如果读者根据作者的文章去实现,可能会导致更大的问题。下面就说一下思路,大家看下自己在哪一层!
第一层
我们最简单的实现就是一个 div 里面套 div,动态改变里面 div 的宽度,就达到了一个进度条的效果
代码:
<!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>Document</title>
<style>
#container {
width: 600px;
height: 20px;
display: inline-block;
border: 1px solid;
overflow: hidden;
}
#progress {
width: 0;
background: pink;
height: 100%;
transition: width 0.5s linear;
}
</style>
</head>
<body>
<div id="container">
<div id='progress'></div>
</div>
<div>
<button id='btn'>按钮</button>
</div>
<script>
const btn = document.getElementById('btn')
const progress = document.getElementById('progress')
btn.onclick = () => {
let width = 0
const timer = setInterval(() => {
if (width === 100) return clearInterval(timer)
progress.style.width = `${++width}%`
}, 50);
}
</script>
</body>
</html>
性能:
从图上就可以看出来改变 width 会频繁的导致 layout,而一般情况下 layout 又会引起 paint,所以会花费很多的时间。
第二层
我们现在到了第二层,所以我们就想到了用 transform,我只去改变他的位置,不触发 layout,那肯定性能好!
代码:
<!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>Document</title>
<style>
#container {
width: 600px;
height: 20px;
display: inline-block;
border: 1px solid;
overflow: hidden;
}
#progress {
display: inline-block;
background: pink;
height: 100%;
transition: transform 0.5s linear;
width: 100%;
transform: translateX(-100%);
}
</style>
</head>
<body>
<div id="container">
<div id='progress'></div>
</div>
<div>
<button id='btn'>按钮</button>
</div>
<script>
const btn = document.getElementById('btn')
const progress = document.getElementById('progress')
btn.onclick = () => {
let process = 0
const timer = setInterval(() => {
if (process === 100) return clearInterval(timer)
progress.style.transform = `translateX(-${100 - ++process}%)`
}, 50);
}
</script>
</body>
</html>
性能:
我们可以看到没有触发 layout,也就没有触发多余的 paint,自然性能就好多了!
第三层
这个时候我们可能想到了 will-change 这个属性,简单来说这个属性就是告诉浏览器即将发生哪些变化,浏览器就可以进行优化,从而使得页面更快速更灵敏。
注意:
- 这个属性千万不可以滥用,用不好还可能导致性能降低。
- will-change 好的使用是在属性要变化之前去添加,但是浏览器通常会为优化设置提供至少 200 毫秒的时间,所以我们要给浏览器留下时间去优化。
参考文档
第四层
我们设置了 'transition: transform'(过渡动画) 或者 'will-change: transform'(优化) 的元素会被提升到合成层去渲染,如下图:
代开控制面板,然后ctrl + shift + p,搜索 layers 打开该面板
层爆炸
如下图,我们进度条开始运动的时候,会被提升到合成层进行渲染,但是下面很多数据标签的层叠位置都比进度条高,浏览器为了保证正确的层叠顺序,就会提升比进度条层叠位置高的元素,又因为我们 li 标签设置了 overflow: hidden,导致浏览器无法对额外提升的元素优化(层压缩),所以这个时候就会导致层爆炸的出现!
层爆炸效果
优化效果
我们给进度条设置高的 z-index: 999,这个时候进度条的层级就比所有的元素高,所以浏览器也不用隐式提升其他的元素,也就不会导致层爆炸的出现
代码:
<!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>Document</title>
<style>
#container {
width: 600px;
height: 20px;
display: inline-block;
border: 1px solid;
overflow: hidden;
}
#progress {
position: relative;
display: inline-block;
background: pink;
height: 100%;
transition: transform 0.5s linear;
width: 100%;
transform: translateX(-100%);
}
.list-item {
position: relative;
/* overflow 导致无法层压缩 */
overflow: hidden;
}
.list-item-text {
position: absolute;
left: 0;
top: 0;
}
</style>
</head>
<body>
<div id="container">
<div id='progress'></div>
</div>
<div>
<button id='btn'>按钮</button>
</div>
<hr />
<!-- 隐式提升的元素 -->
<ul id='listContainer'></ul>
<script>
const btn = document.getElementById('btn')
const progress = document.getElementById('progress')
btn.onclick = () => {
let process = 0
const timer = setInterval(() => {
if (process === 100) return clearInterval(timer)
progress.style.transform = `translateX(-${100 - ++process}%)`
}, 50);
}
const createData = () => {
const listContainer = document.getElementById('listContainer')
let str = ''
for (let i = 0; i < 1000; i++) {
str += `<li class='list-item'>
<div class='list-item-text'>${i}++++++++++++++</div>
</li>`
}
listContainer.innerHTML = str
}
createData()
</script>
</body>
</html>
第五层
假设我们开发了个进度条组件,我们也无法保证其他使用者是否熟知层爆炸的知识,这个时候我们调整了进度条的 z-index,又可能会导致其他样式的错乱,所以这个的取舍得慎重!
最后
一个简单的进度条涉及了浏览器渲染的很多知识,重排,重绘,层提升,层爆炸,层压缩,坑点还是很多的。
之前我看其他开源组件库用的 width 实现进度条,我觉得他在第一层,慢慢学习前端知识之后我才猛然发现,他们在第五层!