硬件加速

阳呀呀

前言

谈起浏览器的硬件加速,想必大家都知道的一个技巧就是在用CSS3做动画时,给元素添加transform: translateZ(0)或者transform: translate3d(0, 0, 0)就会开启GPU的硬件加速,将本来应该是浏览器处理的动画效果转交给GPU处理,从而使得动画看起来更加顺畅,在移动端体验更好。本文将进一步探索其中的奥秘,例如哪些条件可以触发GPU硬件加速?硬件加速背后的工作原理是什么?是不是开启GPU硬件加速的动画应该越多越好?
首先让我们来看一个动画效果,通过CSS3的animation属性来实现让一个小球从坐到右移动200px的距离。现在有两种实现方式:

第一种方法通过改变该元素的top属性来实现:

.ball1{
  width:100px;
  height:100px;
  border-radius: 100px;
  background:red;
}
.ball1{
  animation:mymove 3s infinite linear;
  -moz-animation:mymove 3s infinite linear;
  -webkit-animation:mymove 3s infinite linear;
  -o-animation:mymove 3s infinite linear;
}

@keyframes mymove{
   from {top:0px;}
   to {top:200px;}
}

第二种方法通过translate来实现:

.ball1{
   animation:translateMove 3s infinite linear;
   -moz-animation:translateMove 3s infinite linear;
   -webkit-animation:translateMove 3s infinite linear;
   -o-animation:translateMove 3s infinite linear;
}
@keyframes translateMove {
   from {transform: translate3d(0,0,0);}
   to {transform: translate3d(0,200px,0)}
}

两种方式都能达到同样的效果,但浏览器在内部渲染的过程却大不相同。哪种实现方式更优?在回答这个问题之前,我们先来了解一下浏览器的渲染过程。

浏览器渲染过程

已知JS是单线程工作的,但是浏览器可以开启多个线程,渲染一个网页需要两个重要的线程来共同完成:Main Thread 主线程 Compositor Thread 合成器线程

主线程做的工作:

运行JS
计算 HTML 元素的 CSS 样式
布局页面
将元素绘制到一个或多个位图中
把这些位图交给 Compositor Thread 来处理

合成器线程做的工作:

通过 GPU 将位图绘制到屏幕上
通知主线程去更新页面中可见或即将可见的部分的位图
计算出页面中那些部分是可见的
计算出在滚动页面时候,页面中哪些部分是即将可见的
滚动页面时将相应位置的元素移动到可视区

在了解了这两个线程各自负责的部分之后,我们再来看浏览器的渲染过程。大体流程如下:

clipboard.png


当我们通过某种方法引起浏览器的reflow时,需要重新经历style和layout阶段,导致浏览器重新计算页面中每个dom元素的尺寸及重新布局,伴随着重新进行repaint,这个过程是非常耗时的。为了把代价降到最低,当然最好只留下composite这一个步骤最好。假设当我们改变一个容器的样式时,影响的只是它自己,并且还无需重绘,直接通过在GPU中改变纹理的属性来改变样式,岂不是更好?

clipboard.png

如何能使元素达到这个效果?就是让元素拥有自己的层(layer)。有了层的概念,让我们从层的概念再来看浏览器的渲染过程:

  1. 获取 DOM 并将其分割为多个层(RenderLayer)
  2. 将每个层栅格化,并独立的绘制进位图中
  3. 将这些位图作为纹理上传至 GPU
  4. 复合多个层来生成最终的屏幕图像(终极layer)

可以将这个过程理解为设计师的Photoshop文件。在ps源文件里,一个图像是由若干个图层相互叠加而展示出来的。分成多个图层的好处就是每个图层相对独立,修改方便,对单个图层的修改不会影响到页面上的其他图层。因此层(layer)存在的意义在于:用最小的代价来改变某个页面元素。可以将某个css动画或某个js交互效果抽离到一个单独的渲染层,来达到加速渲染的目的。

那么如何才能创建一个层呢?

  • 3d transform属性
  • backface-visibility为hidden的元素
  • 使用加速视频解码的 <video> 元素
  • 拥有 3D (WebGL) 上下文或加速的 2D 上下文的 <canvas> 元素
  • 混合插件(如 Flash)
  • 对 opacity、transform、fliter、backdrop-filter 应用了 animation 或者 transition(需要是 active 的 animation 或者 transition,当 animation 或者 transition 效果未开始或结束后,合成层也会失效)
  • will-change 设置为 opacity、transform、top、left、bottom、right(其中 top、left 等需要设置明确的定位属性,如 relative 等)
  • 元素有一个包含复合层的后代节点(换句话说,就是一个元素拥有一个子元素,该子元素在自己的层里)
  • 元素有一个 z-index 较低且包含一个复合层的兄弟元素(换句话说就是该元素在复合层上面渲染)

在webkit内核的浏览器中,如果有上述情况,就会创建一个独立的层(layer)。我们可以借助chrome浏览器开发者工具中的layers和rendering结合来查看页面中有哪些独立的层。

性能分析

现在回到文章开始的那个动画效果,让我们通过chrome的performance工具来看看具体的执行过程。(注:本次性能分析是将CPU性能将至原来的六分之一 模拟移动端的效果进行分析的。具体操作可将performance工具中的CPU选择6*slowdown)
通过改变top属性:

clipboard.png

clipboard.png

通过改变transform属性:

clipboard.png

clipboard.png

从上图可以看出,运动的元素如何没有独立的层,每一帧的绘制都需要经过不停的rendering和painting过程。

但硬件加速是把双刃剑,过渡的使用硬件加速会适得其反。其影响表现在:

  1. 内存。创建一个新的渲染层,需要消耗额外的内存和管理资源,如果渲染层的个数过多,很容易引起内存问题,这一点在移动端浏览器上尤为明显,可以引起电池耗电量的上升,降低电池的寿命。所以,一定要牢记不要让页面的每个元素都使用硬件加速,当且仅当需要的时候才为元素创建渲染层。
  2. 使用GPU渲染会影响字体的抗锯齿效果。文本在动画期间有可能会显示的模糊。

参考文档

  1. 无线性能优化:composite
  2. CSS动画及硬件加速
  3. Javascript高性能动画与页面渲染
  4. chrome渲染优化-层模型
阅读 2.9k
2.1k 声望
2.7k 粉丝
0 条评论
你知道吗?

2.1k 声望
2.7k 粉丝
文章目录
宣传栏