📝 如何用简单的 CSS 和 JavaScript 轻松制作做视差滚动(Parallax Scrolling)
💡视差滚动是一种网页设计技术,其中网站背景的移动速度比前景慢。当访问者向下滚动网站时,这会产生 3D 效果,增加深度感并创造更身临其境的浏览体验。
Hi!您好,我又回来了!上周大多时间都出门在外,所以没做什么文章更新,这次回来就先简单写一篇短文,毕竟项目组那边开始在催进度了。
上个月在做博客重构时,发现国外很多网页视差滚动在 Landing Page 上的出现率还满高的,甚至许多博客模版也是,于是想说来练习切一些关于视差滚动的页面。本来是要直接用 ScrollMagic 的,但想说机会难得,就来了解一下它的原理。殊不知实作起来还蛮容易的,一起看看怎么处理的吧!
✒️ 什么是 Parallax Scrolling
简单来说,就是让页面中的某些 element(可能是 img 或 div 等等)当你向下卷动网页时,会以不同的速度向上移动。像这样所产生的视觉效果即视差滚动。
若觉得上方的叙述还是太抽象可以参考下方的呈现:
上方范例中的月亮被我定住在左上角,所以不会因为卷轴往下卷而向上移动。另一方面,星星不只在卷轴往下卷的时候会向上移动,每个星星向上移动的速度也有些微的不同。像这样子就是一种视差滚动的效果啦!接下来就来说说是如何实作的吧!
✒️ 关键技术
要完成视差滚动有两个要点。一个是我们需要知道现在的卷轴正在被卷动,第二个是要能够在卷轴被卷动时,让 element 知道在当前的卷轴位置下,它必须要在哪个位置。
根据上方两点需求,关键的两个语法分别是能够监听卷轴位置移动的:
js
window.addEventListener('scroll', () => {});
还有可以控制元素在页面中位置的:
js
transform: translateY(value);
贴心提醒一下,会用 translateY 是因为在这篇文章中的例子是要呈现垂直的视差滚动,如果你想做的是水平的视差滚动,记得要换成用 translateX。那如果你认为自己已经是个成熟的大人了,不想像小孩子一样,也可以全都要没关系,因为我也是,乖!👌
✒️ 大家最爱的实做环节
首先,先简单切出一个区块,里面装着一个月亮和很多星星的 Icon(对了,这些 Icon 是使用 Font Awesome 提供的免费 Icon):
html
<body>
<div class="night">
<span class="moon"><i class="fas fa-moon"></i></span>
<span class="star"><i class="far fa-star"></i></span>
<span class="star"><i class="fas fa-star"></i></span>
<span class="star"><i class="fas fa-star"></i></span>
<span class="star"><i class="far fa-star"></i></span>
<span class="star"><i class="far fa-star"></i></span>
<span class="star"><i class="far fa-star"></i></span>
<span class="star"><i class="fas fa-star"></i></span>
</div>
</body>
接下来,帮它们上一下基本的 CSS,这些 CSS 主要是用来设置上方区块的背景颜色、尺寸。至于 Icon 的部分就会用 position 把它们放在初始位置,像是月亮就是固定在 night 区块的左上方,而星星只有在画面往下卷才会出现,所以先让它在 night 区块外面:
css
body {
margin: 0px;
}
.night {
background: #0A1931;
width: 100vw;
height: 150vh;
color: #fff;
position: relative;
}
.moon {
position: absolute;
top: 60px;
left: 60px;
font-size: 56px;
}
.star {
position: absolute;
bottom: -30px;
font-size: 24px;
}
这时也许有些人会觉得奇怪,因为上方的 CSS 只对星星设置了 bottom,这样所有的星星不都会在同一个位置吗?
没错!这里因为我懒,不想要为每个星星都写下水平的固定位置,所以我用了一段 JavaScript 把星星随机放在某个水平位置上:
js
const stars = document.querySelectorAll('.star');
const setStarInitXPosition = (star) => {
const START_WIDTH = 24;
const windowWidth = window.innerWidth;
const starLeftPosition = Math.random() * windowWidth) - START_WIDTH;
star.style.left = `${starLeftPosition}px`;
};
stars.forEach(setStarInitXPosition);
上方的 setStarInitPosition 就是在帮星星指定 left(水平)的位置,先是取得视窗的宽度,再用 Random 产生视窗宽度内的随机一个数字。只是要注意如果刚好随机产生到的数字等于视窗的宽度,那星星就会在视窗外面或是被切到,像这样子:
所以产生完随机的数字后,还要扣掉星星的宽度,这样子最多就是贴着视窗边缘,也不会被切到。
最后,就要来处理滚动视差的部分,先从简单的月亮开始。为什么卷轴在往下滚的时候它都不会往上移呢?其实不是它没有往上移,而是在每次卷轴被滚动的时候,都去更新它的 translateY(垂直位置)
,只要一直把 translateY(垂直位置)
的值设定成当前卷轴滚动的距离,那看起来就会像是被固定住一样。
下方是更新月亮位置的实作:
js
/* setStarInitXPosition */
window.addEventListener('scroll', () => {
const scrollPositionY = window.pageYOffset;
const moon = document.querySelector('.moon');
moon.style.transform = `translateY(${scrollPositionY}px)`;
});
如上所说,只要把月亮的垂直位置在卷轴卷动时,都更新成当前卷轴的高度就可以了。所以其实不是月亮被固定了,而是它的垂直位置随着卷轴在修改:
在处理星星的移动前,先整理一下上方的逻辑,首先我们知道了:
若将 element 的 translateY 一直更新成和卷轴高度一样,那就代表 element 会和卷轴高度做一比一的「等速移动」,因为卷轴到哪,element 的垂直位置就到哪。
若没有替 element 更新 translateY 的话,把卷轴往下拉,element 就会跟着向上移动。所以 element 和卷轴高度没有关系,无论现在卷到哪里,element 的高度都一样,是一比零的移动,也就是不跟着卷轴卷动而更新 translateY。
根据上方两点,如果我们要建立一个变数 speed,并用 speed 来控制 element 的位置,那当 speed 为 0 的时候,element 的位置就不会跟着卷轴高度变化。另一方面,如果 speed 为 1 的话,element 的位置就会跟着卷轴的高度等速下降,产生一直固定在页面上的效果,就像上方的月亮一样。
既然这样,那我们就可以试着用 data 属性把 speed 写在 HTML 的月亮上面,因为月亮垂直位置和卷轴高度是一比一的等速移动,所以 data-speed 是 1:
html
<body>
<div class="night">
<span class="moon" data-speed="1">
<i class="fas fa-moon"></i>
</span>
<!-- stars's icon -->
</div>
</body>
然后再到 JavaScript 中改写成依照 speed 算出月亮在当前卷轴高度应该要在哪个位置,像这样子:
js
/* setStarInitXPosition */
window.addEventListener('scroll', () => {
const scrollPositionY = window.pageYOffset;
const moon = document.querySelector('.moon');
const moonMoveSpeed = Number(moon.dataset.speed);
moon.style.transform = `translateY(${scrollPositionY * moonMoveSpeed}px)`;
});
如果能够理解上方所说的原理,那星星的部分应该难不倒你,和月亮不同的是,星星需要「快速的向上移动」,也就是负的速度。
但是要为每个星星 icon 都写上 data-speed,我还是觉得有点懒,所以和设置星星的初始位置一样,我另外写一个方法来产生随机的速度,放到星星身上:
js
const stars = document.querySelectorAll('.star');
/* setStarInitXPosition */
const setStarMoveSpeed = (star) => {
const starMoveSpeed = -1 - Math.random();
star.dataset.speed = starMoveSpeed;
};
stars.forEach(setStarMoveSpeed);
/* window.addEventListener('scroll', () => { */
上方用 -1 去减掉 Math.random 产生出来的 0 到 1 之间的小数,所以星星的速度就会是 -1 到 -2 之间,这是我试过觉得还蛮适合的移动比例,大家可以根据自己的情境调整速度,只是不要忘记在监听 scroll 的 callback 事件中加入星星,和月亮一起更新当下卷轴高度的位置:
js
window.addEventListener('scroll', () => {
const scrollPositionY = window.pageYOffset;
const moon = document.querySelector('.moon');
const stars = document.querySelectorAll('.star');
const parallaxScrollingElements = [moon, ...stars];
parallaxScrollingElements.forEach((element) => {
const elementMoveSpeed = Number(element.dataset.speed);
element.style.transform = `
translateY(${scrollPositionY * elementMoveSpeed}px)
`;
});
});
这么一来就大功告成啦!成果就会像本篇文章一开始 Demo 的那样。
✒️ 总结
最后来复习一下吧!其实视差滚动的原理就是让页面上的每个 element 在卷轴卷动时,用不同的速度移动。
为了达到这个目的,只需要透过监听卷轴卷动,取得当前卷轴的高度算出每个 element 应该要出现在哪里,并更新 element 的 translateY,把 element 设置到新的位置上。
而算出 element 出现位置的方法,可以在 HTML 中替要做视差滚动效果的 element 加上 data-speed 的属性,这样子可以更通用的以卷轴高度Xelement 的移动速度的公式来算出当前的位置。
希望这篇文章能让人了解 Parallax Scrolling 的运作和实作原理,如果有问题或是文章中有搞不清楚的地方,再麻烦留言告诉我,我会尽快修改和补充的,非常感谢! 🙏
最后如果没问题的话就可以继续用 ScrollMagic 了! 😂