Open preflower opened 4 years ago
即将动画从CPU渲染,移交给GPU做
隐式合成就是特定场景下,部分元素会被默认提升为合成层的情况
translate3d
translateZ
<video>
<canvas>
<iframe>
transform
opacity
本文旨在帮助您更好地理解浏览器是如何使用GPU进行渲染的,这样你就可以创建令人印象深刻的网站。
大多数人都知道现代浏览器使用GPU去渲染部分网页,特别是动画。举个例子,一个CSS动画使用transform属性会比使用left, top属性更加顺滑。但是如果你问,“如何从GPU上获得平滑动画?“大部分情况下,你会听到例如”使用transform: translateZ(0)或will-change: transform
left
top
transform: translateZ(0)
will-change: transform
这些已经成为唤起GPU渲染(compositing - 浏览器产商的称呼)的通用属性,就像是如何使用zoom: 1在IE6中一样
zoom: 1
但有时GPU渲染的一个顺滑的动画demo在真实环境下会运行的很慢甚至是导致浏览器崩溃。为什么会这样?如何修复这个问题?让我们走进科学。
设置A, B两个元素,具有absolute属性和不同的z-index属性。浏览器将会从CPU绘制它们,并把生成的图像发送给GPU,后者将其展示到屏幕上
absolute
z-index
<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>
我们决定使用left属性和animation属性使A元素移动
animation
<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>
在这种情况下,对于每个动画帧,浏览器必须重新计算元素的几何形状(即回流 - Reflow),渲染页面的新状态的图像(即重绘 - Repaint),然后再次发送给GPU在屏幕上显示。我们知道重绘是非常耗费性能的,但每个现代浏览器都足够聪明,只重新绘制页面的更改区域,而不是整个页面。虽然浏览器在大多数情况下可以很快地重新绘制,但我们的动画仍然不流畅。
在动画的每个帧中(甚至是增量地)重绘和回流整个页面听起来非常缓慢,特别是对于大型和复杂的布局。如果只绘制两个独立的图像,一个用于A元素,另一个用于没有A元素的整个页面,然后简单地相互偏移这些图像,效果会更好。换句话说,合成(Compositing)已缓存元素的图像会更快。这正是GPU的亮点所在: 它能够以亚像素的精度非常快速地合成图像,这为动画增添了一种性感的流畅感。
为了优化合成(Compositing)性能,浏览器需要动画属性确保以下几点:
有人可能会认为position为absolute/fixed下的top, left等属性不依赖元素环境,但事实并非如此。例如:left属性设置支持百分比,其依赖于.offsetParent的大小;除此以外,em, vh这些单位也都依赖于元素环境。相反,transfomr和opacity是唯一满足上述条件的CSS属性。
position
fixed
.offsetParent
em
vh
transfomr
让我们用transform代替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>
这里我们显式地描述了动画:起始位置和持续时间等。这等于提前告诉浏览器这些CSS属性将会被改变,因为浏览器看到这些属性不会造成重绘和回流就会提供一个合成(Compositing)优化:绘制两个图像作为合成层(Compositing Layers),并将它们发送到GPU。
这些优化的优点是什么?
一切看起来都很简单明了,对吧?我们会遇到什么问题?让我们看看这个优化是如何工作的。
这可能会让你感到惊讶,但GPU是一个独立的计算机。没错:每个现代设备的基本组成部分实际上都是一个独立的单元,有自己的处理器、自己的内存和数据处理模型。与其他应用或游戏一样,浏览器也必须像与外部设备一样与GPU通信。
为了更好地理解这是如何工作的,可以参考AJAX。假设您想要注册一个网站访问者的数据,他们已经在一个web表单中输入。您不能直接告诉远程服务器:“嘿,从这些输入字段中获取数据和JavaScript变量并将其保存到数据库。”远程服务器无法访问用户浏览器中的内存。相反,您必须将页面中的数据收集到一个有效负载中,该有效负载具有易于解析的简单数据格式(如JSON),并将其发送到远程服务器。
在合成过程中也会发生非常类似的事情。因为GPU就像一个远程服务器,浏览器必须首先创建一个有效负载(Payload),然后将其发送到设备。当然,GPU与CPU之间并没有几千公里的距离; 就在那儿。然而,在许多情况下,远程服务器请求和响应所需的2秒时间是可以接受的,GPU数据传输额外的3到5毫秒将导致笨拙的动画。
GPU有效负载(Payload)是什么样子的?在大多数情况下,它包括图层图像,以及附加的说明,如图层的大小,偏移量,动画参数等。以下是使用GPU制作有效负载和传输数据的大致情况:
// TODO:待重新翻译 正如您所看到的,每次向元素添加神奇的transform: translateZ(0)或will-change: transform属性时,都会启动完全相同的过程。 重绘(Repaint)的性能消耗非常昂贵,但在这里它甚至更慢。在大多数情况下,浏览器无法增量地重新绘制。它必须用新创建的复合图层绘制之前覆盖的区域
让我们回到A和B元素的例子。前面,我们动画了A元素,它位于页面上所有其他元素的顶部。这样就形成了A元素和B元素和页面背景的两个合成层。
现在,让我们把B元素变成动画
我们遇到了一个逻辑问题。元素B应该在一个单独的合成层上,屏幕的最终页面图像应该在GPU上合成。但是A元素应该出现在元素B的顶部,并且我们没有指定关于A的任何东西来将它提升到自己的层。
记住一个重要的免责声明: 特殊的GPU合成模式不是CSS规范的一部分;这只是浏览器内部应用的优化。我们必须让A按照z-index的顺序出现在B的上面。浏览器会做什么?
你猜对了! 它会强制为元素a创建一个新的合成层,并添加另一个重绘
这被称为隐式合成: 一个或多个非合成元素按照堆叠顺序出现在合成元素之上,被提升到合成层,即被绘制成独立的图像,然后发送到GPU
我们发现隐性作曲的频率比你想象的要高得多。浏览器将一个元素提升到合成层的原因有很多,只是其中的一些原因:
似乎GPU动画的主要问题是意料之外的重绘。但事实并非如此。更大的问题是...
另一个温和的提醒是,GPU是一个独立的计算机:它不仅需要发送渲染层图像到GPU,而且需要存储它们,以便以后在动画中重用。
一个复合层需要多少内存?让我们举一个简单的例子。试着猜测一个320x240像素的矩形,填充纯色#FF0000,需要多少内存。
320x240
#FF0000
一个典型的网页开发人员会想,嗯,这是一个纯色图像。我将它保存为PNG格式,并检查其大小。它应该小于1 KB。他们完全正确:这张图片PNG格式的大小是104字节。
计算公式: bytes = width(px)xheight(px)x3(RGB) 若存在透明度,则为bytes = width(px)xheight(px)x4(RGBa),而浏览器总是将合成图层绘制为RGBa图像。似乎没有有效的方法来确定一个元素是否包含透明区域。
bytes = width(px)xheight(px)x3(RGB)
bytes = width(px)xheight(px)x4(RGBa)
本文只做自身学习使用翻译,拒绝转载,拒绝商业化行为
原文:https://www.smashingmagazine.com/2016/12/gpu-animation-doing-it-right/
其他参考:
TLDR
基本概念
什么是GPU渲染
即将动画从CPU渲染,移交给GPU做
优势
劣势
隐式合成
隐式合成就是特定场景下,部分元素会被默认提升为合成层的情况
如何触发GPU渲染
translate3d
,translateZ
, 因为它使用 GPU 来计算 perspective distortion(透视失真)<video>
,<canvas>
and<iframe>
元素transform
andopacity
触发的动画优化点
transform
和opacity
实现: 相当于预先告知浏览器动画轨迹,浏览器可以提前渲染引言
大多数人都知道现代浏览器使用GPU去渲染部分网页,特别是动画。举个例子,一个CSS动画使用
transform
属性会比使用left
,top
属性更加顺滑。但是如果你问,“如何从GPU上获得平滑动画?“大部分情况下,你会听到例如”使用transform: translateZ(0)
或will-change: transform
这些已经成为唤起GPU渲染(compositing - 浏览器产商的称呼)的通用属性,就像是如何使用
zoom: 1
在IE6中一样但有时GPU渲染的一个顺滑的动画demo在真实环境下会运行的很慢甚至是导致浏览器崩溃。为什么会这样?如何修复这个问题?让我们走进科学。
Compositing 是如何工作的?
设置A, B两个元素,具有
absolute
属性和不同的z-index
属性。浏览器将会从CPU绘制它们,并把生成的图像发送给GPU,后者将其展示到屏幕上我们决定使用
left
属性和animation
属性使A元素移动在这种情况下,对于每个动画帧,浏览器必须重新计算元素的几何形状(即回流 - Reflow),渲染页面的新状态的图像(即重绘 - Repaint),然后再次发送给GPU在屏幕上显示。我们知道重绘是非常耗费性能的,但每个现代浏览器都足够聪明,只重新绘制页面的更改区域,而不是整个页面。虽然浏览器在大多数情况下可以很快地重新绘制,但我们的动画仍然不流畅。
在动画的每个帧中(甚至是增量地)重绘和回流整个页面听起来非常缓慢,特别是对于大型和复杂的布局。如果只绘制两个独立的图像,一个用于A元素,另一个用于没有A元素的整个页面,然后简单地相互偏移这些图像,效果会更好。换句话说,合成(Compositing)已缓存元素的图像会更快。这正是GPU的亮点所在: 它能够以亚像素的精度非常快速地合成图像,这为动画增添了一种性感的流畅感。
为了优化合成(Compositing)性能,浏览器需要动画属性确保以下几点:
有人可能会认为
position
为absolute
/fixed
下的top
,left
等属性不依赖元素环境,但事实并非如此。例如:left
属性设置支持百分比,其依赖于.offsetParent
的大小;除此以外,em
,vh
这些单位也都依赖于元素环境。相反,transfomr
和opacity
是唯一满足上述条件的CSS属性。让我们用transform代替left来制作动画:
这里我们显式地描述了动画:起始位置和持续时间等。这等于提前告诉浏览器这些CSS属性将会被改变,因为浏览器看到这些属性不会造成重绘和回流就会提供一个合成(Compositing)优化:绘制两个图像作为合成层(Compositing Layers),并将它们发送到GPU。
这些优化的优点是什么?
一切看起来都很简单明了,对吧?我们会遇到什么问题?让我们看看这个优化是如何工作的。
这可能会让你感到惊讶,但GPU是一个独立的计算机。没错:每个现代设备的基本组成部分实际上都是一个独立的单元,有自己的处理器、自己的内存和数据处理模型。与其他应用或游戏一样,浏览器也必须像与外部设备一样与GPU通信。
为了更好地理解这是如何工作的,可以参考AJAX。假设您想要注册一个网站访问者的数据,他们已经在一个web表单中输入。您不能直接告诉远程服务器:“嘿,从这些输入字段中获取数据和JavaScript变量并将其保存到数据库。”远程服务器无法访问用户浏览器中的内存。相反,您必须将页面中的数据收集到一个有效负载中,该有效负载具有易于解析的简单数据格式(如JSON),并将其发送到远程服务器。
在合成过程中也会发生非常类似的事情。因为GPU就像一个远程服务器,浏览器必须首先创建一个有效负载(Payload),然后将其发送到设备。当然,GPU与CPU之间并没有几千公里的距离; 就在那儿。然而,在许多情况下,远程服务器请求和响应所需的2秒时间是可以接受的,GPU数据传输额外的3到5毫秒将导致笨拙的动画。
GPU有效负载(Payload)是什么样子的?在大多数情况下,它包括图层图像,以及附加的说明,如图层的大小,偏移量,动画参数等。以下是使用GPU制作有效负载和传输数据的大致情况:
// TODO:待重新翻译 正如您所看到的,每次向元素添加神奇的
transform: translateZ(0)
或will-change: transform
属性时,都会启动完全相同的过程。 重绘(Repaint)的性能消耗非常昂贵,但在这里它甚至更慢。在大多数情况下,浏览器无法增量地重新绘制。它必须用新创建的复合图层绘制之前覆盖的区域Implicit Compositing - 隐式合成
让我们回到A和B元素的例子。前面,我们动画了A元素,它位于页面上所有其他元素的顶部。这样就形成了A元素和B元素和页面背景的两个合成层。
现在,让我们把B元素变成动画
我们遇到了一个逻辑问题。元素B应该在一个单独的合成层上,屏幕的最终页面图像应该在GPU上合成。但是A元素应该出现在元素B的顶部,并且我们没有指定关于A的任何东西来将它提升到自己的层。
记住一个重要的免责声明: 特殊的GPU合成模式不是CSS规范的一部分;这只是浏览器内部应用的优化。我们必须让A按照z-index的顺序出现在B的上面。浏览器会做什么?
你猜对了! 它会强制为元素a创建一个新的合成层,并添加另一个重绘
这被称为隐式合成: 一个或多个非合成元素按照堆叠顺序出现在合成元素之上,被提升到合成层,即被绘制成独立的图像,然后发送到GPU
我们发现隐性作曲的频率比你想象的要高得多。浏览器将一个元素提升到合成层的原因有很多,只是其中的一些原因:
translate3d
,translateZ
等等<video>
,<canvas>
and<iframe>
元素;transform
andopacity
触发的动画;似乎GPU动画的主要问题是意料之外的重绘。但事实并非如此。更大的问题是...
Memory Consumption - 内存消耗
另一个温和的提醒是,GPU是一个独立的计算机:它不仅需要发送渲染层图像到GPU,而且需要存储它们,以便以后在动画中重用。
一个复合层需要多少内存?让我们举一个简单的例子。试着猜测一个
320x240
像素的矩形,填充纯色#FF0000
,需要多少内存。一个典型的网页开发人员会想,嗯,这是一个纯色图像。我将它保存为PNG格式,并检查其大小。它应该小于1 KB。他们完全正确:这张图片PNG格式的大小是104字节。
计算公式:
bytes = width(px)xheight(px)x3(RGB)
若存在透明度,则为bytes = width(px)xheight(px)x4(RGBa)
,而浏览器总是将合成图层绘制为RGBa图像。似乎没有有效的方法来确定一个元素是否包含透明区域。优势和劣势
优势
劣势
优化点
transform
和opacity
实现须知
原文:https://www.smashingmagazine.com/2016/12/gpu-animation-doing-it-right/
其他参考: