【Web动画】CSS3 3D 行星运转 && 浏览器渲染原理

原文: 【Web动画】CSS3 3D 行星运转 && 浏览器渲染原理

承接上一篇:【CSS3进阶】酷炫的3D旋转透视 。javascript

最近入坑 Web 动画,因此把本身的学习过程记录一下分享给你们。php

CSS3 3D 行星运转 demo 页面请戳:Demo。(建议使用Chrome打开)css

本文完整的代码,以及更多的 CSS3 效果,在我 Github 上能够看到,也但愿你们能够点个 star。html

嗯,可能有些人打不开 demo 或者页面乱了,贴几张效果图:(图片有点大,耐心等待一会)html5

CSS3 3D 行星运转效果图

CSS3 3D 行星运转动画,太阳系动画

随机再截屏了一张:java

CSS3 3D 行星运转动画,太阳系动画

强烈建议你点进 Demo 页感觉一下 CSS3 3D 的魅力,图片能展示的东西毕竟有限。css3

而后,这个 CSS3 3D 行星运转动画的制做过程再也不详细赘述,本篇的重点放在 Web 动画介绍及性能优化方面。详细的 CSS3 3D 能够回看上一篇博客:【CSS3进阶】酷炫的3D旋转透视。简单的思路:git

1. 利用上一篇所制做的 3D 照片墙为原型,改造而来;github

2. 每个球体的制做,想了许多方法,最终使用了这种折中的方式,每个球体自己也是一个 CSS3 3D 图形。而后在制做过程当中使用 Sass 编写 CSS 能够减小不少繁琐的编写 CSS 动画的过程;web

3. Demo 当中有使用 Javascript 写了一个鼠标跟随的监听事件,去掉这个事件,整个行星运动动画自己是纯 CSS 实现的。

 

下面将进入本文的重点,从性能优化的角度讲讲浏览器渲染展现原理,浏览器的重绘与重排,动画的性能检测优化等:

 

   浏览器渲染展现原理 及 对web动画的影响

小标题起得有点大,咱们知道,不一样浏览器的内核(渲染引擎,Rendering Engine)是不同的,例如如今最主流的 chrome 浏览器的内核是 Blink 内核(在Chrome(28及日后版本)、Opera(15及日后版本)和Yandex浏览器中使用),火狐是 Gecko,IE 是 Trident ,浏览器内核负责对网页语法的解释并渲染(显示)网页,不一样浏览器内核的工做原理并不彻底一致。

因此其实下面将主要讨论的是 chrome 浏览器下的渲染原理。由于 chrome 内核渲染可查证的资料较多,对于其余内核的浏览器不敢妄下定论,因此下面展开的讨论默认是针对 chrome 浏览器的。

首先,我要抛出一点结论:

使用 transform3d api 代替 transform api,强制开始 GPU 加速

这里谈到了 GPU 加速,为何 GPU 可以加速 3D 变换?这一切又必需要从浏览器底层的渲染讲起,浏览器渲染展现网页的过程,老生常谈,面试必问,大体分为:

1. 解析HTML(HTML Parser)

2. 构建DOM树(DOM Tree)

3. 渲染树构建(Render Tree)

4. 绘制渲染树(Painting)

简单解释一下,经过请求获得的 HTML 通过解析(HTML parser)生成 DOM Tree。而在 CSS 解析完毕后,须要将解析的结果与 DOM Tree 的内容一块儿进行分析创建一棵 Render Tree,最终用来进行绘图(Painting)。

找到了一张很经典的图:

浏览器渲染页面过程

这个渲染过程做为一个基础知识,继续往下深刻。

当页面加载并解析完毕后,它在浏览器内表明了一个你们十分熟悉的结构:DOM(Document Object Model,文档对象模型)。在浏览器渲染一个页面时,它使用了许多没有暴露给开发者的中间表现形式,其中最重要的结构即是(layer)。

这个层就是本文重点要讨论的内容:

而在 Chrome 中,存在有不一样类型的层: RenderLayer(负责 DOM 子树),GraphicsLayer(负责 RenderLayer 的子树)。接下来咱们所讨论的将是 GraphicsLayer 层。

GraphicsLayer 层是做为纹理(texture)上传给 GPU 的。

这里这个纹理很重要,那么,

什么是纹理(texture)

这里的纹理指的是 GPU 的一个术语:能够把它想象成一个从主存储器(例如 RAM)移动到图像存储器(例如 GPU 中的 VRAM)的位图图像(bitmap image)。一旦它被移动到 GPU 中,你能够将它匹配成一个网格几何体(mesh geometry),在 Chrome 中使用纹理来从 GPU 上得到大块的页面内容。经过将纹理应用到一个很是简单的矩形网格就能很容易匹配不一样的位置(position)和变形(transformation),这也就是 3D CSS 的工做原理。

提及来很难懂,直接看例子,在 chrome 中,咱们是能够看到上文所述的 GraphicsLayer -- 层的概念。在开发者工具中,咱们进行以下选择调出 show layer borders 选项:

在一个极简单的页面,咱们能够看到以下所示,这个页面只有一个层。蓝色网格表示瓦片(tile),你能够把它们看成是层的单元(并非层),Chrome 能够将它们做为一个大层的部分上传给 GPU:

元素自身层的建立

由于上面的页面十分简单,因此并无产生层,可是在很复杂的页面中,譬如咱们给元素设置一个 3D CSS 属性来变换它,咱们就能看到当元素拥有本身的层时是什么样子。

注意橘黄色的边框,它画出了该视图中层的轮廓:

 

什么时候触发建立层 ?

上面示意图中黄色边框框住的层,就是 GraphicsLayer ,它对于咱们的 Web 动画而言很是重要,一般,Chrome 会将一个层的内容在做为纹理上传到 GPU 前先绘制(paint)进一个位图中。若是内容不会改变,那么就没有必要重绘(repaint)层。

这样作的意义在于:花在重绘上的时间能够用来作别的事情,例如运行 JavaScript,若是绘制的时间很长,还会形成动画的故障与延迟。

那么一个元素何时会触发建立一个层?从目前来讲,知足如下任意状况便会建立层:

  • 3D 或透视变换(perspective、transform) CSS 属性
  • 使用加速视频解码的 <video> 元素
  • 拥有 3D (WebGL) 上下文或加速的 2D 上下文的 <canvas> 元素
  • 混合插件(如 Flash)
  • 对本身的 opacity 作 CSS 动画或使用一个动画变换的元素
  • 拥有加速 CSS 过滤器的元素
  • 元素有一个包含复合层的后代节点(换句话说,就是一个元素拥有一个子元素,该子元素在本身的层里)
  • 元素有一个 z-index 较低且包含一个复合层的兄弟元素(换句话说就是该元素在复合层上面渲染)

 

层的重绘

对于静态 Web 页面而言,层在第一次被绘制出来以后将不会被改变,但对于 Web 动画,页面的 DOM 元素是在不断变换的,若是层的内容在变换过程当中发生了改变,那么层将会被重绘(repaint)。

强大的 chrome 开发者工具提供了工具让咱们能够查看到动画页面运行中,哪些内容被从新绘制了:

在旧版的 chrome 中,是有 show paint rects 这一个选项的,能够查看页面有哪些层被重绘了,并以红色边框标识出来。

可是新版的 chrome 貌似把这个选项移除了,如今的选项是 enable paint flashing ,其做用也是标识出网站动态变换的地方,而且以绿色边框标识出来。

看上面的示意图,能够看到页面中有几处绿色的框,表示发生了重绘。注意 Chrome 并不会始终重绘整个层,它会尝试智能的去重绘 DOM 中失效的部分。

按照道理,页面发生这么多动画,重绘应该很频繁才对,可是上图个人行星动画中我只看到了寥寥绿色重绘框,个人我的理解是,一是 GPU 优化,二是若是整个动画页面只有一个层,那么运用了 transform 进行变换,页面必然须要重绘,可是采用分层(GraphicsLayer )技术,也就是上面说符合状况的元素各自建立层,那么一个元素所建立的层运用 transform 变换,譬如 rotate 旋转,这个时候该层的旋转变换并无影响到其余层,那么该层不必定须要被重绘。(我的之见,还请提出指正)。

了解层的重绘对 Web 动画的性能优化相当重要。

是什么缘由致使失效(invalidation)进而强制重绘的呢?这个问题很难详尽回答,由于存在大量致使边界失效的状况。最多见的状况就是经过操做 CSS 样式来修改 DOM 或致使重排。

查找引起重绘和重排根源的最好办法就是使用开发者工具的时间轴和 enable paint flashing 工具,而后试着找出刚好在重绘/重排前修改了 DOM 的地方。

总结

那么浏览器是如何从 DOM 元素到最终动画的展现呢?

  • 浏览器解析 HTML 获取 DOM 后分割为多个图层(GraphicsLayer)
  • 对每一个图层的节点计算样式结果(Recalculate style--样式重计算)
  • 为每一个节点生成图形和位置(Layout--回流和重布局)
  • 将每一个节点绘制填充到图层位图中(Paint Setup和Paint--重绘)
  • 图层做为纹理(texture)上传至 GPU
  • 符合多个图层到页面上生成最终屏幕图像(Composite Layers--图层重组)

Web 动画很大一部分开销在于层的重绘,以层为基础的复合模型对渲染性能有着深远的影响。当不须要绘制时,复合操做的开销能够忽略不计,所以在试着调试渲染性能问题时,首要目标就是要避免层的重绘。那么这就给动画的性能优化提供了方向,减小元素的重绘与回流。

 

   回流(reflow)与重绘(repaint)

这里首先要分清两个概念,重绘与回流。

回流(reflow)

当渲染树(render Tree)中的一部分(或所有)由于元素的规模尺寸,布局,隐藏等改变而须要从新构建。这就称为回流(reflow),也就是从新布局(relayout)。

每一个页面至少须要一次回流,就是在页面第一次加载的时候。在回流的时候,浏览器会使渲染树中受到影响的部分失效,并从新构造这部分渲染树,完成回流后,浏览器会从新绘制受影响的部分到屏幕中,该过程成为重绘。

重绘(repaint)

当render tree中的一些元素须要更新属性,而这些属性只是影响元素的外观,风格,而不会影响布局的,好比 background-color 。则就叫称为重绘。

值得注意的是,回流必将引发重绘,而重绘不必定会引发回流。

明显,回流的代价更大,简单而言,当操做元素会使元素修改它的大小或位置,那么就会发生回流。

回流什么时候触发:

  • 调整窗口大小(Resizing the window)
  • 改变字体(Changing the font)
  • 增长或者移除样式表(Adding or removing a stylesheet)
  • 内容变化,好比用户在input框中输入文字(Content changes, such as a user typing text in
  • an input box)
  • 激活 CSS 伪类,好比 :hover (IE 中为兄弟结点伪类的激活)(Activation of CSS pseudo classes such as :hover (in IE the activation of the pseudo class of a sibling))
  • 操做 class 属性(Manipulating the class attribute)
  • 脚本操做 DOM(A script manipulating the DOM)
  • 计算 offsetWidth 和 offsetHeight 属性(Calculating offsetWidth and offsetHeight)
  • 设置 style 属性的值 (Setting a property of the style attribute)

因此对于页面而言,咱们的宗旨就是尽可能减小页面的回流重绘,简单的一个栗子:

// 下面这种方式将会致使回流reflow两次
var newWidth = aDiv.offsetWidth + 10; // Read
aDiv.style.width = newWidth + 'px'; // Write
var newHeight = aDiv.offsetHeight + 10; // Read
aDiv.style.height = newHeight + 'px'; // Write

// 下面这种方式更好,只会回流reflow一次
var newWidth = aDiv.offsetWidth + 10; // Read
var newHeight = aDiv.offsetHeight + 10; // Read
aDiv.style.width = newWidth + 'px'; // Write
aDiv.style.height = newHeight + 'px'; // Write

上面四句,由于涉及了 offsetHeight 操做,浏览器强制 reflow 了两次,而下面四句合并了 offset 操做,因此减小了一次页面的回流。 

减小回流、重绘其实就是须要减小对渲染树的操做(合并屡次多DOM和样式的修改),并减小对一些style信息的请求,尽可能利用好浏览器的优化策略。

flush队列

其实浏览器自身是有优化策略的,若是每句 Javascript 都去操做 DOM 使之进行回流重绘的话,浏览器可能就会受不了。因此不少浏览器都会优化这些操做,浏览器会维护 1 个队列,把全部会引发回流、重绘的操做放入这个队列,等队列中的操做到了必定的数量或者到了必定的时间间隔,浏览器就会 flush 队列,进行一个批处理。这样就会让屡次的回流、重绘变成一次回流重绘。

可是也有例外,由于有的时候咱们须要精确获取某些样式信息,下面这些:

  • offsetTop, offsetLeft, offsetWidth, offsetHeight
  • scrollTop/Left/Width/Height
  • clientTop/Left/Width/Height
  • width,height
  • 请求了getComputedStyle(), 或者 IE的 currentStyle

这个时候,浏览器为了反馈最精确的信息,须要当即回流重绘一次,确保给到咱们的信息是准确的,因此可能致使 flush 队列提早执行了。

display:none 与 visibility:hidden 的异同

二者均可以在页面上隐藏节点。不一样之处在于,

  • display:none 隐藏后的元素不占据任何空间。它的宽度、高度等各类属性值都将“丢失”
  • visibility:hidden 隐藏的元素空间依旧存在。它仍具备高度、宽度等属性值

从性能的角度而言,便是回流与重绘的方面,

  • display:none  会触发 reflow(回流)
  • visibility:hidden  只会触发 repaint(重绘),由于没有发现位置变化

他们二者在优化中 visibility:hidden 会显得更好,由于咱们不会由于它而去改变了文档中已经定义好的显示层次结构了。

对子元素的影响:

  • display:none 一旦父节点元素应用了 display:none,父节点及其子孙节点元素所有不可见,并且不管其子孙元素如何设置 display 值都没法显示;
  • visibility:hidden 一旦父节点元素应用了 visibility:hidden,则其子孙后代也都会所有不可见。不过存在隐藏“失效”的状况。当其子孙元素应用了 visibility:visible,那么这个子孙元素又会显现出来。

 

   动画的性能检测及优化

 

耗性能样式

比错误放置的动画更糟的事情是致使页面卡顿的动画。 这将让用户以为失望和不悦,而且可能但愿您根本没必要费心去设置动画!

不一样样式在消耗性能方面是不一样的,改变一些属性的开销比改变其余属性要多,所以更可能使动画卡顿。

例如,与改变元素的文本颜色相比,改变元素的 box-shadow 将须要开销大不少的绘图操做。 改变元素的 width 可能比改变其 transform 要多一些开销。如 box-shadow 属性,从渲染角度来说十分耗性能,缘由就是与其余样式相比,它们的绘制代码执行时间过长。这就是说,若是一个耗性能严重的样式常常须要重绘,那么你就会遇到性能问题。其次你要知道,没有不变的事情,在今天性能不好的样式,可能明天就被优化,而且浏览器之间也存在差别。

所以关键在于,你要借助开发工具来分辨出性能瓶颈所在,而后设法减小浏览器的工做量。

好在 chrome 浏览器提供了许多强大的功能,让咱们能够检测咱们的动画性能,除了上面提到的,咱们还能够经过勾选下面这个 show FPS meter 显示页面的 FPS 信息,以及 GPU 的使用率:

 

使用 will-change 提升页面滚动、动画等渲染性能

官方文档说,这是一个仍处于实验阶段的功能,因此在将来版本的浏览器中该功能的语法和行为可能随之改变。

使用方法示例:(具体每一个取值的意义,去翻翻文档)

will-change: auto
will-change: scroll-position
will-change: contents
will-change: transform        // Example of <custom-ident> 
will-change: opacity          // Example of <custom-ident>
will-change: left, top        // Example of two <animateable-feature>

will-change: unset
will-change: initial
will-change: inherit

// 示例
.example{
    will-change: transform;
}

will-change 为 web 开发者提供了一种告知浏览器该元素会有哪些变化的方法,这样浏览器能够在元素属性真正发生变化以前提早作好对应的优化准备工做。 这种优化能够将一部分复杂的计算工做提早准备好,使页面的反应更为快速灵敏。

值得注意的是,用好这个属性并非很容易:

  • 不要将 will-change 应用到太多元素上:浏览器已经尽力尝试去优化一切能够优化的东西了。有一些更强力的优化,若是与 will-change 结合在一块儿的话,有可能会消耗不少机器资源,若是过分使用的话,可能致使页面响应缓慢或者消耗很是多的资源。
  • 有节制地使用:一般,当元素恢复到初始状态时,浏览器会丢弃掉以前作的优化工做。可是若是直接在样式表中显式声明了 will-change 属性,则表示目标元素可能会常常变化,浏览器会将优化工做保存得比以前更久。因此最佳实践是当元素变化以前和以后经过脚原本切换 will-change 的值。
  • 不要过早应用 will-change 优化:若是你的页面在性能方面没什么问题,则不要添加 will-change 属性来榨取一丁点的速度。 will-change 的设计初衷是做为最后的优化手段,用来尝试解决现有的性能问题。它不该该被用来预防性能问题。过分使用 will-change 会致使大量的内存占用,并会致使更复杂的渲染过程,由于浏览器会试图准备可能存在的变化过程。这会致使更严重的性能问题。
  • 给它足够的工做时间:这个属性是用来让页面开发者告知浏览器哪些属性可能会变化的。而后浏览器能够选择在变化发生前提早去作一些优化工做。因此给浏览器一点时间去真正作这些优化工做是很是重要的。使用时须要尝试去找到一些方法提早必定时间获知元素可能发生的变化,而后为它加上 will-change 属性。

 

使用 transform3d api 代替 transform api,强制开始 GPU 加速

GPU 可以加速 Web 动画,这个上文已经反复提到了。

3D transform 会启用GPU加速,例如 translate3D, scaleZ 之类,固然咱们的页面可能并无 3D 变换,可是不表明咱们不能启用 GPU 加速,在非 3D 变换的页面也使用 3D transform 来操做,算是一种 hack 加速法。咱们实际上不须要z轴的变化,可是仍是假模假样地声明了,去欺骗浏览器。

 

参考文献:

Rendering: repaint, reflow/relayout, restyle

Scrolling Performance

MDN--will-change

How (not) to trigger a layout in WebKit

High Performance Animations

Accelerated Rendering in Chrome

CSS3 制做3D旋转球体

 

到此本文结束,若是还有什么疑问或者建议,能够多多交流,原创文章,文笔有限,才疏学浅,文中如有不正之处,万望告知。

CSS3 3D 行星运转 demo 页面请戳:Demo。(建议使用Chrome打开)

本文完整的代码,以及更多的 CSS3 效果,在我 Github 上能够看到,也但愿你们能够点个 star。

若是本文对你有帮助,请点下推荐,写文章不容易。

相关文章
相关标签/搜索