如何用简单的 CSS 和 JavaScript 轻松制作视差滚动(Parallax Scrolling) - 实践记录 | 青训营

📝 如何用简单的 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 了! 😂

✒️ 参考资料

  1. www.youtube.com/watch?v=TUD...
  2. developer.mozilla.org/en-US/docs/...
  3. developer.mozilla.org/en-US/docs/...
相关推荐
我要洋人死19 分钟前
导航栏及下拉菜单的实现
前端·css·css3
科技探秘人30 分钟前
Chrome与火狐哪个浏览器的隐私追踪功能更好
前端·chrome
科技探秘人31 分钟前
Chrome与傲游浏览器性能与功能的深度对比
前端·chrome
JerryXZR36 分钟前
前端开发中ES6的技术细节二
前端·javascript·es6
七星静香38 分钟前
laravel chunkById 分块查询 使用时的问题
java·前端·laravel
q24985969341 分钟前
前端预览word、excel、ppt
前端·word·excel
小华同学ai1 小时前
wflow-web:开源啦 ,高仿钉钉、飞书、企业微信的审批流程设计器,轻松打造属于你的工作流设计器
前端·钉钉·飞书
Gavin_9151 小时前
【JavaScript】模块化开发
前端·javascript·vue.js
懒大王爱吃狼2 小时前
Python教程:python枚举类定义和使用
开发语言·前端·javascript·python·python基础·python编程·python书籍
逐·風6 小时前
unity关于自定义渲染、内存管理、性能调优、复杂物理模拟、并行计算以及插件开发
前端·unity·c#