一篇文章说清浏览器剖析和CSS(GPU)动画优化

置信不少人在做挪动端动画的时刻遇到了卡顿的题目,这篇文章尝试从浏览器衬着的角度;一点一点通知你动画优化的道理及其技能,作为你事情中优化动画的参考。文末有优化技能的总结。

因为GPU合成没有官方范例,每一个浏览器的题目和处理体式格局也差别;所以文章内容仅供参考。

浏览器衬着

进步动画的优化不能不说起浏览器是怎样衬着一个页面。在从服务器中拿到数据后,浏览器会先做剖析三类东西:

  • 剖析html,xhtml,svg这三类文档,构成dom树。

  • 剖析css,发作css rule tree。

  • 剖析js,js会经由历程api来操纵dom tree和css rule tree。

剖析完成以后,浏览器引擎会经由历程dom tree和css rule tree来构建rendering tree:

  • rendering tree和dom tree并不完整相同,比方:<head></head>或display:none的东西就不会放在衬着树中。

  • css rule tree主假如完成婚配,并把css rule附加给rendering tree的每一个element。

在衬着树构建完成后,

  • 浏览器会对这些元素举行定位和规划,这一步也叫做reflow或许layout。

  • 浏览器绘制这些元素的款式,色彩,背景,大小及边框等,这一步也叫做repaint。

  • 然后浏览器会将各层的信息发送给GPU,GPU会将各层合成;显如今屏幕上。

衬着优化道理

如上所说,衬着树构建完成后;浏览器要做的步骤:

reflow——》repaint——》composite

reflow和repaint

reflow和repaint都是斲丧浏览器机能的操纵,这两者尤以reflow为甚;因为每次reflow,浏览器都要从新盘算每一个元素的外形和位置。

因为reflow和repaint都是异常斲丧机能的,我们的浏览器为此做了一些优化。浏览器会将reflow和repaint的操纵积累一批,然后做一次reflow。然则有些时刻,你的代码会强迫浏览器做屡次reflow。比方:

var content = document.getElementById('content');
content.style.width = 700px;
var contentWidth = content.offsetWidth;
content.style.backgound = 'red';

以上第三行代码,须要浏览器reflow后;再获取值,所以会致使浏览器多做一次reflow。

下面是一些针对reflow和repaint的最好实践:

  • 不要一条一条地修正dom的款式,只管运用className一次修正。

  • 将dom离线后修正

    • 运用documentFragment对象在内存里操纵dom。

    • 先把dom节点display:none;(会触发一次reflow)。然后做大批的修正后,再把它显现出来。

    • clone一个dom节点在内存里,修正以后;与在线的节点相替代。

  • 不要运用table规划,一个小修改会形成全部table的从新规划。

  • transform和opacity只会引发合成,不会引发规划和重绘。

从上述的最好实践中你能够发明,动画优化平常都是只管地削减reflow、repaint的发作。关于哪些属性会引发reflow、repaint及composite,你能够在这个网站找到https://csstriggers.com/

composite

在reflow和repaint以后,浏览器会将多个复合层传入GPU;举行合成事情,那末合成是怎样事情的呢?

假定我们的页面中有A和B两个元素,它们有absolute和z-index属性;浏览器会重绘它们,然后将图象发送给GPU;然后GPU将会把多个图象合成展如今屏幕上。

<style>
#a, #b {
 position: absolute;
}

#a {
 left: 30px;
 top: 30px;
 z-index: 2;
}

#b {
 z-index: 1;
}
</style>
<div id="#a">A</div>
<div id="#b">B</div>

《一篇文章说清浏览器剖析和CSS(GPU)动画优化》

我们将A元素运用left属性,做一个挪动动画:

<style>
#a, #b {
 position: absolute;
}

#a {
 left: 10px;
 top: 10px;
 z-index: 2;
 animation: move 1s linear;
}

#b {
 left: 50px;
 top: 50px;
 z-index: 1;
}

@keyframes move {
 from { left: 30px; }
 to { left: 100px; }
}
</style>
<div id="#a">A</div>
<div id="#b">B</div>

在这个例子中,关于动画的每一帧;浏览器会盘算元素的若干外形,衬着新状况的图象;并把它们发送给GPU。(你没看错,position也会引发浏览器重排的)只管浏览器做了优化,在repaint时,只会repaint部份地区;然则我们的动画依旧不够流通。

因为重排和重绘发作在动画的每一帧,一个有用防止reflow和repaint的体式格局是我们仅仅画两个图象;一个是a元素,一个是b元素及全部页面;我们将这两张图片发送给GPU,然后动画发作的时刻;只做两张图片相对对方的平移。也就是说,仅仅合成缓存的图片将会很快;这也是GPU的上风——它能异常快地以亚像素精度地合成图片,并给动画带来腻滑的曲线。

为了仅发作composite,我们做动画的css property必需满足以下三个前提:

  • 不影响文档流。

  • 不依赖文档流。

  • 不会形成重绘。

满足以上以上前提的css property只要transform和opacity。你能够认为position也满足以上前提,但现实不是如许,举个例子left属性能够运用百分比的值,依赖于它的offset parent。另有em、vh等其他单元也依赖于他们的环境。

我们运用translate来替代left

<style>
#a, #b {
 position: absolute;
}

#a {
 left: 10px;
 top: 10px;
 z-index: 2;
 animation: move 1s linear;
}

#b {
 left: 50px;
 top: 50px;
 z-index: 1;
}

@keyframes move {
 from { transform: translateX(0); }
 to { transform: translateX(70px); }
}
</style>
<div id="#a">A</div>
<div id="#b">B</div>

浏览器在动画实行之前就晓得动画怎样最先和完毕,因为浏览器没有看到须要reflow和repaint的操纵;浏览器就会画两张图象作为复合层,并将它们传入GPU。

如许做有两个上风:

  • 动画将会异常流通

  • 动画不在绑定到CPU,纵然js实行大批的事情;动画依旧流通。

看起来机能题目彷佛已处理了?在下文你会看到GPU动画的一些题目。

GPU是怎样合成图象的

GPU实际上能够看做一个自力的盘算机,它有本身的处理器和存储器及数据处理模子。当浏览器向GPU发送音讯的时刻,就像向一个外部装备发送音讯。

你能够把浏览器向GPU发送数据的历程,与运用ajax向服务器发送音讯异常相似。想一下,你用ajax向服务器发送数据,服务器是不会直接接收浏览器的存储的信息的。你须要网络页面上的数据,把它们放进一个载体内里(比方JSON),然后发送数据到长途服务器。

一样的,浏览器向GPU发送数据也须要先建立一个载体;只不过GPU间隔CPU很近,不会像长途服务器那样能够几千里那末远。然则关于长途服务器,2秒的耽误是能够接收的;然则关于GPU,几毫秒的耽误都邑形成动画的卡顿。

浏览器向GPU发送的数据载体是什么样?这里给出一个简朴的制造载体,并把它们发送到GPU的历程。

  • 画每一个复合层的图象

  • 预备图层的数据

  • 预备动画的着色器(假如须要)

  • 向GPU发送数据

所以你能够看到,每次当你增添transform:translateZ(0)will-change:transform给一个元素,你都邑做一样的事情。重绘是异常斲丧机能的,在这里它特别迟缓。在大多数状况,浏览器不能增量重绘。它不能不重绘先前被复合层掩盖的地区。

隐式合成

还记得适才a元素和b元素动画的例子吗?如今我们将b元素做动画,a元素静止不动。

《一篇文章说清浏览器剖析和CSS(GPU)动画优化》

和适才的例子差别,如今b元素将具有一个自力复合层;然后它们将被GPU合成。然则因为a元素要在b元素的上面(因为a元素的z-index比b元素高),那末浏览器会做什么?浏览器会将a元素也零丁做一个复合层!

所以我们如今有三个复合层a元素地点的复合层、b元素地点的复合层、其他内容及背景层。

一个或多个没有本身复合层的元素要出如今有复合层元素的上方,它就会具有本身的复合层;这类状况被称为隐式合成。

浏览器将a元素提拔为一个复合层有很多种缘由,下面列举了一些:

  • 3d或透视变更css属性,比方translate3d,translateZ等等(js平常经由历程这类体式格局,使元素取得复合层)

  • <video><iframe><canvas><webgl>等元素。

  • 夹杂插件(如flash)。

  • 元素本身的 opacity和transform 做 CSS 动画。

  • 具有css过滤器的元素。

  • 运用will-change属性。

  • position:fixed。

  • 元素有一个 z-index 较低且包括一个复合层的兄弟元素(换句话说就是该元素在复合层上面衬着)

这看起来css动画的机能瓶颈是在重绘上,然则实在的题目是在内存上:

内存占用

运用GPU动画须要发送多张衬着层的图象给GPU,GPU也须要缓存它们以便于后续动画的运用。

一个衬着层,须要若干内存占用?为了便于明白,举一个简朴的例子;一个宽、高都是300px的纯色图象须要若干内存?

300 300 4 = 360000字节,即360kb。这里乘以4是因为,每一个像素须要四个字节盘算机内存来形貌。

假定我们做一个轮播图组件,轮播图有10张图片;为了完成图片间腻滑过渡的交互;为每一个图象增添了will-change:transform。这将提拔图象为复合层,它将多须要19mb的空间。800 600 4 * 10 = 1920000。

仅仅是一个轮播图组件就须要19m的分外空间!

在chrome的开发者东西中翻开setting——》Experiments——》layers能够看到每一个层的内存占用。如图所示:

《一篇文章说清浏览器剖析和CSS(GPU)动画优化》

《一篇文章说清浏览器剖析和CSS(GPU)动画优化》

GPU动画的长处和瑕玷

如今我们能够总结一下GPU动画的长处和瑕玷:

  • 每秒60帧,动画腻滑、流通。

  • 一个适宜的动画事情在一个零丁的线程,它不会被大批的js盘算壅塞。

  • 3D“变更”是廉价的。

瑕玷:

  • 提拔一个元素到复合层须要分外的重绘,偶然这是慢的。(即我们获得的是一个全层重绘,而不是一个增量)

  • 绘图层必需传输到GPU。取决于层的数目和传输能够会异常迟缓。这能够让一个元素在中低档装备上闪灼。

  • 每一个复合层都须要斲丧分外的内存,过量的内存能够致使浏览器的崩溃。

  • 假如你不斟酌隐式合成,而运用重绘;会致使分外的内存占用,而且浏览器崩溃的几率是异常高的。

  • 我们会有视觉假象,比方在Safari中的文本衬着,在某些状况下页面内容将消逝或变形。

优化技能

防止隐式合成

  • 坚持动画的对象的z-index只管的高。抱负的,这些元素应该是body元素的直接子元素。固然,这不是总能够的。所以你能够克隆一个元素,把它放在body元素下仅仅是为了做动画。

  • 将元素上设置will-change CSS属性,元素上有了这个属性,浏览器会提拔这个元素成为一个复合层(不是老是)。如许动画就能够腻滑的最先和完毕。然则不要滥用这个属性,否则会大大增添内存斲丧。

动画中只运用transform和opacity

如上所说,transform和opacity保证了元素属性的变化不影响文档流、也不受文档流影响;而且不会形成repaint。
有些时刻你能够想要转变其他的css属性,作为动画。比方:你能够想运用background属性转变背景:

<div class="bg-change"></div>
.bg-change {
  width: 100px;
  height: 100px;
  background: red;
  transition: opacity 2s;
}
.bg-change:hover {
  background: blue;
}

在这个例子中,在动画的每一步;浏览器都邑举行一次重绘。我们能够运用一个复层在这个元素上面,而且仅仅变更opacity属性:

<div class="bg-change"></div>
<style>
.bg-change {
  width: 100px;
  height: 100px;
  background: red;
}
.bg-change::before {
  content: '';
  display: block;
  width: 100%;
  height: 100%;
  background: blue;
  opacity: 0;
  transition: opacity 20s;
}
.bg-change:hover::before {
  opacity: 1;
}
</style>

减小复合层的尺寸

看一下两张图片,有什么差别吗?

《一篇文章说清浏览器剖析和CSS(GPU)动画优化》

这两张图片视觉上是一样的,然则它们的尺寸一个是39kb;别的一个是400b。差别之处在于,第二个纯色层是经由历程scale放大10倍做到的。

<div id="a"></div>
<div id="b"></div>

<style>
#a, #b {
 will-change: transform;
}

#a {
 width: 100px;
 height: 100px;
}

#b {
 width: 10px;
 height: 10px;
 transform: scale(10);
}
</style>

关于图片,你要怎么做呢?你能够将图片的尺寸削减5%——10%,然后运用scale将它们放大;用户不会看到什么区别,然则你能够削减大批的存储空间。

用css动画而不是js动画

css动画有一个重要的特征,它是完整事情在GPU上。因为你声清楚明了一个动画怎样最先和怎样完毕,浏览器会在动画最先前预备好一切须要的指令;并把它们发送给GPU。而假如运用js动画,浏览器必需盘算每一帧的状况;为了保证腻滑的动画,我们必需在浏览器主线程盘算新状况;把它们发送给GPU最少60次每秒。除了盘算和发送数据比css动画要慢,主线程的负载也会影响动画; 当主线程的盘算使命过量时,会形成动画的耽误、卡顿。

所以只管地运用基于css的动画,不单单议更快;也不会被大批的js盘算所壅塞。

优化技能总结

  • 削减浏览器的重排和重绘的发作。

  • 不要运用table规划。

  • css动画中只管只运用transform和opacity,这不会发作重排和重绘。

  • 只管地只运用css做动画。

  • 防止浏览器的隐式合成。

  • 转变复合层的尺寸。

参考

GPU合成重要参考:

https://www.smashingmagazine….

哪些属性会引发reflow、repaint及composite,你能够在这个网站找到:

https://csstriggers.com/

    原文作者:黑色的影子
    原文地址: https://segmentfault.com/a/1190000008015671
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞