Web滚动性能优化实战

最后更新于:2022-04-01 19:44:58

### 我的前言       HTML5的API体系是重要知识,但是如何写出更高效的Web App对于从程序员进阶为高级程序员来说更为重要。技很重要,但是容易学会,术才是茫茫人海中鹤立鸡群,安生立命之本。       码农们容易吗?是的,我们必须要不断努力和学习才能进化为高级码农乃至顶级码农。       Web App的性能优化非常重要,之前我有过一篇LinkedIn的相关文章《[用HTML5实现iPad应用无限平滑滚动](http://blog.csdn.net/hfahe/article/details/7535914)》。       本文从另外一方面揭示了浏览器的渲染原理,就像开发Windows客户端要理解操作系统原理一样,有时候,性能优化不仅仅是了解HTML、JavaScript、CSS,浏览器的实现是墙外的另一角。       本文中所讲述的原理可以延伸应用到很多平台,例如如何保证Android应用屏幕滑动时的流畅性?有许多需要注意的点,例如不要在主线程中进行耗时的计算、不要采用过大过多的背景图片等等。       其实这个事情我们完全可以写一本书来详细阐述,也许。 ……………………………………✄…………………………………… ### 目录 - 介绍 - 滚动的原理 - 绘制分析 - 图像缩放 - 代价昂贵的样式 - Reflow与重绘 - 滚动事件防抖 - 结论 - 扩展阅读 ### 介绍       滚动的性能优化看起来不是一件很有必要的事情。毕竟,内容已有样式,所有资源已经或正在加载,我们为什么要关心滚动时会发生些什么呢?其实原因很简单,只要你滚动,浏览器就会把你的网站或者App绘制到屏幕上。这意味着我们有机会尽量减少浏览器的工作来最大限度地提高页面性能。       当用户使用你的应用时,平滑滚动实际上是用户体验里经常被忽视的重要组成部分。当滚动体验流畅时,使用这个应用会感觉到如丝般光滑和愉悦的体验,而卡顿则让人难以忍受。 ### 滚动的原理       让我们来看看在滚动时有什么事情发生。要理解这点,我们必须简单介绍一下浏览器如何在屏幕上绘制内容。一切都始于DOM树,它实际上是页面内的所有元素。浏览器查看带样式的DOM树,找出滚动时它认为看起来是一样的元素。然后,它把这些元素组合在一起,并且为它们生成一张图片,这就是所谓的层。每一层需要被绘制和栅格化为一种结构,然后合成在一起,这就是你在屏幕上看到的图像。       如果你有对Chrome渲染的详细信息感兴趣,可以看看这篇[概要](http://www.chromium.org/developers/design-documents/gpu-accelerated-compositing-in-chrome)(宇捷:在国内可以看看[这篇](http://www.docin.com/p-437050924.html))。       当你滚动页面时,浏览器可能会需要绘制这些层(有时也被称为合成层)里的一些像素。通过元素分组,当某个层的内容改变时,我们只需要更新该层的结构,并仅仅重绘和栅格化渲染层结构里变化的那一部分,而无需完全重绘。显然,如果当你滚动时,像视差网站(宇捷:关于视差网站,可以看看[这篇文章](http://www.html5rocks.com/en/tutorials/speed/parallax/),以及[示例网站1](http://www.rowtothepole.com/)、[示例网站2](http://www.adidas.com/com/apps/snowboarding/)、[示例网站3](http://www.bbc.co.uk/news/entertainment-arts-20026367),稍后我可能会介绍这种设计方式)这样有东西在移动时,有可能在多层导致大面积的内容调整,这会导致大量的绘制工作。       总之,**少绘最佳**。 ### 绘制分析       理论说了这么多,让我们来看看实践中如何工作。你可以用Chrome浏览器的DevTools来看看这个[演示页面](http://www.html5rocks.com/static/demos/scrolling/demo.html)。如果你打开​​时间轴面板(Timeline panel),设置为帧模式(frame mode),点击纪录按钮(record button),然后开始滚动,你可以看到一堆绿色的条。我录制了一段[视频](http://www.youtube.com/embed/KCtOt9OXvAM?rel=0)来展示你应该做什么和可以看到什么(宇捷:甚至在切换Tab时你也可以看见图形在变化)。       在时间线下方的列表中,你会看到全部绘制记录。这是Chrome绘制和栅格化页面合成层结构的过程。(所有绘制完成后,你还可以看到一些合成层的记录,这是所有这些已经更新的层为将在屏幕上显示进行合成准备) ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-09_57a9aa57eb5e3.jpg) Chrome DevTools里的绘制记录       在每一个绘制记录后,你可以看到绘制区域的尺寸,如果你滚动页面,Chrome将突出显示页面中需要重绘的区域。另一个查看重绘区域的方法是,通过右下角的小齿轮图标进入DevTools设置,在里面激活 “显示绘制矩形(show paint rectangles)”选项。这是我们得到的滚动时页面性能的第一个指标。提醒一下:你应该寻找最小的可能绘制区域。       在页面左侧菜单可见的情况下,绘制区域基本上是整个屏幕,性能会受到很大影响。原因是这时Chrome创建了多个需调整的区域。在这种情况下,整个横向的可视内容会进入视图,还有100%高的左侧菜单也会进入同一个合成层,所有这一切会合成为一个100%高、100%宽的区域。如果你使用页面上方的勾选框禁用侧边栏菜单,再次滚动,你会发现此时只需要重绘横向的细条,这会快上很多。       如果你想看看实际的例子,试试打开[Google+](https://plus.google.com/),然后把侧边栏导航的样式从position: fixed修改为position: absolute。当然这会改变站点的行为,但是关键是通过切换风格,你可以看到真正性能的提升。你对此的反应可能是考虑以后应不应该使用position:fixed,但实际上所有这一切都依赖于环境和需求。最重要的是要能够测量和了解你的决定所带来的影响。 ### 图像缩放       在基本的绘制记录外我们还得到了一些信息,尤其是和图像相关。 如果在记录时缩放页面,你会看到一些绘制记录允许展开来查看更多信息。这么做之后,你应该可以看到一些图像缩放记录。这正是它所揭示的:浏览器需要把缩放图像作为绘制工作的一部分。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-09_57a9aa5811406.jpg) Chrome DevTools的图像缩放记录       如果用CSS或者图像尺寸属性缩放了一张大图,你就更有可能看到这样的情况。 当然,浏览器重新缩放图像的次数,以及它需要这样做的频率,会影响页面的性能,因为它们发生在浏览器主线程,由此会阻塞其它任务。       因此,使用无需缩放的图像是非常重要的。可以从一个有趣的侧面说明这一点,缩放操作很多情况下是不可避免的,尤其是在移动设备上。在这种情况下你没有太多可以做的事情,但是你最好知道这会(并且极有可能)发生。有利的一面是,浏览器总是在改进中,尤其渲染更是一个热门的话题,所以你可以期望事情在未来的几个月内会变得更好。       因此综上所述,还有其它可能会影响滚动性能的因素: - 代价昂贵的样式 - Reflow与重绘 - 滚动事件去抖       让我们来详细谈谈每一点。 ### 代价昂贵的样式       首先要说的是,并非所有的样式都是一样的,一些效果例如盒阴影(box-shadow)和oft-quoted代价特别昂贵。原因很简单,因为它们相关的绘制代码比其它样式需要更长的时间来运行。这意味着,如果你使用了这些样式,而且需要经常重绘,你很可能遇到性能问题。第二点要说的是一切都会变化,所以现在速度慢的样式可以被优化,以后变得很快,并且浏览器间还有很大不同。这里的关键是像上面一样利用Chrome的开发者工具来确定其中的瓶颈来自哪里,然后看看我们如何可以减少浏览器的负载。       2012年[谷歌I / O大会上](https://developers.google.com/events/io/)Nat Duca和Tom Wiltzius做了一场关于Chrome如何进行渲染优化的[精彩演讲](http://www.youtube.com/embed/hAzhayTnhEI?rel=0),其中提到了一大堆宝贵的经验,包括代价昂贵的样式以及如何衡量其影响。 ### Reflow与重绘       当用JavaScript请求一个元素的`offsetTop`属性时,你相当于马上给浏览器分配了大量的工作,因为它需要去进行页面布局来计算正确答案。这个过程被称为Reflow。如果我们基于offsetTop值再改变了此元素的其它属性,它将需要重新绘制(会影响到合成层),这个代价可能非常昂贵。它还具有连锁效应,重绘会导致`offsetTop`计算失效,因为该元素发生了变化。       真正的问题来自于元素集合。如果我们在重绘后马上计算每个元素的位置,我们会迫使浏览器进入一个代价昂贵的、完全不必要的Reflow-重绘循环。在这种情况下,我们需要做的是分两次来限制避免这个循环:第一次我们简单的获取`offsetTop`值,而在第二次里进行视觉更新。通过这种方式,我们避免了需要反复重新计算元素在页面中的位置,并且假定我们使用`requestAnimationFrame` ,我们将为浏览器安排在最佳时机进行视觉更新。       值得一提的是除了offsetTop之外还有[其它操作](http://gent.ilcore.com/2011/03/how-not-to-trigger-layout-in-webkit.html)会导致Reflow操作发生,所以必须要小心检查所有相关内容。 ### 滚动事件防抖       如果你正在构建一个大的视差滚动(`Parallax Scrolling`)网站,你自然倾向于在触发滚动事件时进行视觉更新。这里的主要问题是,滚动事件没有限制浏览器视觉更新的时间,比如在一个`requestAnimationFrame`回调里。所以在一个独立的渲染帧里会存在执行多个更新的风险。如果视觉更新代价巨大,而你正在开发一个视差滚动类似的网站(有大量区域调整,大量绘制和合成),频繁而不是仅必需的视觉更新无疑是一个坏主意。要解决这个问题,你需要将滚动事件进行防抖处理。你只需要在收到滚动事件时简单的存储最后一个滚动值,然后在`requestAnimationFrame里`执行视觉更新时,使用这个值。这意味着浏览器可以在正确的时刻安排视觉更新,我们在每帧中仅完成绝对必要的工作。       我们在[前面的文章](http://www.html5rocks.com/en/tutorials/speed/animations/)讨论过Reflow-重绘循环,如果你想了解更多时可以看看。 ### 结论       现在你知道如何使用Chrome DevTools的时间轴来分析应用的滚动性能了。值得重申的是,性能特性总是在改变,因此有些方式目前速度比其它更快,但这会随时间而改变。关键是理解如何认识上面读到的内容,这样你可以相应调整你的实现方式。       你应该经常使用开发者工具检查**实际的**性能数据。靠眼睛来判断可能很简单,但是结果没那么直接。所以,不要去揣测它,而应该去测试它。 ### 扩展阅读       如果你对浏览器实现感兴趣,或者想了解更多技巧来避免渲染性能的瓶颈,可以看看下面这些文章。 1. [Tali Garsiel & Paul Irish on how browsers work](http://www.html5rocks.com/en/tutorials/internals/howbrowserswork/):浏览器如何工作 1. [A summary of hardware compositing in Chrome](http://www.chromium.org/developers/design-documents/gpu-accelerated-compositing-in-chrome):Chrome硬件合成概要 1. [Paul Lewis on debouncing scroll events and reflow / repaint cycles](http://www.html5rocks.com/en/tutorials/speed/animations/):滚动时间防抖和Reflow-重绘循环 1. [Tom Wiltzius on jank busting for better rendering performance](http://www.html5rocks.com/en/tutorials/speed/rendering/):更好渲染的性能       此网页内容的授权是[知识共享许可3.0](http://creativecommons.org/licenses/by/3.0/)协议,代码授权是[Apache 2.0协议](http://www.apache.org/licenses/LICENSE-2.0)。       译自:[HTML5Rocks](http://www.html5rocks.com/en/tutorials/speed/scrolling/)译者:[蒋宇捷](http://blog.csdn.net/hfahe)转载请注明
';