Web浏览器对HTML,CSS和JavaScript的渲染过程非常复杂,并且很神奇。大概经历了下面几步:
- 浏览器创建DOM和CSSOM。
- 浏览器创建渲染树,把CSSOM中的DOM和样式也包括在内。(
display:none
属性的元素是不渲染的。) - 浏览器根据渲染树计算布局及其元素的位置。
- 浏览器逐个像素地进行绘制,最终创建我们在屏幕上看到的样子。
在本文中,我想重点关注最后一部分:绘制。
当页面在加载的时候,要走完这些步骤,需要浏览器进行大量的工作。实际上,当DOM
(document object model,文档对象模型)和CSSOM
(css object model,css对象模型)改变的时候也会重复这些步骤。这也就是为什么开发者在开发的时候更偏向于用一些前端框架,例如React
。这些框架可以高度优化dom树的改变,从而避免不必要的渲染。
你可能听说过“状态(state
)”、“组件渲染(component rendering
)”或者“不变性(immutability
)”等这些名词。所有这些名词的意义都是为了优化dom的改变,也就是说,避免不必要的dom改变。
举个例子,一个web应用的改变可能会导致UI界面的改变,但是实际上,某些(或者很多)组件在并不需要变化。React
所做的就是,当状态改变的时候,计算出真正的最小的受影响的组件,从而尽可能地减少dom的渲染。 (diff算法了解一下?)
DOM/CSSOM → render tree → layout → painting
但是,浏览器绘制有着自己的特别的方法,因为它甚至可以在不对DOM和/或CSSOM进行任何更改的情况下发生
上图是使用DevTools中的Chrome性能面板生成的(后面会详细介绍),它显示了重新加载页面后,在记录时间(0-7.12秒)内,浏览器中每个任务花了多少时间。正如你所看到的,绘制(painting )占了重要的一部分,而这并不是一件坏事。
在这个特定的例子中,额外的绘画是由页面上的动画GIF和画布绘图(60fps)的组合引起的,其中两者都不会导致DOM或其样式的任何更改,同时仍然触发绘制。
不用外在原因就能触发绘制,除了GIF动画和canvas之后,还有css的动画(animation
)属性,这种情况在浏览器中更加常见。动画通常都是有用户的动作触发的,如:hover的时候,但是由于css3有了animation
和@keyframes
这两个特性,我们可以毫不费力地就制作出复杂炫酷动画。
我们可能会忽略的地方是,这些动画可能会失控然后就一直执行,这很影响计算机的处理能力,造成卡机的状况。当然,有一些准则可以应用于避免绘制的。首先就是限制元素对css属性transform
和opacity
的操作,虽然这些在默认情况下是不绘触发绘制的,但是在元素是一个svg这种特殊情况下还是会的。
绘制闪烁
你可能知道Chrome有DevTools。您可能不知道的是一个快捷键(Mac上的Shift + Cmd + P或win上的Control + Shift + P),可以在DevTools中使用它来调出一个小的搜索栏和命令菜单。
我开始挖掘它(DevTools),除了许多其他有用和令人难以置信的有趣选项外,渲染面板(render panel)引起了我的注意。
乍一看,您可以看到一些有趣的选项,这些选项在调试Web上的动画时非常有用,例如FPS仪表。
图层边框和绘制闪动也是有趣的工具。图层边框用于在浏览器呈现图层边框时显示图层的边框,以便可以轻松识别任何转换或大小变化。绘制闪动用于突出显示浏览器强制重绘的网页区域。
这里有一个展示的视频:Paint flashing
在发现绘制闪动之后,我做的第一件事就是在我的一个项目上查看它。在大多数地方都没有毛病。例如,由网站上的滚动触发的任何移动都由CSS转换属性提供支持,正如我们所述,它不会导致绘制。像人们所期望的一样进行绘制,比如在悬停时文字颜色的变化,但由于它的面积和存在仅在悬停元素时,这不是一个值得关注的问题。总而言之,即使您昨天编写了代码,也总能找到改进的东西……
但有一件事打了我的脸。
无论你是多么有经验或小心,你都可以,而且很可能会犯错误。我们只是普通人,有些人认为修复自己的BUG是开发工作的大部分工作。但是,对于可以修复的BUG,我们需要注意它……而这正是渲染面板起作用的地方。
案例分析
让我们仔细看看实际问题。设计提出了嘈杂背景的要求,要求像当旧电视没有信号时的那种效果。
众所周知,GIF有许多问题,其中性能肯定是其中之一,所以我绝对不能将它用于整个页面背景。如果你想阅读更多关于为什么要避免使用GIF的内容,这里有一个很好的资源
在这种情况下,使用JavaScript无疑是一种方法。显示或隐藏具有轻微移动背景的元素是我想到的第一件事,使用canvas
画布也可以提供帮助。然而,所有这些对于简单地拥有背景似乎有点过分。所以我决定只采用CSS去实现效果。
我的解决方案是将一个小的“noisy”图片作为background-image
,启用background-repeat
并将其投射到单色背景上。那么我是如何实现噪音效果的?用无限的CSS动画!通过在200毫秒的时间段内将background-position
设置为不同的值。结果如下:
codepen
你能猜到这个问题吗?对我来说这似乎是一个非常优雅的解决方案,我很高兴我没有一个糟糕的GIF,甚至没有一行JavaScript的成功。
绘制闪动显示出完全不同的东西。窗口大小的层不断重新绘制,用户甚至没有做任何事情。如果在渲染面板中启用它,您可以在上面的演示中看到绘制闪动(请注意,paint flashing不会显示在嵌入式笔中)。
Without paint flashing (left) vs. with paint flashing (right)
这显然不能很好地发挥网站的性能。
通过使用transform
或opacity
替换对background-position
的更改,可以避免所有这些CPU使用。
codepen示例:https://codepen.io/gmrchk/pen/XYOYGm
问题所在
我已经做了一段时间的web开发,我非常清楚动画化一个背景永远不是一个好主意。这感觉就像一个菜鸟的错误。人们犯错误……但这不是整个故事。这个网站都很迟钝,导航很不舒服。我是怎样忍下来的….
事实上,在开发设备方面,我(并且你可能也是如此)有点被宠坏了。我有一个很好的,功能强大的计算机,可以工作和访问快速的互联网。除非我们编写一些非常糟糕的代码,否则我们编写的任何内容都会在我们眼中非常流畅,但对于我们的用户来说并非总是如此。
类似的问题也适用于许多其他事情 - 例如显示尺寸。稍微夸张的是,当我们开发具有4K分辨率的27“显示器并且主要用于1920x1080的设计时,我们的访客主要来自1366x768笔记本电脑,并且在使用计算机时具有完全不同的工作流程。
结论
虽然这篇文章最初是关于绘制的,但它的主要内容更多的是关注我们的代码对绘制过程或整体表现的影响。显然绘制是一个很好的例子,绘制过程中可能会出现问题而且容易被遗漏,但更多的是开发人员和用户之间的脱节问题。
网络是许多环境的地方,开发人员的环境通常与用户的环境大不相同。虽然没有必要改变我们的方式或转换到懒惰的计算机,但它确实有助于看到我们的工作不时被别人看到。我的建议是:当你下班回家并有一点空闲时间时,试着拿起旧电脑并检查你的工作,以便更接近你的用户体验。
如果你没有这种类型的计算机,像渲染面板这样的工具可以变得非常方便。
原文:Browser painting and considerations for web performanc— GEORGY MARCHUK(2018-08-08)