上个星期去yy语音面试,就有一个这样问题: transform与position:absolute 有什么区别? 我回家后查资料发现这道题目其实不简单啊,涉及到重排、重绘、硬件加速等网页优化的知识。
首先看一个用top、left实现的动画效果
<style>html,body {width: 100%;height: 100%;}.ball-running {animation: run-around 4s infinite;width: 100px;height: 100px;background-color: red;position: absolute;}@keyframes run-around {0%: {top: 0;left: 0;}25% {top: 0;left: 200px;}50% {top: 200px;left: 200px;}75% {top: 200px;left: 0;}}</style>
<body><div class="ball-running"></div></body>
在运行的时候,会隐约觉得动画的运行并不流畅,动画有些停顿的感觉。这时因为top和left的改变会触发浏览器的 reflow和 repaint 。然后整个动画过程都在不断触发浏览器的重新渲染,这个过程是很影响性能的。
下面是chrome 浏览器performance中的监测到的数据。在 chrome的rendering面板中可以看到没改变一次位置浏览器就要渲染一个。
然后我用transform 重写一下这个动画效果
<style>html,body {width: 100%;height: 100%;}.ball-running {animation: run-around2 4s infinite;width: 100px;height: 100px;background-color: red;position: absolute;}@keyframes run-around2 {0%: {transform: translate(0, 0);}25% {transform: translate(200px, 0);}50% {transform: translate(200px, 200px);}75% {transform: translate(0, 200px);}}
</style><body><div class="ball-running"></div></body>
这时候会发现整个动画效果流畅了很多,在动画移动的过程中也没有发生repaint和reflow。
下面是chrome 浏览器performance中的监测到的数据。以及rendering面板。
那么,为什么 transform
没有触发 repaint 呢?原因就是,transform
动画由GPU控制,支持硬件加速。
看完以上两个例子,那么我们就要入正题了。
主线程和合成线程
现代浏览器通常由两个重要的线程组成(主线程 和 合成线程)。这两个线程一起工作完成绘制页面的任务:
主线程需要做的任务如下:
- 运行Javascript
- 计算HTML元素的CSS样式
- layout (relayout)
- 将页面元素绘制成一张或多张位图
- 将位图发送给合成线程
合成线程主要任务是:
- 利用GPU将位图绘制到屏幕上
- 让主线程将可见的或即将可见的位图发给自己
- 计算哪部分页面是可见的
- 计算哪部分页面是即将可见的(当你的滚动页面的时候)
- 在你滚动时移动部分页面
在很长的一段时间内,主线程都在忙于运行Javascript和绘制元素。
例如,当用户滚动一个页面时,合成线程会让主线程提供最新的可见部分的页面位图。然而主线程不能及时的响应。这时合成线程不会等待,它会绘制已有的页面位图。对于没有的部分则绘制白屏。
GPU擅长的领域:
- 绘制东西到屏幕上
- 一次次绘制同一张位图到屏幕上
- 绘制同一张位图到不同的位置、旋转角度和缩放比例
我们来看一个例子:将一个页面元素的高度从100px渐变到200px,方法可以是:1、height 2、transform: scale(1.0)。两个方法性能是不一样的,现在我们来从主线程和合成线程的角度来看看
传统的方法:改变 height
div {height: 100px;transition: height 1s linear;
}div:hover {height: 200px;
}
下图是一张主线程和合成线程的互相交互的时间线图。
在transition动画的每一帧中,都修改元素的高度,这可能会导致子元素的大小也会变化,然后浏览器不得不进行relayout。在relayout之后主线程还需要重新生成元素的位图,加载位图到GPU内存中
css3的方法:transform
直接改变元素的高度是很耗时的,但我们可以使用 transform: scale(); 来缩放元素,实现这个改变高度的目的。
div {transform: scale(0.5);transition: transform 1s linear;
}div:hover {transform: scale(1.0);
}
CSS transform属性并不会触发当前元素或附近元素的relayout。浏览器将当前元素视为一个整体,它会缩放、旋转、移动这一整个元素。
浏览器只需要在动画开始之时生成位图,然后将位图发送给GPU。之后浏览器不需要做额外的relayout和repaint,甚至不需要发送位图给GPU。浏览器只需要充分发挥GPU的长处:绘制同一张位图到不同的位置、旋转角度和缩放比例。
可以使用GPU加速的CSS3属性
- CSS transform
- CSS opacity
- CSS filter
感想
既然transform这个属性那么强大,我们就可以用他来优化我们平时的一下操作。例如:拖拽,在mouseover阶段就用transform,在mousedown阶段在再用position绝对定位,这样是不是就可以减少repaint和reflow的操作呢。还有就是动画。等等。
参考资料
CSS动画之硬件加速
CSS animation和transition的性能探究