原文地址: developers.google.com/web/updates…
原文做者: Paul Lewis
译文做者: 接灰的电子产品javascript
爱也好,恨也好,视差效果已经遍及web之上了。当你用的巧妙的时候,它能够给应用增长深度和隐喻效果。但问题在于实现一个高性能的视差效果是一个颇有挑战的工做。在这篇文章里,咱们将讨论如何构造一个高性能的视差效果,固然一样重要的是还得跨浏览器。css
position: sticky
来确保视差能够生效。若是你想要一个开箱即用的方案,请访问 Parallax helper JS ,这里还有一个 demo演示。html
在开始以前,咱们先来看两个现有的常见的实现视差的方法,探讨为什么它们不适合咱们要追求的目标。java
视差的关键需求是它应该是滚动耦合的:对于页面滚动的每个位置变化,视差元素的位置也应被更新。这看上去很容易,现代浏览器的重要机制之一就是它们能够异步处理工做。尽管如此,在大多数浏览器中,滚动事件是被做为“尽可能好”(best effort)的工做处理的,也就意味着:浏览器并不确保每一帧的滚动动画送达!git
这个重要的信息告诉咱们为何要避免使用Javascript基于滚动事件去移动元素:Javascript并不能确保视差会和页面滚动保持一样的步调。。在一些老版本的Mobile Safari上,滚动事件甚至是在滚动完成后才触发的,这一点让基于Javascript的滚动效果没法实现。在较新的Mobile Safari版本中,滚动事件能够在动画过程当中触发了,可是和Chrome同样,它是一个基于“尽可能好”的原则的。因此当主线程忙于其余工做时,滚动事件不会当即触发,也就意味着视差效果的丢失。github
咱们要避免的另外一个场景是在每帧都进行绘制。不少方案试图采用改变 background-position
来提供视差效果, 但这会让浏览器在滚动时重绘受影响的部分,而这可能会是至关消耗资源的,这种影响会使动画卡顿。web
若是咱们想提供一个高质量的视差动画,咱们想要的是一个能够看成加速的属性(这里咱们指的是 transform
和 opacity
),而这是不依赖于滚动事件的。chrome
Scott Kellum 和 Keith Clark 都已经在利用 CSS 3D 来实现视差效果领域作出了很重要的工做。他们采用的很是有效技术有:浏览器
overflow-y: scroll
使其能够滚动(同时可能须要 overflow-x: hidden
)。perspective
值,而后设置 perspective-origin
到 top left
, 或者 0 0
。这种方案的 CSS 看起来是下面的样子:异步
.container {
width: 100%;
height: 100%;
overflow-x: hidden;
overflow-y: scroll;
perspective: 1px;
perspective-origin: 0 0;
}
.parallax-child {
transform-origin: 0 0;
transform: translateZ(-2px) scale(3);
}复制代码
固然咱们假定你的 HTML 是下面的样子:
<div class="container”> <div class="parallax-child”></div>
</div>复制代码
把子元素挤回来会要求它设置一个更小的相对于 perspective
的比例。你能够经过下列等式来计算须要的缩放比例: (perspective - distance) / perspective。因为咱们但愿视差元素看上去和咱们一开始设定的同样大,因此它应该根据这样的等式进行放缩而不是保持不变。
拿上面的例子来讲, perspective
是 1px
, 而 parallax-child
在 Z 轴方向是 -2px
,这就意味着元素须要被放大3倍,你能够看到咱们在 scale
中写入了 3
这个值。
对于任何没有应用 translateZ
的内容,你能够用 0 替代,也就是缩放比为 (perspective - 0) / perspective
,结果为 1 ,即既不放大也不缩小。真的是很是方便。
弄清楚为何这种方案好用是很是重要的,由于咱们很快就要使用这个知识了。滚动其实本质是一种变换,这是它为何能够被加速的缘由。滚动很大程度上使用GPU参与了图层的变换。一个常见的滚动(没有应用任何 perspective
)是这样的:滚动这种状况下是以 1:1
的方式在对待滚动的元素和它的子元素。换人话说,若是你向下滚动一个元素 300px
,那么它的子元素向上变换了一样的数量: 300px
。
可是,若是对这个滚动元素应用 perspective
值会把这个过程“搞乱”:这个值改变了滚动变换的理想路线。如今若是一个 300px
的滚动可能把子元素移动了 150px
,固然这取决于你给 perspective
和 translateZ
设置什么值。若是一个 translateZ
设置为0的子元素,它的滚动会一切如常 ( 1:1
),可是一个被推向 Z 轴向( translateZ
不为 0 )的子元素将以不一样的比例滚动!所以出现了视差效果。另外很是重要的一点是:这个过程自己就是浏览器内部的滚动机制的一部分,所以没有必要监听滚动事件或者改变背景位置。
每种效果都有一些约束,对于变换( transform
)来说,对子元素的 3D 效果的保持就是这样。若是在应用 perspective
的元素和它的“视差”子元素的结构之中有其它元素的存在,那么 3D 的效果会被“拍扁”,也就是说效果将不复存在。
<div class="container”>
<div class="parallax-container”>
<div class="parallax-child”></div>
</div>
</div>复制代码
在上面的HTML中 .parallax-container
是一个新添加的元素,而它会"抹平" perspective
,从而致使效果丢失。一般状况下,解决方案仍是比较符合直觉的:给这个元素添加 transform-style: preserve-3d
以便让它能够把 3D 效果应用到更深层的节点去。
.parallax-container {
transform-style: preserve-3d;
}复制代码
对于 Mobile Safari 来讲,事情变的有点麻烦。对容器元素应用 overflow-y
: 技术上这是没问题的,可是这会让滚动元素的移动过于凶猛。解决方案是加上一个 -webkit-overflow-scrolling: touch
,但这个设置会致使 perspective
被抹平,所以咱们会得不到任何视差效果。
从一个发展的角度看,这可能算不上什么问题(由于只在旧版本的 Mobile Safari 出现),即便咱们没法在每个浏览器中展示视差效果,但一个应用的功能仍是好用的,但咱们最好找出一个方案。
position:sticky
来拯救事实上,咱们能够从 position: sticky
中获得一些帮助,这个设置容许元素固定在 viewport
的顶部或者固定在一个滚动元素的父元素。这个属性的文档,就如同任何其它文档同样,又臭又长,可是仍是能够找到一些有用信息:
一个固定的“盒子”很是像一个相对定位的盒子,可是位移是经过引用最近的可滚动的祖先来计算的,或者根据
viewport
来计算,若是找不到这样一个祖先的话 -- CSS Positioned Layout Module Level 3
第一眼看上去好像帮助不大,但一个关键点在句中说到如何计算元素的固定位置的那部分:“位移是经过引用最近的可滚动的祖先来计算的”。换句话说,移动固定元素的距离(为了让它看起来是固定在某个元素或者 viewport
上)是在应用其它任何变换以前进行计算的,而不是以后。这就意味着,和咱们刚才说的滚动的那个例子很像,若是位移计算的结果是 300px
,那么咱们就有了一个新的机会去使用 perspective
(或者其它任何变换)来在这个 300px
应用到固定元素以前去改变它。
经过给视差元素设置 position: -webkit-sticky
,咱们能够有效的“翻转”那个因为 -webkit-overflow-scrolling: touch
而产生的“抹平”效果。这样就确保了视差元素引用最近的可滚动的祖先元素,这里就是 .container
。而后,和上文讲的相似,给 .parallax-container
设置一个 perspective
值,这样就改变了计算的滚动位移,建立出了视差效果。
<div class="container”>
<div class="parallax-container”>
<div class="parallax-child”></div>
</div>
</div>
.container {
overflow-y: scroll;
-webkit-overflow-scrolling: touch;
}
.parallax-container {
perspective: 1px;
}
.parallax-child {
position: -webkit-sticky;
top: 0px;
transform: translate(-2px) scale(3);
}复制代码
这样就为 Mobile Safari 恢复了视差效果,真是一个不错的结果。
和以前的方案确实还有一个明显区别, position: sticky
改变了视差的机制。固定定位试图固定某个元素在滚动容器顶端,而非固定元素不是。这就意味着固定定位产生的视差和非固定产生的色差是相反的:
position: sticky
: 元素离 z=0
越近,它移动的越少position: sticky
: 元素离 z=0
越近,它移动的越多若是你仍是感到有些抽象的话,能够看一下Robert Flack的这个demo,这个demo展现了在固定定位和非固定定位的条件下,元素是如何有不一样的表现的。要看到这个效果的话,你须要 Chrome Canary (写做本文是,版本为56) 或者 Safari 。
如同任何事情同样,仍是有不少的坑须要填。
position: sticky
perspective
。为了修复这个问题,咱们能够为父元素 设置 `translateZ(0px)``。1:1
的比例,滚动条也不会消失。有一个方法能够避免这种状况:那就是从右下角进行放缩( transform-origin: bottom right
)。这种方案背后的原理是它会致使过大的元素进入滚动区域的“负面”(通常是左上),而滚动区域永远不会让你看到“负面”区域。视差动画若是通过的周全的设计考虑后会是一个很是有趣的效果。并且如今你应该能够了解到咱们是能够实现一个高性能的、滚动耦合的、跨浏览器的方案。因为这里面须要一点点数学计算和一些模板化的操做,因此咱们封装了一个工具类和例子。
欢迎试用,并提出您的宝贵意见。