文中多处涉及浏览器重绘和性能优化的原理,也是《Web滚动性能优化实战》的拓展和延续,难度上属于中级进阶,请在阅读前请先看看这篇文章。 如果你还不了解它们,它们其实就是页面的视觉结构随滚动变化的站点。正常情况下,页面上的元素按比例缩放、旋转或者移动到滚动的位置。
我们视差效果的演示页面
你喜不喜欢视差网站是一回事,但是我们能确定的是这绝对是一个性能黑洞。原因是当你滚动时,浏览器会试图对新内容出现的地方(根据滚动的方向)进行性能优 化,总的来讲,在滚动中视觉上越少更新浏览器性能越好。对于视差网站来说这很少见,因为在整个页面上大的视觉元素会多次发生改变,从而导致浏览器必须对整 个页面进行重绘(为什么是性能黑洞,可以参考我的这篇文章《Web滚动性能优化实战》)。
把视差网站归纳为下面的特性是合理的:
1、 当你向上或者向下滚动页面时,背景元素改变位置、旋转或者缩放。
2、 页面内容,例如文本或者小图片,以特别的从上到下的方式滚动。
我们之前介绍过滚动性能及其优化方式,你可以以此来改进应用的响应能力。本文将建立在此基础上,所以你需要先读一下上面这篇文章。
所以现在的问题是,如果你正在构建一个视差滚动网站,是必须要进行代价昂贵的重绘,还是有其它方法可以采用来最大限度的提高性能?让我们来看看可供选择的方法。
方法1:使用DOM元素和绝对定位
这可能是大多数人选择的方式。页面里有许多元素,当滚动事件触发时,许多视觉上的更新会发生在这些元素上。这里我展示了一个演示页面。
如果你开启了开发者工具时间轴的frame模式,并且上下滚动,你会注意到有代价昂贵的全屏绘制操作。如果你滚动多次,你也许可以在一个单独的帧里看到多个滚动事件,每一个都会触发布局工作。
开发者工具展示了一帧里有大量的绘制操作和多个由事件触发的布局
重要的是要牢记,为了达到60fps(与典型的显示器刷新率60Hz相匹配),我们必须要在差不多16ms内完成所有事情。在这第一个版本中,我们每当得 到一个滚动事件,我们就要执行一次视觉更新,但是正如我们在前面的文章-《用requestAnimationFrame实现更简单动画》和《Web滚动 性能优化实战》里讨论到的一样,这与浏览器的更新节奏并不一致。所以我们要么错过帧,要么在一帧里完成太多的工作。这会让你的站点很容易看起来不舒服和不 自然,导致用户感觉失望。
让我们把视觉更新的代码从滚动事件中移到requestAnimationFrame回调里,并且在滚动事件的回调里简单的获取滚动的值。我们在第二个演示中展示了这个变化。
如果你重复滚动测试,你可能会注意到有轻微的改善,虽然并不多。原因是由滚动触发的布局操作代价昂贵,而现在我们只在每帧中执行一次布局操作。
开发者工具展示了一帧里有大量的绘制操作和多个由事件触发的布局
我们现在在每帧里可以处理一个或者上百个滚动事件,但最重要的是,我们仅仅存储最近的一个滚动值,供requestAnimationFrame回调触发时使用,并执行视觉上的更新。关键是我们已经从每次接收到滚动事件时进行视觉更新优化为在浏览器给我们的合适时机进行处理。你是不是觉得这相当给力?
这个方法的主要问题是,无论使用requestAnimationFrame与否,我们基本上都会生成整个页面的层,在移动这些视觉元素时需要大量和代价 昂贵的重绘。通常重绘会是一个阻塞操作(虽然这点将会优化),这意味着浏览器不能同时进行其它工作,而我们经常有可能超过浏览器16ms的帧的处理时限, 这代表会出现性能上卡顿的情况。
方法2:使用DOM元素和3D转换
除了绝对定位之外,另外一种我们可以采用的方法就是3D转换(transform)。在这种情况下我们可以看到每个用3D转换处理的元素都会产生新的层。相比之下,在方法1中,如果有任何变化时,我们必须要重绘页面上一大部分的层。
这意味着使用此方法情况会大为不同:我们可能对应用了3D转换的任何元素都会有一个层。如果通过更多元素的转换做到这一点,我们不需要重绘任何层,GPU 能够处理移动元素和合成整个页面。也许你想知道为什么用3D转换替代3D,原因是2D转换不能保证得到一个新的层,而3D转换可以。
这是另一个使用了3D转换的演示。滚动时你可以看到性能已经大有改观。
很多时候人们使用-webkit-transform:translateZ(0)这个技巧,能够看到有奇妙的性能改善(宇捷注:关于这种方式,其实就是 利用3D转换来开启浏览器硬件加速,属于一种Hack。国内很少有资料提及,而国外有很多移动App开发性能优化的文章提到。国内可以看看《改善 HTML5网页性能》,国外可以看看《IncreasingPerformance of HTML and JavaScript on Mobile Devices》)。这种方式现在可以正常工作,但是会带来一些问题:
1、 它并不是浏览器兼容的;
2、 它强迫浏览器为每一个转换的元素创建新的层。大量的层会带来其它性能瓶颈,所以需要有节制的使用。
3、 它在某些Webkit版本的移植上被禁用。
所以,你如果采用这种方法需要非常谨慎,这对解决问题来说是一个临时方案。在完美的情况下我们甚至都不会考虑它,而且浏览器每天都在改进中,谁知道也许哪天我们就不需要它了。
方法3:使用固定定位(Fixed Position)的Canvas或者WebGL
我们最后要考虑的方法就是在页面上采用固定定位的Canvas,而把转换的图像绘制在上面。乍看之下,这可能不是最高效的解决方案,但是它有几个好处:
我们不再需要大量合成工作,因为页面只有一个元素 - Canvas;
我们可以高效的通过硬件加速处理一个单独的bitmap;
Canvas2D API非常适合我们要执行的转换类型,这意味着开发和维护更容易管理。
使用Canvas元素为我们提供了一个新的层,但是它只有一层,而在方法2中我们为每一个应用3D转换的元素都创建了一个新层,所以有额外的工作量来把这些层合成在一起。
发表回复