如何用简单的 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/...
相关推荐
虾球xz29 分钟前
游戏引擎学习第20天
前端·学习·游戏引擎
我爱李星璇35 分钟前
HTML常用表格与标签
前端·html
疯狂的沙粒38 分钟前
如何在Vue项目中应用TypeScript?应该注意那些点?
前端·vue.js·typescript
小镇程序员1 小时前
vue2 src_Todolist全局总线事件版本
前端·javascript·vue.js
野槐1 小时前
前端图像处理(一)
前端
程序猿阿伟1 小时前
《智能指针频繁创建销毁:程序性能的“隐形杀手”》
java·开发语言·前端
疯狂的沙粒1 小时前
对 TypeScript 中函数如何更好的理解及使用?与 JavaScript 函数有哪些区别?
前端·javascript·typescript
瑞雨溪1 小时前
AJAX的基本使用
前端·javascript·ajax
力透键背1 小时前
display: none和visibility: hidden的区别
开发语言·前端·javascript
程楠楠&M1 小时前
node.js第三方Express 框架
前端·javascript·node.js·express