从浏览器渲染原理,说一说如何实现高效的动画

简介: 从浏览器渲染原理,说一说如何实现高效的动画

640.jpg


写在前面


在平时的工作中,页面的动画效果是很常见的需求。那么,怎么样实现一个高效的动画呢?


注,本文谈到的浏览器,均为基于Chromium的现代浏览器。


页面渲染原理

012728cd90021d1ae0a942ee380bdee4.jpg


一个页面展示在用户面前,简单来说,会经历以上5个步骤。我们可以把上面这个图称为像素管道


  • Javascript:  执行js逻辑,修改DOM,修改CSS等。
  • Style:计算样式。
  • Layout:在知道对一个元素应用哪些规则之后,浏览器即可开始计算它要占据的空间大小及其在屏幕的位置。这个步骤,就是我们常说的重排。
  • Paint:  绘制是填充像素的过程。它涉及绘出文本、颜色、图像、边框和阴影,基本上包括元素的每个可视部分。绘制一般是在多个表面(通常称为层)上完成的。这个步骤,就是我们常说的重绘。
  • Composite:渲染层合并,由上一步可知,对页面中 DOM 元素的绘制是在多个层上进行的。在每个层上完成绘制过程之后,浏览器会将所有层按照合理的顺序合并成一个图层,然后显示在屏幕上。


在浏览器中,页面的渲染由浏览器的渲染进程完成,而渲染进程中,包含了主线程,worker线程,Compositer线程,Raster线程。上述像素管道的5个过程中,前4个过程,都由主线程完成,最后一个步骤,主要由Raster线程、Compositer线程完成

JavaScript Style Layout


像素管道中的前三个步骤,大家都很熟悉了。JavaScript、Style两个步骤,一图以蔽之:

38e00b092ba32ca4286357d6830bcb9c.jpg

接着是Layout,浏览器遍历render tree的每一个节点,计算其确切大小和位置。最终形成一个Layout Tree。

3027231ea25e55050549106ca1d45ee5.jpg

Paint


在Paint之前,浏览器会根据Layout Tree,确定需要绘制的对象的层级,我们可以把这个层级叫做渲染层,最终生成Layer Tree。这个阶段被称作:Update Layer Tree

d7890d69e1157979dfe5890a8f5864fd.jpg

在Paint这个阶段,浏览器会根据Layer Tree,生成Paint Records。

Paint Records就是描述先画什么,再画什么的记录,跟我们写canvas代码时很像。Paint Records是根据渲染层划分的的。来看一个Paint Records的实例:

e46c0df9c42415ed58c7016770071574.jpg

尽管生成了Pain Records,真正的绘制并不在Paint这个阶段完成的,而是在Composite阶段由Raster线程完成的。


Composite


经过之前的几个步骤,浏览器主线程已经将页面的内容分成了若干渲染层。为了提升性能,某些特定的渲染层,会被提升为合成层。我们可以通过下面两个css属性,将某个元素强制提升为合成层


will-change: transform;
// 或者transform: translateZ(0);


注:提升为合成层的条件比较复杂,这里就不一一展开了。可以参考这篇文章:

http://taobaofed.org/blog/2016/04/25/performance-composite/


主线程在处理完所有的所有的数据后,会把数据提交到Compositer线程。Compositer线程会利用Raster线程来做光栅化处理,并将处理好的内容存入内存中。随着Compositer线程完成渲染层合成操作,扔给GPU,页面最终被渲染到屏幕上。

371944f42166bf42c51804241e42a7f1.jpg

可以通过Chrome开发者工具中的Layer来查看合成层:

8dc1fdd1929b65a12b27f8e12f2404fa.jpg


其他运行方式的像素管道


上文中的像素管道共有5个步骤。不一定每帧都总是会经过管道每个部分的处理。实际上,不管是使用 JavaScript、CSS 还是网络动画,在实现视觉变化时,管道针对指定帧的运行还有其他两种方式:

6621d768ac154fff9c3a47adb127efb9.jpg

3d16e5a688314bf5b49d2f54b077d5df.jpg


第一种就是我们所说的页面没有进行重排,只进行了重绘;第二种就是页面既没有进行重排,也没有进行重绘。


最后这种运行方式的开销最小,适合于页面上的动画效果。


实现动画效果


不考虑canvas等,有三种常见的方式来实现页面上的动效,

  1. 完全不用css3相关属性,仅使用setTimeout,  setInterval,  requestAnimationFrame,通过js修改DOM的样式来实现动画。
  2. 使用纯css3来实现动画。
  3. js与css3相结合来实现动画。

一般情况下,使用第一种方式的时候,虽然有的动画效果在进行过程中不会触发像素管道中的Layout,但是Paint往往是避免不了的。而使用css3来实现动画时,我们可以跳过Layout和Paint步骤。


下面,来看看三种实现方式下,浏览器的处理过程。


完全由JS驱动的动画


代码如下:


<html>  <head>    <style type="text/css">      #test2 {        margin-top: 100px;        width: 100px;        height: 100px;        position: relative;        background-color: black;      }</style>  </head>  <body>    <p>       这是一段无用的文字,这是一段无用的文字,这是一段无用的文字,这是一段无用的文字,这是一段无用的文字,这是一段无用的文字    </p>    <div id="test2"></div>    <script type="text/javascript">      window.onload = function() {        const el = document.getElementById('test2');        let left = 0;        const startTimeStamp = Date.now();        const fn = function() {          left += 2;          if(Date.now() - startTimeStamp > 2000) {            return;          }          el.style.left = left + 'px';          return window.requestAnimationFrame(fn);        }        window.requestAnimationFrame(fn)      }</script>  </body></html>


选取动画过程中的一帧,浏览器的处理过程如下:

5a219274ed7c877622b8e2618259ce17.jpg

可以看到,在这里帧里,浏览器走完了完整的像素管道:JavaScript ->Style->Layout->Paint->Composite。


纯CSS3实现动画


我们用纯css来实现动画:


<html>  <head>    <style type="text/css">      #test2 {        margin-top: 100px;        width: 100px;        height: 100px;        position: relative;        background-color: black;        animation: move 2s;        animation-fill-mode: forwards;      }      @keyframes move {        0% {          transform: translate(0);        }        100% {          transform: translate(200px);        }      }</style>  </head>  <body>    <p>     这是一段无用的文字,这是一段无用的文字,这是一段无用的文字,这是一段无用的文字,这是一段无用的文字,这是一段无用的文字    </p>    <div id="test2"></div>  </body></html>


我们来看看动画进行过程中:

b1a9ec58b3a9971cefa9ff0495351e02.jpg


可以看到,主线程里没有任务在执行,而Compositer线程、Raster线程以及GPU在工作。在动画结束后的那一帧里,触发了Paint。


JS和CSS3结合


代码如下:


<html>  <head>    <style type="text/css">      #test2 {        margin-top: 100px;        width: 100px;        height: 100px;        position: relative;        background-color: black;      }</style>  </head>  <body>    <p>       这是一段无用的文字,这是一段无用的文字,这是一段无用的文字,这是一段无用的文字,这是一段无用的文字,这是一段无用的文字    </p>    <div id="test2"></div>    <script type="text/javascript">      window.onload = function() {        const el = document.getElementById('test2');        let left = 0;        const startTimeStamp = Date.now();        const fn = function() {          left += 2;          if(Date.now() - startTimeStamp > 2000) {              return;          }          el.style.transform = `translate(${left}px)`;          return window.requestAnimationFrame(fn);        }        window.requestAnimationFrame(fn);      }</script>  </body></html>


动画运行时,浏览器的处理过程如下图所示,并没有触发Layout和paint。

5a52b867ec37822b825a2e2cb671c430.jpg

另外,在动画结束的那一帧里,也没有触发Layout和Paint。


小结


从上面的几个实例可以看到,在仅使用css动画时,动画过程完全交由Composite线程处理,释放了主线程。事实上,在执行纯css3动画时,浏览器会将响应的元素提升到一个单独的合成层,不会影响到页面上的其他元素。

使用js操作css3属性,也可以跳过Layout和Paint。

当然,并不是所有的css属性都可以跳过Layout和Paint仅触发Composite,常见的属性是:transformopacity。具体属性可以到下面的网址查看:

https://csstriggers.com/


这里还有几点补充的地方:


  1. 动画开始时,都会触发一次paint。
  2. 对于纯css3操作transform和opacity的动画,在动画开始时,浏览器会自动将动画元素提升为合成层,但是在动画结束后,合成层会失效。在动画结束后(合成层失效)的那一帧,浏览器是会触发Paint的。如果我们强制将动画元素提升为合成层,动画结束后的那一帧,就不会触发Paint了。
  3. 对于js操作css3的transform和opacity的动画,在动画过程中,浏览器不会自动将动画元素提升为合成层,但是也不会触发Paint。在动画结束后的那一帧,不管我们是否强制将动画元素提升为合成层,当页面动画元素嵌套复杂时,可能会触发Paint。


总结


想要实现高性能的动画,尽量使用css动画或者使用js操作css3属性的方式,同时,要注意动画用到的css3属性。动画的目标就是跳过浏览器的Layout和Paint,仅触发Composite。


对于特定的动画元素,我们可以适当将其提升到合成层,这样该元素不会影响到页面其他地方。当然,合成层的使用要适当,因为合成层会带来内存压力。


写在后面


本文从浏览器渲染原理入手,谈到了如何实现一个高效的动画。在写作本文的过程中,学习、巩固了很多的知识。还有一些更深入的点值得去继续研究。符合预期。

相关文章
|
2月前
|
存储 缓存 前端开发
浏览器缓存工作原理是什么?
浏览器缓存工作原理是什么?
|
2月前
|
Web App开发 JavaScript 前端开发
浏览器与Node.js事件循环:异同点及工作原理
浏览器与Node.js事件循环:异同点及工作原理
|
2月前
|
缓存 JavaScript 前端开发
浏览器渲染:理解页面加载的幕后工作
浏览器渲染:理解页面加载的幕后工作
|
27天前
|
JavaScript 前端开发 网络协议
浏览器的工作原理
主要分为导航、获取数据、HTML解析、css解析、执行javaScript、渲染树几个步骤。
19 1
|
25天前
|
移动开发 前端开发 JavaScript
浏览器端图表渲染技术SVG, VML HTML Canvas
浏览器端图表渲染技术SVG, VML HTML Canvas
14 0
|
2月前
|
前端开发 JavaScript 数据可视化
探索浏览器的内心世界:渲染机制的奥秘
探索浏览器的内心世界:渲染机制的奥秘
探索浏览器的内心世界:渲染机制的奥秘
|
2月前
|
存储 安全 前端开发
浏览器跨窗口通信:原理与实践
浏览器跨窗口通信:原理与实践
115 0
|
2月前
|
消息中间件 JavaScript 前端开发
前端秘法进阶篇----这还是我们熟悉的浏览器吗?(浏览器的渲染原理)
前端秘法进阶篇----这还是我们熟悉的浏览器吗?(浏览器的渲染原理)
|
2月前
|
消息中间件 前端开发 Java
【面试题】前端必修-浏览器的渲染原理
【面试题】前端必修-浏览器的渲染原理
|
7月前
|
Web App开发 JavaScript 前端开发
从浏览器原理出发聊聊Chrome插件
本文从浏览器架构演进、插件运行机制、插件基本介绍和一些常见的插件实现思路几个方向聊聊Chrome插件。
907 0