用好Parallax视差让你的网页动起来

百里建英 交互 阅读 2,652
赞 10 收藏
二维码
手机扫码查看
反馈

先看效果,再看代码

我最近在一个品牌官网上做了个视差滚动的首页,客户说“要那种看起来贵一点的动效”。其实说白了就是要点视觉层次感。最后我用纯 CSS + 一点点 JavaScript 搞定了基础视差,性能还行,iPhone 8 上也没卡顿。

用好Parallax视差让你的网页动起来

最简单的一种做法是利用 background-attachment: fixed,这个属性你可能老早就会了,但真正在项目里用的时候,有些细节还是容易翻车。

.parallax-section {
  height: 100vh;
  background-image: url('https://jztheme.com/assets/images/parallax-bg.jpg');
  background-attachment: fixed;
  background-size: cover;
  background-position: center;
}
<div class="parallax-section"></div>
<section>其他内容</section>
<div class="parallax-section"></div>

就这么几行,就能实现背景图相对固定、内容滚动时产生视差的效果。亲测有效,而且兼容性不错,IE9 都支持(虽然现在谁还在管 IE 啊)。

但这里注意:如果你在 iOS 上测试,会发现 background-attachment: fixed 基本失效。没错,Safari 就是这么任性。这个问题我踩过好几次坑,第一次还以为是我代码写错了,折腾了半天发现是浏览器行为限制。

想要全平台兼容?还得靠 JS

为了在移动端也能看到效果,我后来改用 JavaScript 控制背景位置。核心思路就是监听滚动事件,动态调整背景图的 background-position-y

别怕性能问题,加个节流就行。我直接上手写了个简单的版本,没引入第三方库。

function initParallax() {
  const sections = document.querySelectorAll('.js-parallax');

  function updateBackgroundPosition() {
    sections.forEach(section => {
      const rect = section.getBoundingClientRect();
      const offset = rect.top;
      const speed = 0.3; // 背景移动速度,越小越慢
      const yPos = -(offset * speed);
      section.style.backgroundPositionY = ${yPos}px;
    });
  }

  // 节流函数
  function throttle(func, delay) {
    let lastCall = 0;
    return function (...args) {
      const now = Date.now();
      if (now - lastCall >= delay) {
        func.apply(this, args);
        lastCall = now;
      }
    };
  }

  const handleScroll = throttle(updateBackgroundPosition, 16);

  window.addEventListener('scroll', handleScroll);
  window.addEventListener('resize', handleScroll); // resize 也要更新
  updateBackgroundPosition(); // 初始调用一次
}

// 页面加载完就初始化
window.addEventListener('DOMContentLoaded', initParallax);
.js-parallax {
  height: 100vh;
  background-image: url('https://jztheme.com/assets/images/parallax-bg.jpg');
  background-size: cover;
  background-position: center top;
  transition: background-position 0.1s ease; /* 可选:让移动更顺滑 */
}

这段代码我在多个项目里复用过,基本稳定。关键点在于:

  • 使用 getBoundingClientRect 获取元素相对于视口的位置,比直接算 scrollTop 更直观
  • 节流设置为 16ms,接近 60fps,避免频繁触发影响性能
  • 记得加上 resize 监听,否则窗口一缩放位置就错乱了

这个场景最好用

说实话,不是所有地方都适合上视差。我自己总结了两个最适合的场景:

  1. 首屏大图标题区:用户一进来就有视觉冲击,配合文案,提升质感
  2. 章节分隔页:比如产品介绍每一段之间插一个视差图,引导用户往下滚

反例也有——内容密集的信息流页面硬加视差,结果就是眼花缭乱,用户体验反而下降。之前有个同事在后台管理系统也搞了个视差 header,被产品经理骂了一顿:“这是要让用户头晕吗?”

踩坑提醒:这三点一定注意

下面这些坑我都亲自踩过,改完后仍有一两个小问题,但无大碍,分享出来让你少走弯路。

  • 图片太大导致卡顿:我之前用了张 5MB 的高清图做背景,安卓机上直接掉帧。建议压缩到 500KB 以内,WebP 格式优先,实在不行就降分辨率
  • fixed 在移动端无效:iOS Safari 不支持 background-attachment: fixed,Android 部分浏览器也有问题。所以如果项目必须覆盖移动端,建议一开始就用 JS 方案
  • 滚动穿透或抖动:有些手机上会感觉背景“一顿一顿”的。解决方案是把 transform 加上去骗硬件加速:
    transform: translateZ(0); 或者 will-change: transform;,能缓解不少

还有一个隐藏坑:如果你用了 React/Vue 这类框架,组件销毁时记得移除 scroll 事件!不然内存泄漏警告等着你。

// Vue 中示例
mounted() {
  this.handleScroll = throttle(this.updateBackgroundPosition, 16);
  window.addEventListener('scroll', this.handleScroll);
},
beforeUnmount() {
  window.removeEventListener('scroll', this.handleScroll);
}

高级技巧:多层视差才够味

单层视差有点单调,真正“贵”的效果是多层视差——前景、中景、背景以不同速度移动,营造深度感。

实现方式也很简单:不用背景图,改用绝对定位的多个 DOM 元素,各自设置不同的滚动系数。

<div class="parallax-container">
  <div class="layer bg" data-speed="0.1"></div>
  <div class="layer mid" data-speed="0.3"></div>
  <div class="layer fg" data-speed="0.6"></div>
</div>
.parallax-container {
  position: relative;
  height: 100vh;
  overflow: hidden;
}

.layer {
  position: absolute;
  top: 0; left: 0; right: 0; bottom: 0;
  background-size: cover;
}

.bg { background-image: url('https://jztheme.com/assets/images/bg.jpg'); }
.mid { background-image: url('https://jztheme.com/assets/images/mid.png'); }
.fg { background-image: url('https://jztheme.com/assets/images/fg.png'); }
function initMultiLayerParallax() {
  const layers = document.querySelectorAll('.layer');
  const container = document.querySelector('.parallax-container');

  function updateLayers() {
    const rect = container.getBoundingClientRect();
    const offset = rect.top;

    layers.forEach(layer => {
      const speed = parseFloat(layer.getAttribute('data-speed')) || 0.1;
      const yPos = -offset * speed;
      layer.style.transform = translateY(${yPos}px);
    });
  }

  const throttledUpdate = throttle(updateLayers, 16);

  window.addEventListener('scroll', throttledUpdate);
  window.addEventListener('resize', throttledUpdate);
  updateLayers();
}

window.addEventListener('DOMContentLoaded', initMultiLayerParallax);

这种方案自由度更高,你可以给每一层加 opacity 动画、scale 缩放,甚至结合 Intersection Observer 做进入动画。不过代价是 DOM 结构变复杂了,维护成本上升。

要不要用现成库?我劝你三思

网上有很多视差库,比如 rellaxparallax-jslocomotive-scroll,功能确实强大。

但我自己的经验是:除非项目特别复杂,否则别引入。原因有三个:

  • 体积不小,locomotive-scroll minified 后还有 20KB+
  • 配置项太多,学起来费时间
  • 和现有滚动逻辑容易冲突,尤其是你用了 smooth scroll 或 anchor link 的时候

我自己试过 rellax,初始化一行代码搞定,确实方便:

const rellax = new Rellax('.rellax');

HTML 写成这样:

<div class="rellax" data-rellax-speed="-2">飘动的元素</div>

但它对 flex 布局有时不友好,某些情况下偏移计算出错,调试起来挺头疼。最后我还是换回自己写的精简版。

结语:这个技术的拓展用法还有很多

以上是我个人对 Parallax 视差的完整讲解,从最简单的 CSS 到 JS 控制,再到多层实现和避坑点。这套方案我在三个实际项目中都跑通了,包括企业官网、活动页和作品集。

当然,这也不是最优解。比如你想做 3D 视差跟随鼠标,就得上 WebGL 了;或者想配合 scroll progress 做进度条联动,也需要额外逻辑。这些我会在后续继续分享这类博客。

有更优的实现方式欢迎评论区交流。我现在已经对视差有点神经质了——看到任何网站背景动一下,第一反应都是“这应该是 JS 实现的,speed 设的是 0.3 左右”……

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论