
当开发超出条件引导框架之外的站点时,关于动画性能的迟早会出现问题。 它们在设计网站中尤其重要,例如属于Awwwards,FWA,CSS Design Awards等目录的网站。 在这种情况下,创建动画和随后进行优化(如果需要)的任务通常落在那些不太有经验的开发人员的肩膀上,他们甚至都不知道从哪里开始。 通常,所有这些都会转化为无法使用的抑制位点,以及随之而来的对此类项目整个类别的负面态度。 在本文中,我们将尝试找出可接受的动画性能的边界在哪里,常见的瓶颈是什么,以及首先在开发人员工具中应该看的地方。
一句话:由于本文更多地面向初学者,并且其目的是展示优化动画的一般方法,因此许多事情将以简化的,不太学术的形式给出。
浏览器如何显示页面
首先,了解浏览器向我们显示页面当前状态时会发生什么会很有用。 主要有四个步骤:
- 样式计算(浏览器解析CSS选择器,确定应将哪些样式应用于什么样式)
- 布局创建(页面布局已实际形成)
- 绘画(创建元素的像素表示形式以进行后续渲染)
- 图层组成(浏览器将所有内容收集在一起并显示在屏幕上)
此外,浏览器始终按此顺序运行并结束。 加载页面后最初显示该页面时,将完成所有四个步骤。 将来,我们的动作可能会导致其中之一的执行,但同时所有后续动作都会被执行。 但不是以前的。
我们将进一步考虑每个步骤的瓶颈,现在我们将乍看之下一个愚蠢的问题,从理论上讲,您需要从这里开始...
是否减慢速度,这就是问题所在...
很多时候,您会遇到一个网站明显慢的人,他们没有做任何事情,然后说:“但是我的页面速度提高了100分,一切都很好。” 反之亦然,在功能良好的网站上,人们长期以来一直在进行某种优化,因为某些算法基于某些神秘指标无法有效地工作。 但是在这些极端之间应该是常识的中间,那么它在哪里呢?

到 认识禅 要了解是否需要优化动画,您需要了解深刻的哲学思想:
如果您发现该网站运行缓慢,则说明它运行缓慢。 如果您没有看到该站点的运行速度变慢,则说明该站点没有变慢。
由于某些原因,很多人觉得这个说法很愚蠢,但是是吗? 对于最终用户而言,性能不是具有严格数学依据的某种度量标准或理想算法。 对他来说,性能是两件事之一:它减慢或不减慢速度。
他如何确定呢? 在显示器后面花费大量时间的人的眼睛开始对fps下降做出急剧反应。 这会引起奇怪的不适感。 因此,作为开发人员,我们的任务是防止沉降。 用户是否习惯于以60fps的速度运行浏览器? 好吧,那么我们正在做所有事情,以便一切都保持这种状态。 我们带中铁的笔记本电脑看一下。 我们看到的远低于60fps-我们进行了优化。 我们看到大约60-不要触摸任何东西。 用户无论如何也不会注意到差异,并且为了优化,我们将花费大量时间进行优化。
不要为优化而进行优化。
16.5毫秒
用fps表示自己并不方便,所以让我们继续讲毫秒。 通过简单地划分1000ms / 60fps,我们每帧时间大约为16.5ms。
这是什么意思? 在16.5毫秒内,浏览器应按照上面的步骤向我们显示带有动画的页面的当前状态,同时,应保留用于其他脚本工作,与服务器通信等的资源。 如果花费更多的时间来显示页面的当前状态,我们将通过眼睛看到滞后。 如果大约16毫秒,则不会下沉,但是铁的负载可能会很高,冷却器会嗡嗡作响,并且电话会变热。 因此,我们需要确保一帧的渲染在时间上不会接近此值,甚至更好的是不超过10ms,以便在性能上有余地。 不要忘记测试总是在中间硬件上进行的-例如,在以下示例中,截屏将在带有集成图形的奔腾Silver上进行。
在您的用户更可能拥有的硬件上进行测试。 如果您在工作场所的桌子底下有一个高端处理器和一个采矿场,那么一切都会很好,而您使用预算笔记本电脑的用户可能会很难过。
为了不仅仅依赖您的良好视力和直觉,至少在基本级别上掌握开发人员工具很有用。 他们不会提供准确的性能数据,还会告诉您在哪里查找问题(如果一切都无法很好完成)。
Google Chrome浏览器中的开发者工具
浏览器中的开发人员工具常常比Linux控制台更能打击许多编码器。 但是实际上没有什么可害怕的。 是的,有很多按钮,但是它们对于解决我们的问题是多余的。 现在,我们将首先了解值得关注的地方,以了解如何处理动画以及是否有必要做任何事情。
在性能方面,我们将大部分时间花在“效果”标签中,然后按相同的按钮。

Ctrl-E快捷键或左侧的圆形按钮将开始并停止记录正在发生的事情。 结果显示在这里。 浏览器写了很多东西,但是一次浏览比多次阅读要好,所以让我们看一下动画并加以观察。 让它成为适合初学者的简单CSS动画。 如果以全屏模式打开它,则可以看到它在明显的卡纸情况下起作用:
我们将在全屏模式下记录几秒钟,然后查看发生了什么:

浏览器会记录其所做的一切。 在窗口的上部,我们可以看到一个fps图表。 如果在处理页面的过程中它开始急剧下降,则可以轻松地使用它来检测异常。 如果用鼠标单击该图并将其拉到侧面或转动滚轮,则可以选择此时间范围,其详细信息将显示在下面。 在我们的简单示例中,没有异常,但是可以清楚地看到,一切工作都不太均匀。
立即注意“ 框架”行,其中包含有关在每个框架上花费的时间的信息。 您会注意到,这一次不断跳跃并且明显超过了16ms(以下,在实际示例中,我们将稍微改善此动画)。
接下来,我们看到几行以不同的颜色显示负载-您可以看到浏览器在不同类型的活动上花费了多少时间。 我们的动画是统一的,并且每帧都执行相同的操作,以紫色和绿色表示。 如果将鼠标移到彩色块上,则很明显,我们正在处理开始时提到的项目- 重新计算样式和更新层树为紫色,而绘画 层和复合层为绿色。
考虑另一个动画。 这次使用脚本-一个简单的噪声发生器。 这是一个相当说明性的示例,尽管从设计的角度来看它并不有趣:
您可能会注意到,已添加了黄色块来显示脚本的执行情况。 如果将有许多函数调用,那么对于每个调用,将添加一个更多的块-根据它们的大小,很容易找到“最重”的函数,可能应该从中开始优化。

在该示例中,花费在一帧上的时间在80ms左右波动。 但是在那里,即使用肉眼也可以清楚地看到一切崩溃的原因。 查看下面的摘要部分,我们看到脚本占用了最多的时间。 与它们相比, 渲染和绘画看起来像是可以忽略的错误。 当然,它并不总是发生,而是经常发生。
如果单击标记为function call的块,则下面是脚本代码中该函数的链接。 如果仔细查看,可以看到在此示例中,屏幕上的所有像素都有一个循环。 在着色器上执行这样的任务会更合乎逻辑,然后性能会好很多倍。 但是我们将在实际示例中进行介绍。
如果...怎么办
我们了解了在浏览器中显示页面的当前状态时需要执行哪些步骤,以及在哪里查看耗时最多的页面。 现在该熟悉为什么此步骤开始需要太多资源的最常见原因,并给出一些有关在此情况下该怎么做的提示。
风格计算
如果您已经看到问题已经开始,那么问题就开始出现了-最有可能的问题甚至不在动画中,而是事实上页面上有太多元素。 在设计站点中,这种情况很少见,通常这样的问题是一个包含数千个元素的大型表的人造卫星,但是如果您仍然遇到此问题:
减少页面上的元素数量,简化布局。 要特别注意使用包装器重复执行代码段,很可能将其删除。
与第一个相关的第二个原因是复杂的CSS选择器。 如果在小页面上很有可能使用深层嵌套,带有相邻元素的棘手技巧等等,那么在很大的页面上,这都可能导致性能下降。
简化CSS选择器,使用BEM。
布局创建
此项已经接近设计和动画,有趣的事情从这里开始。 首先要了解的是,整个布局已形成。 如果我们更改某些内容,它将重新形成。 因此,即使在大页面上进行很小的更改也会在此步骤中引起明显的延迟。
指导我们创建动画时的主要规则是不允许不惜一切代价对布局进行重组。 因此,我们通常不尝试对其进行优化(并且没有特别的机会),也就是说,我们尝试避免它。
有许多属性可能会导致重新布局,您可以在Internet上找到列表,例如csstriggers.com不错。 在动画中比其他动画更常见的是,您可以找到属性:
display position / top / left / right / bottom width / height padding / margin border font-size / font-weight / line-height ...
您可能会注意到,所有这些属性都是一件事结合在一起的-它们描述了元素的几何特征-显示参数,大小和物理位置。 因此,请记住它们的含义,而不是全部记住它们。
不要更改元素的几何属性,最好使用变换和不透明度。
另外,值得注意的是,更改元素的背景也会使我们回到这一步骤。 他们经常忘记这一点,因此我们在另一项建议中着重介绍:
请勿更改背景元素。
在某些浏览器中( 我不会在Firefox中戳手指 )可能会出现典型的CSS动画转换滞后现象,尤其是在每单位时间执行多个动画的情况下。 从外观上看,这不仅可以看作是她工作的暂停,而且可以看作是动画一开始的“崩溃”。 似乎浏览器正在不断重新计算某些内容。 几乎总是使用backface-visibility属性纠正此行为。
如果可能,在动画元素中添加“背面可见性:隐藏”。
同样,重建布局是由我们对脚本元素的调用引起的。 而且,这不必是对CSS的直接更改,也可以是对元素的某些属性和方法的一种吸引。 最常见的是:
offset*** client*** inner*** scroll***
在动画中,您应该小心使用它们,因为 如果我们开始针对大量元素引用这些属性和方法,则每次都会导致布局的重组。
避免针对循环中的各个元素引用提到的属性和方法。
绘画和图层组成
我们将一起考虑这两个步骤,因为 它们在某种程度上是相关的,通常,如果其中一个存在问题,那么另一个也会存在问题。 跳过这些步骤,避免它们将不起作用,因此我们正在尝试以某种方式优化它们。
浏览器不会准备页面的像素图像,而是准备部分-图层。 可能有很多。 每层都好像存在一样,本身并不影响其余部分,这为某些CSS黑客打下了基础。 但是我们下次再讨论。 然后从这些层收集最终图像。 在动画的上下文中,将动画元素放置在单独的图层中非常有用,这样它们的更改不会影响周围的所有内容。 希望元素的含量小。 我们可以使用will-change属性来完成此操作,或者像以前一样使用transform:translateZ(0) 。 唯一要记住的是,您不能无限期地增加层数。 在某些时候,这会起到一定的作用,相反的性能会下降。 因此,有两个提示:
使用will-change或transform:translateZ(0)将动画元素移动到单独的图层。
但同时
请勿过度从事这项业务。 检入开发人员工具,情况还不错。
通常,严重的问题是由过滤器引起的,它们以某种方式转换了元素的图像。 它可以是带有模糊效果的简单CSS过滤器,也可以是SVG的混淆选项,但是效果是一样的-性能明显下降。
不要使用复杂的过滤器。 如果仍然需要预期的效果,请考虑在WebGL上实现它。
这些技巧如何运作?
它们有效,但是您不必期望它们会产生奇迹。 在网上,新手有时会说:“我增加了意志改变,但没有任何改变。” 通常,这意味着主要问题在另一个地方,并且该技术使生产率的提高幅度很小,以至于没有引起注意。 这就是为什么使用开发人员的工具清楚地了解瓶颈所在,而不要花费时间和精力来尝试优化正常工作的原因,这一点很重要。
从所有这些我们可以得出结论,没有很多方法可以影响页面的呈现,并且它们的效果并不总是很明显。 这些技巧不是灵丹妙药,而是抛光动画所必需的。 如果我们查看性能确实很差的网站,则会注意到在大多数情况下,我们自己的脚本应该受到指责,而不是CSS在浏览器肠道中解析的神秘问题。
脚本...
您是否知道抑制性动画的问题最常出现在哪里(根据我的观察)? 从这种开发方法来看:

听起来很傻,但是确实如此。 不断有解决方案,显然是完全从某个地方复制而已,根本不了解发生了什么。 您甚至可以删除一半的代码,一切都会继续进行。 SO或Toaster答案中的代码通常不适合您的生产。 那应该很明显。 他展示了想法,回答了问题,但对于您的特定任务而言,根本不是最佳的最终选择。
如果已经在复制,则至少查看代码中是否有不必要的操作。
RequestAnimationFrame
他们经常谈论这种方法,并建议在动画中使用它代替setTimeout / setInterval 。 这是有道理的,因为这些方法具有与浏览器重绘的帧不同步的属性,从而导致较小的滞后。 但是有两点。
首先,如果页面上有多个动画元素,并且我们多次调用requestAnimationFrame,这将导致fps急剧下降。 从理论上讲,这不是应该的,但实际上,所有事情都是这样。 您可以在这里熟悉测试。
将所有动画回调组合到一个requestAnimationFrame中。
第二点更可能与以下情况有关:当我们已经有大量动画时,也许是在使用画布时,我们无法摆脱或没有时间进行重做,并且发生了以下情况:假设动画应该在N秒内完成并且我们已经使用requestAnimationFrame 。 但是计算当前状态需要大量资源,我们可以看到这张图片:动画可以流畅,精美地运行,但仅需2N甚至3N秒。 结果,一切都被感觉到了。 为了以某种方式纠正此行为,可以违反所有建议,从而使用setInterval / setTimeout并将动画元素的状态绑定到物理时间,而不是抽象帧。 结果,我们得到了fps的正式降低,但受到了生产率提高的心理影响。
在动画非常慢的情况下,最好拒绝setAnimal / setTimeout的requestAnimationFrame。
画布和着色器
非标准网站上的动画很大一部分与画布有关。 这是可以理解的,CSS是有限的事情,但是在这里我们可以实现任何设计师的幻想。 但是您需要记住,普通的2D画布远不是最高效的技术。 如果您开始在其上绘制大量元素或直接使用像素,那么您会很快发现fps正在下降,或者突然之间绘画和图层合成开始花费大量时间的事实。 在示例中可以清楚地看到此问题:
让我们看一下浏览器的功能(Linux下最新的Google Chrome):

请注意, 图层合成步骤已扩展了多少。 看起来有点不合逻辑,因为只有一个元素,所以可以在那里组装这么长时间? 但是,当使用2d canvas时,这种现象并不少见,与此相关的问题非常严重。 这是我们通常倾向于使用WebGL的原因之一,没有这样的问题。
如果在2d canvas和WebGL之间进行选择,请选择第二个。 这将为相同任务提供初始性能奖励。
WebGL通常与什么相关? 带有着色器。 对于使用着色器的人来说,调试着色器令人头疼。 而且这里的开发人员工具几乎无能为力。 通常,如果着色器中的计算太多,我们在下面的摘要中看到最多的时间是“简单”,实际上是在不考虑浏览器的情况下执行着色器,而且我们无法获得任何有用的细节。
关于优先使用哪些功能而不是着色器,存在不同的建议,因为它们可能得到了更好的优化。 否则应避免阻塞操作。 都是如此,但是根据我的观察,在大多数情况下,会使站点减慢太多的着色器只是非常大的着色器。 如果您在一处编写了100条GLSL行,几乎可以保证它不能正常工作。 而且,如果还有其他不同的嵌套构造,循环,那么所有内容-写入操作都将消失。 除非在以下情况下很难在此处提供任何建议:
如果在工作期间您意识到一切都比最初看起来要复杂,并且会有很多代码并且会变慢-最好尽快与设计人员和客户讨论此问题并考虑可以更改的内容。
通常,您可以得出这样的结论:预先准备的视频比尝试实时呈现某种混乱的事物要好得多。 记住这一点。 是的,每个人都想展示自己,他们想展示“但我可以那样做”,但不要忘记最终用户。
关于这一思想,我记得前奥运会特别容易引起的“疾病”。 由于某些原因,在使用画布时会强烈体现出来。 由于这个原因,您应该始终仔细复制这些人的代码。 他们尝试使用“正确的”数学算法,复杂的物理公式来计算元素的所有运动,即使在完全没有用处的情况下,其准确性也很高。 这导致处理器上的负载增加,并且导致这样的事实:对于我们的条件10ms,它没有时间计算任何东西。 在实践中,您通常可以获得近似公式和有关物理的学校知识。 不必使事情复杂化,我们可以创建网站,而不是用于弹道导弹的软件。
使用简单的算法。
还有另一个技巧叫RayMarching 。 有些人认为创造不同的效果就像是挑战,是心灵的热身,有时效果是非常令人印象深刻的。 例如,这里生成了一个整个水下世界(我插入了一个视频,因为根据对此的实时计算,手机/笔记本电脑可以自行挂起):
着色器本身可以在这里找到。
实际上,所有这些都需要大量的资源来工作。 在全屏模式下,每帧有400-800ms(通常,在此示例中,它最多可以达到1500ms):

因此,如果您发现自己想在战场上做这样的事情,请给自己戴上键盘,喝茶,然后考虑实现效果的其他选择。
不要使用RayMarching,这是确保性能的肯定方法。
实际例子
关于生产力的文章中通常没有足够的例子,但是一句话很难说。 所以考虑一对。 还记得第一个CSS旋转隧道示例吗? 浏览器做了很多事情:

我们想加快速度。 从哪里开始? 我们看到紫色块,这表示浏览器正在不断重建布局。 那里没有脚本,但是有一些CSS动画,其中有些变化。 让我们看看他们的代码:
@keyframes rotate { from { transform: rotate(0); } to { transform: rotate(360deg); } } @keyframes move-block { from { transform: translateX(0); background: @color1; } to { transform: translateX(-@block-size * 6); background: @color2; } }
转换不会吓到我们,但是我们看到元素背景发生了变化。 我们记得这可能会导致布局重组,并且我们认为在这种情况下可以采取的措施...
需要不惜一切代价消除更改背景的情况,因此,基于动画的一般思想,我们决定可以在顶部放置一个径向渐变,这将产生几乎相同的体积效果。 有人会说渐变对性能有不良影响,但我们不会更改它。 一旦它受到严重的影响,那就更好了,而不是让我们拥有一堆不断受到严重影响的元素。 结果是:
让我们看看浏览器的功能:

哇……我们看到的是,除了一系列动作之外,我们很少看到对GPU的调用,而动画本身开始变得更加平滑。
另一个例子
回顾噪声发生器中的浏览器外观:

问题肯定在脚本中。 可以看出,“渲染”块最大。 这是我们渲染图像的主要功能。 让我们看看她:
function render() { let imageData = CTX.createImageData(CTX.canvas.width, CTX.canvas.height); for (let i = 0; i < imageData.data.length; i += 4) { const color = getRandom(); imageData.data[i] = color; imageData.data[i + 1] = color; imageData.data[i + 2] = color; imageData.data[i + 3] = 255; } CTX.putImageData(imageData, 0, 0); requestAnimationFrame(render); }
单个像素肯定有工作要做。 这不是很健康。 我们说过,如果可能的话,最好不要使用2d画布,而最好使用WebGL,而此任务只是想使用着色器进行并行化。 让我们做吧:
结果如何? 自己看看:

一帧的时间减少到将近16ms。 当然这不是理想的,但仍比80ms好。 在复杂的精美动画中,这种性能提升非常明显。 我借此机会建议初学者熟悉编程中着色器的介绍以及示例的延续 。
结论
在本文中,我们找出了优化动画性能的时间,在这种情况下如何在Chrome中使用开发人员工具以及首先要寻找的内容。 我希望这些信息对于第一次遇到此类任务并且不知道从哪里开始的开发人员有用。