本文介绍了一种时尚的网站设计方法,以及如何由浅入深的通过HTML5和浏览器渲染机制来构建高性能的站点。
文中多处涉及浏览器重绘和性能优化的原理,也是《[Web滚动性能优化实战](http://blog.csdn.net/hfahe/article/details/8539579)》的拓展和延续,难度上属于中级进阶,请在阅读前请先看看这篇文章。
**介绍**
视差网站最近风靡一时,只需看看下面这些站点:
- [Old Pulteney Row to the Pole](http://www.rowtothepole.com/)
- [Adidas Snowboarding](http://www.adidas.com/com/apps/snowboarding/)
- [BBC News - James Bond: Cars, catchphrases and kisses](http://www.bbc.co.uk/news/entertainment-arts-20026367)
如果你还不了解它们,它们其实就是页面的视觉结构随滚动变化的站点。正常情况下,页面上的元素按比例缩放、旋转或者移动到滚动的位置。
![](https://box.kancloud.cn/2016-08-09_57a9aa587fe96.jpg)
我们视差效果的演示页面
你喜不喜欢视差网站是一回事,但是我们能确定的是这绝对是一个性能黑洞。原因是当你滚动时,浏览器会试图对新内容出现的地方(根据滚动的方向)进行性能优化,总的来讲,在滚动中视觉上越少更新浏览器性能越好。对于视差网站来说这很少见,因为在整个页面上大的视觉元素会多次发生改变,从而导致浏览器必须对整个页面进行重绘(为什么是性能黑洞,可以参考我的这篇文章《[Web滚动性能优化实战](http://blog.csdn.net/hfahe/article/details/8539579)》)。
把视差网站归纳为下面的特性是合理的:
1、 当你向上或者向下滚动页面时,背景元素改变位置、旋转或者缩放。
2、 页面内容,例如文本或者小图片,以特别的从上到下的方式滚动。
我们之前介绍过[滚动性能](http://blog.csdn.net/hfahe/article/details/8539579)及其优化方式,你可以以此来改进应用的响应能力。本文将建立在此基础上,所以你需要先读一下上面这篇文章。
所以现在的问题是,如果你正在构建一个视差滚动网站,是必须要进行代价昂贵的重绘,还是有其它方法可以采用来最大限度的提高性能?让我们来看看可供选择的方法。
**方法1:使用DOM元素和绝对定位**
这可能是大多数人选择的方式。页面里有许多元素,当滚动事件触发时,许多视觉上的更新会发生在这些元素上。这里我展示了一个[演示页面](http://www.html5rocks.com/static/demos/parallax/demo-1a/demo.html)。
如果你开启了开发者工具时间轴的frame模式,并且上下滚动,你会注意到有代价昂贵的全屏绘制操作。如果你滚动多次,你也许可以在一个单独的帧里看到多个滚动事件,每一个都会触发布局工作。
![](https://box.kancloud.cn/2016-08-09_57a9aa58956a6.jpg)
开发者工具展示了一帧里有大量的绘制操作和多个由事件触发的布局
重要的是要牢记,为了达到60fps(与典型的显示器刷新率60Hz相匹配),我们必须要在差不多16ms内完成所有事情。在这第一个版本中,我们每当得到一个滚动事件,我们就要执行一次视觉更新,但是正如我们在前面的文章-《[用requestAnimationFrame实现更简单动画](http://www.html5rocks.com/en/tutorials/speed/animations/)》和《[Web滚动性能优化实战](http://www.html5rocks.com/en/tutorials/speed/scrolling/)》里讨论到的一样,这与浏览器的更新节奏并不一致。所以我们要么错过帧,要么在一帧里完成太多的工作。这会让你的站点很容易看起来不舒服和不自然,导致用户感觉失望。
让我们把视觉更新的代码从滚动事件中移到requestAnimationFrame回调里,并且在滚动事件的回调里简单的获取滚动的值。我们在[第二个演示](http://www.html5rocks.com/static/demos/parallax/demo-1b/demo.html)中展示了这个变化。
如果你重复滚动测试,你可能会注意到有轻微的改善,虽然并不多。原因是由滚动触发的布局操作代价昂贵,而现在我们只在每帧中执行一次布局操作。
![](https://box.kancloud.cn/2016-08-09_57a9aa58aaa64.jpg)
开发者工具展示了一帧里有大量的绘制操作和多个由事件触发的布局
我们现在在每帧里可以处理一个或者上百个滚动事件,但最重要的是,我们仅仅存储最近的一个滚动值,供requestAnimationFrame回调触发时使用,并执行视觉上的更新。关键是我们已经从每次接收到滚动事件时进行视觉更新优化为在浏览器给我们的合适时机进行处理。你是不是觉得这相当给力?
这个方法的主要问题是,无论使用requestAnimationFrame与否,我们基本上都会生成整个页面的层,在移动这些视觉元素时需要大量和代价昂贵的重绘。通常重绘会是一个阻塞操作(虽然这点将会[优化](http://www.chromium.org/developers/design-documents/impl-side-painting)),这意味着浏览器不能同时进行其它工作,而我们经常有可能超过浏览器16ms的帧的处理时限,这代表会出现性能上卡顿的情况。
**方法2:使用DOM元素和3D转换**
除了绝对定位之外,另外一种我们可以采用的方法就是3D转换(transform)。在这种情况下我们可以看到每个用3D转换处理的元素都会产生新的层。相比之下,在方法1中,如果有任何变化时,我们必须要重绘页面上一大部分的层。
这意味着使用此方法情况会大为不同:我们可能对应用了3D转换的任何元素都会有一个层。如果通过更多元素的转换做到这一点,我们不需要重绘任何层,GPU能够处理移动元素和合成整个页面。也许你想知道为什么用3D转换替代3D,原因是2D转换不能保证得到一个新的层,而3D转换可以。
这是另一个使用了3D转换的[演示](http://www.html5rocks.com/static/demos/parallax/demo-2/demo.html)。滚动时你可以看到性能已经大有改观。
很多时候人们使用-webkit-transform:translateZ(0)这个技巧,能够看到有奇妙的性能改善(宇捷注:关于这种方式,其实就是利用3D转换来开启浏览器硬件加速,属于一种Hack。国内很少有资料提及,而国外有很多移动App开发性能优化的文章提到。国内可以看看《[改善HTML5网页性能](http://www.doc88.com/p-307127263294.html)》,国外可以看看《[IncreasingPerformance of HTML and JavaScript on Mobile Devices](http://the.ichibod.com/kiji/increasing-performance-of-html-and-javascript-on-mobile-devices-especially-ios/)》)。这种方式现在可以正常工作,但是会带来一些问题:
1、 它并不是浏览器兼容的;
2、 它强迫浏览器为每一个转换的元素创建新的层。大量的层会带来其它性能瓶颈,所以需要有节制的使用。
3、 它在某些[Webkit版本的移植](http://developer.apple.com/library/ios/#releasenotes/General/RN-iOSSDK-6_0/_index.html)上被禁用。
所以,你如果采用这种方法需要非常谨慎,这对解决问题来说是一个临时方案。在完美的情况下我们甚至都不会考虑它,而且浏览器每天都在改进中,谁知道也许哪天我们就不需要它了。
**方法3:使用固定定位(Fixed Position)的Canvas或者WebGL**
我们最后要考虑的方法就是在页面上采用固定定位的Canvas,而把转换的图像绘制在上面。乍看之下,这可能不是最高效的解决方案,但是它有几个好处:
- 我们不再需要大量合成工作,因为页面只有一个元素 - Canvas;
- 我们可以高效的通过硬件加速处理一个单独的bitmap;
- Canvas2D API非常适合我们要执行的转换类型,这意味着开发和维护更容易管理。
使用Canvas元素为我们提供了一个新的层,但是它只有一层,而在方法2中我们为每一个应用3D转换的元素都创建了一个新层,所以有额外的工作量来把这些层合成在一起。
如果你看看这种方法的[演示](http://www.html5rocks.com/static/demos/parallax/demo-3/demo.html),并且在开发者工具中观察,你会发现它的性能更加优异。在这个方法里,我们只需在Canvas上调用drawImage API、设置背景图像,以及每一个要在屏幕上正确位置绘制的色块。
~~~
/**
* Updates and draws in the underlying visual elements to the canvas.
*/
function updateElements () {
var relativeY = lastScrollY / h;
// Fill the canvas up
context.fillStyle = "#1e2124";
context.fillRect(0, 0, canvas.width, canvas.height);
// Draw the background
context.drawImage(bg, 0, pos(0, -3600, relativeY, 0));
// Draw each of the blobs in turn
context.drawImage(blob1, 484, pos(254, -4400, relativeY, 0));
context.drawImage(blob2, 84, pos(954, -5400, relativeY, 0));
context.drawImage(blob3, 584, pos(1054, -3900, relativeY, 0));
context.drawImage(blob4, 44, pos(1400, -6900, relativeY, 0));
context.drawImage(blob5, -40, pos(1730, -5900, relativeY, 0));
context.drawImage(blob6, 325, pos(2860, -7900, relativeY, 0));
context.drawImage(blob7, 725, pos(2550, -4900, relativeY, 0));
context.drawImage(blob8, 570, pos(2300, -3700, relativeY, 0));
context.drawImage(blob9, 640, pos(3700, -9000, relativeY, 0));
// Allow another rAF call to be scheduled
ticking = false;
}
/**
* Calculates a relative disposition given the page’s scroll
* range normalized from 0 to 1
* @param {number} base The starting value.
* @param {number} range The amount of pixels it can move.
* @param {number} relY The normalized scroll value.
* @param {number} offset A base normalized value from which to start the scroll behavior.
* @returns {number} The updated position value.
*/
function pos(base, range, relY, offset) {
return base + limit(0, 1, relY - offset) * range;
}
/**
* Clamps a number to a range.
* @param {number} min The minimum value.
* @param {number} max The maximum value.
* @param {number} value The value to limit.
* @returns {number} The clamped value.
*/
function limit(min, max, value) {
return Math.max(min, Math.min(max, value));
}
~~~
这种做法真的在处理大图片(或者其它很容易写到一个Canvas上的元素)或者大块的文本时肯定根据挑战性。但是在你的网站上,它可能被证明会是最合适的解决方案。如果你不得不在Canvas上处理文本,你也许要使用fillText API,但是它有访问成本(你刚刚才把文本转换为bitmap!)而且你需要处理文本换行以及其它问题。你需要尽量避免这么做。
讨论了这么多,我们没有理由假设视差的工作就一定要用Canvas元素。如果浏览器支持,我们可以使用WebGL。这里面的关键是WebGL是所有API到显卡最直接的方式,并且在你的站点效果很复杂的情况下性能是最有可能达到60fps的。
你最直接的反应可能是觉得采用WebGL矫枉过正,或者它并没有获得广泛支持,但是如果你如果使用了类似于[Three.js](https://github.com/mrdoob/three.js/)的库,你可以随时回退为使用Canvas元素,同时你的代码能以一种一致和友好的方式进行抽象。我们需要做的只是用[Modernizr](http://modernizr.com/)来检测相应API的支持:
~~~
// check for WebGL support, otherwise switch to canvas
if (Modernizr.webgl) {
renderer = new THREE.WebGLRenderer();
} else if (Modernizr.canvas) {
renderer = new THREE.CanvasRenderer();
}
~~~
然后使用Three.js的API,而不是自己处理上下文。这里有一个支持两种渲染方式的[演示](http://www.html5rocks.com/static/demos/parallax/demo-4/demo.html)。
这种方法的最后一个问题是,如果你并不特别爱好在页面上添加额外的元素,你可以总是在Firefox和Webkit浏览器里[使用canvas作为背景元素](http://updates.html5rocks.com/2012/12/Canvas-driven-background-images)。很明显,这并不是普遍适用的,所以你应该对此持谨慎态度。
**逐步退化**
开发者默认采用绝对定位元素而不是其它方法的主要原因可能仅仅简单是浏览器支持的问题。这种方式在一定程度上是错误的,因为对于老旧的浏览器来说,只能提供非常贫乏的渲染体验。即便在现代浏览器中,使用绝对定位也不一定能带来好的性能。
更好的方案是在老旧的浏览器上避免尝试视差效果,仅在最好的浏览器上确保能够用正确的API呈现站点效果。当然,如果你使用了[Three.js](https://github.com/mrdoob/three.js/),你应该能够很容易根据所需要的支持在渲染器之间进行切换。
**结论**
我们评估了几种方式来处理大量重绘的区域,从绝对定位的元素到使用固定定位的Canvas。当然你要采用的实现方式,取决于你要达到的目标和具体设计,但是知道有多种选择是一件好事情。在本文的例子中,我们设法从相对卡顿、低于30fps优化到了平滑、60fps的效果。
与往常一样,无论尝试哪种方法,不要凭借猜测,而应该亲自动手去尝试。
像往常一样,转载请注明:来自蒋宇捷的博客(http://blog.csdn.net/hfahe)
译自:[http://www.html5rocks.com/en/tutorials/speed/parallax/](http://www.html5rocks.com/en/tutorials/speed/parallax/)
相关文章:《[Web滚动性能优化实战](http://blog.csdn.net/hfahe/article/details/8539579)》
- 前言
- AutoPager的简单实现
- 利用CSS3特性巧妙实现漂亮的DIV箭头
- IE9在Win7下任务栏新特性简介
- 浏览器九宫格的简单实现
- Raphael js库简介
- 使用CSS3构建Ajax加载动画
- 用CSS3创建动画价格表
- 用CSS3实现浏览器的缩放功能
- 用纯CSS3实现QQ LOGO
- 用CSS3创建旋转载入器
- 使用Javascript开发移动应用程序
- 用HTML5创建超酷图像灰度渐变效果
- 使用CSS3创建文字颜色渐变(CSS3 Text Gradient)
- 仅用CSS创建立体旋转幻灯片
- 如何创建跨浏览器的HTML5表单
- 用CSS3实现动画进度条
- HTML5 Guitar Tab Player
- 奇妙的HTML5 Canvas动画实例
- 谈HTML5和CSS3的国际化支持
- 实现跨浏览器的HTML5占位符
- 前端开发必备工具:WhatFont Bookmarklet-方便的查询网页上的字体
- 使用HTML5和CSS3来创建幻灯片
- HTML5之美
- 如何使用HTML5创建在线精美简历
- 以小见大、由浅入深-谈如何面试Javascript工程师
- 快速入门:HTML5强大的Details元素
- 用CSS3实现图像风格
- HTML5视频字幕与WebVTT
- 用纯CSS3实现Path华丽动画
- 用3个步骤实现响应式网页设计
- 遇见CSS3滤镜
- 关于CSS3滤镜的碎念
- 用纯CSS3绘制萌系漫画人物动态头像
- CSS3新的鼠标样式介绍
- 用HTML5献上爱的3D玫瑰
- 对HTML5 Device API相关规范的解惑
- 如何使用HTML5实现拍照上传应用
- 2012第一季度国外HTML5移动开发趋势
- HTML5新特性:范围样式
- 百度开发者大会-《用HTML5新特性开发移动App》PPT分享
- Chrome 19对于HTML5最新支持的动态:电池状态API,全屏API,震动API,语音API
- 遇见Javascript类型数组(Typed Array)
- 用HTML5 Audio API开发游戏音乐
- 用HTML5实现人脸识别
- 用Javascript实现人脸美容
- Chrome 20对于HTML5最新支持的动态:颜色输入,网络信息API,CSS着色器
- 用HTML5实现手机摇一摇的功能
- 用HTML5实现iPad应用无限平滑滚动
- 用非响应式设计构建跨端Web App
- 了解SVG
- HTML5图像适配介绍
- HTML5安全:内容安全策略(CSP)简介
- HTML5安全:CORS(跨域资源共享)简介
- 用CSS3 Region和3D变换实现书籍翻页效果
- 谈谈移动App的思维误区
- Chrome新特性:文件夹拖拽支持
- 《关注HTML5安全》
- HTML5安全风险详析之一:CORS攻击
- HTML5安全风险详析之二:Web Storage攻击
- HTML5图像适配最新进展:响应式图片规范草案
- HTML5移动Web App相关标准状态及路线图
- HTML5安全风险详析之三:WebSQL攻击
- Chrome引入WebRTC支持视频聊天App
- HTML5安全风险详析之四:Web Worker攻击
- HTML5安全风险详析之五:劫持攻击
- HTML5安全风险详析之六:API攻击
- HTML5安全攻防详析之七:新标签攻击
- 在iOS Safari中播放离线音频
- 使用WebRTC实现远程屏幕共享
- Firefox、Android、iOS遇见WebRTC
- HTML5光线传感器简介
- HTML5安全攻防详析之八:Web Socket攻击
- HTML5安全攻防详析之完结篇:HTML5对安全的改进
- 激动人心!在网页上通过语音输入文字 - HTML5 Web Speech API介绍
- Web滚动性能优化实战
- 用CSS3设计响应式导航菜单
- 用HTML5构建高性能视差网站
- 漫谈@supports与CSS3条件规则
- HTML5下载属性简介
- 如何开发优秀的HTML5游戏?-迪斯尼《寻找奥兹之路》游戏技术详解(一)
- 如何开发优秀的HTML5游戏?-迪斯尼《寻找奥兹之路》游戏技术详解(二)
- 趋势:Chrome为打包应用提供强大新特性
- 从HTML5移动应用现状谈发展趋势
- 基于HTML5的Web跨设备超声波通信方案