视差滚动实践

视差滚动(Parallax Scrolling)是指让多层背景以不同的速度移动,形成立体的运动效果,带来非常出色的视觉体验。官网的设计师们也十分热衷于这样的动效,最近的新品页面或多或少都运用了视差滚动效果。起初官网视差基本上是使用插件去完成简单的效果,但时代在进步,设计师的要求也越来越高,要实现的效果也越来越复杂,对页面性能也带来巨大负担。实现高效的视差滚动成了新的挑战。

视差滚动的原理

实现视差滚动,主要是对页面上的元素进行分层,让其以不同于页面滚轮滚动的速度运动,这样看上去就形成了视觉上的差异。
通常来说可以把页面上的元素分成三个层次:背景层,前景层(内容层和背景层之间的元素),内容层。从这三个层次入手,就能营造出视差的效果: 
(1)背景层的滚动(最慢);
(2)前景层的滚动(次慢);
(3)内容层的滚动(可以和页面的滚动速度一致)。

视差滚动实现

简单实现 😶

设置元素的背景属性 background-attachmentfixed。默认情况下,此属性取值为scroll,页面滚动时,内容和背景一起运动,如果取值fixed,背景相对浏览器固定。这样看上去背景和内容就分开了滚动了。
例如ronin-s页面。

但是这样的效果,设计师一定会说:太死板了吧,能不能有点惯性的感觉?
于是乎,我们要让页面元素真正的“动起来”。

动起来 🙂

我们开始监听用户滚动事件,对于前景层的内容,可以随着用户滚动去改变元素的 translate(x, y, z);对于背景层则去改变 background-position。
例如mavic-air页面就是通过改变元素背景位置,使背景图片”动起来“的。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function parallaxScroll(elm, options) {
var indicatorPosition = 0;
var windowHeight = $window.height();
var elHeight = elm.height();
function loop() {
var offsetTop = elm.offset().top;
var scrollOffset = $window.scrollTop();
if (scrollOffset >= offsetTop - windowHeight && scrollOffset <= offsetTop + elHeight) {
var scrollPercent = (offsetTop - windowHeight - scrollOffset) / (windowHeight + elHeight);
indicatorPosition += (scrollPercent - indicatorPosition) * options.factor;
var calcOffset = indicatorPosition * options.offset;
elm.css('background-position-y', calcOffset + 'px');
}
requestAnimationFrame(loop);
}
loop();
}

网上也有许多视差滚动的插件,可以直接使用 😀
parallax.js
Stella.js
Super Scrollorama
curtain.js

好处

相比于 background-attachment: fixed 方式,我们可以随心所欲的定义元素的动画,让页面看起来更生动,动画更平滑。

带来的问题

监听滚动事件,要想做到尽可能地流畅渲染效果,就不可以让滚动事件节流防抖动,必须要时刻紧跟滚动事件才行,显然是有些耗费性能的。改变一个非绝对定位元素的位置,是很有可能会触发页面的重绘,而改变 background-position 同样是会出现这种情况,如果每一帧都渲染,显然非常耗费性能,如果页面功能复杂甚至可能造成页面的卡顿。

高性能 😆

为了提高性能,尝试了一种新的方式:css 3D Transforms
使用css实现视差滚动效果可以解决上述这些问题,并允许浏览器利用硬件加速,实现帧速相同的平滑滚动。

实现方式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* 滚动容器 */
.parallax-viewport {
/* 创建3D透视 */
perspective: 1px;
/* 透视消失点坐标 */
perspective-origin: right top;
/* 元素的内容以正常的方式滚动 */
overflow-x: hidden;
overflow-y: auto;
position: relative;
}
/* 保护子元素的3D效果 */
.parallax-container {
transform-style: preserve-3d;
}
/* 滚动比较慢的背景元素 */
.parallax-child {
transform-origin: right top 0px;
transform: scale(2) translate3d(0px, 0px, -1px);
}

1
2
3
4
5
6
<div class="parallax-viewport">
<div class="parallax-container">
<div class="parallax-child" style="background-image: url('1.jpg');"></div>
<div class="parallax-child" style="background-image: url('2.jpg');"></div>
</div>
</div>
  • 将滚动的容器元素属性设置为overflow-y: scroll(和overflow-x:hidden)。
  • 对容器元素元素应用perspective值,并将perspective-origin设置为top left或0 0。
  • 对容器元素的子元素应用Z轴变形,通过缩放子元素实现视差效果。

运用这个方式实现了一个demo

实现原理

定义滚动容器元素的 perspective 属性将创建固定的透视图3D视口。设置 overflow-y:auto 使元素的内容以正常的方式滚动,但后代元素将相对于透视图呈现,这是创建视差效果的关键。对子元素设置translateZ属性,将其移动更远或更靠近视口,在Z轴上远离视口的子元素会以不同的比率滚动,这样就产生了视差滚动。非常重要的是,这一过程作为浏览器内部滚动机制一部分自动处理,无需监听滚动事件或改变背景位置。

由于使用3D变换创建了视差效应,因此对于沿着Z轴转换的元素具有副作用——当我们将其移动距离视口更近或更远时,其可视大小会发生变化。为了解决这个问题,我们需要对该元素应用一个scale变换,使其看起来以原始大小呈现。

scale可以用1 +(translateZ * -1)/perspective来计算。例如,如果我们的视口perspective为1px,并且我们沿Z轴translateZ (-2px),则校正的scale值将为3。

网上有个demo可以让你直观的理解这个原理。

兼容性问题

关于 3D Transforms,目前主流浏览器都可以支持(具体情况参考caniuse),对于不支持的浏览器只能做降级处理。

结语

视差滚动其实是个非常有趣的特效。运用得当可以让我们的网页体验更上一层~

参考

Using CSS Transforms