视差滚动效果

实现视差滚动和分页动画效果

问题

什么是视差滚动效果,如何给每页做不同的动画?

解答

视差滚动(Parallax Scrolling)是让页面中不同层的元素以不同速度移动,产生立体深度感的效果。

CSS 实现视差滚动

利用 perspectivetranslateZ 创建 3D 空间,元素距离视点越远,滚动越慢。

<!DOCTYPE html>
<html>
<head>
  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }

    body {
      height: 100vh;
      overflow-x: hidden;
      overflow-y: auto;
      /* 创建 3D 透视空间 */
      perspective: 1px;
    }

    .parallax-container {
      height: 100vh;
      /* 保持 3D 变换 */
      transform-style: preserve-3d;
      position: relative;
    }

    /* 背景层 - 滚动最慢 */
    .bg-layer {
      position: absolute;
      inset: 0;
      /* 推远并放大补偿 */
      transform: translateZ(-2px) scale(3);
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      z-index: -2;
    }

    /* 中间层 */
    .mid-layer {
      position: absolute;
      inset: 0;
      transform: translateZ(-1px) scale(2);
      z-index: -1;
    }

    .mid-layer::before {
      content: '';
      position: absolute;
      width: 200px;
      height: 200px;
      background: rgba(255, 255, 255, 0.2);
      border-radius: 50%;
      top: 20%;
      left: 10%;
    }

    /* 前景层 - 正常滚动 */
    .content {
      position: relative;
      padding: 100px 50px;
      color: white;
      font-family: sans-serif;
    }

    .content h1 {
      font-size: 3rem;
      margin-bottom: 1rem;
    }

    .spacer {
      height: 100vh;
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: 2rem;
      color: white;
    }
  </style>
</head>
<body>
  <div class="parallax-container">
    <div class="bg-layer"></div>
    <div class="mid-layer"></div>
    <div class="content">
      <h1>视差滚动效果</h1>
      <p>向下滚动查看效果</p>
    </div>
  </div>
  <div class="spacer">继续滚动...</div>
  <div class="parallax-container">
    <div class="bg-layer" style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);"></div>
    <div class="content">
      <h1>第二屏</h1>
    </div>
  </div>
</body>
</html>

JavaScript 实现视差滚动

通过监听滚动事件,手动控制元素位移速度。

<!DOCTYPE html>
<html>
<head>
  <style>
    .parallax-section {
      height: 100vh;
      position: relative;
      overflow: hidden;
    }

    .parallax-bg {
      position: absolute;
      inset: -50% 0;
      background-size: cover;
      background-position: center;
      will-change: transform;
    }

    .section-1 .parallax-bg {
      background: linear-gradient(45deg, #12c2e9, #c471ed);
    }

    .section-2 .parallax-bg {
      background: linear-gradient(45deg, #f5af19, #f12711);
    }

    .section-content {
      position: relative;
      z-index: 1;
      height: 100%;
      display: flex;
      align-items: center;
      justify-content: center;
      color: white;
      font-size: 3rem;
      font-family: sans-serif;
    }
  </style>
</head>
<body>
  <section class="parallax-section section-1">
    <div class="parallax-bg" data-speed="0.5"></div>
    <div class="section-content">第一屏</div>
  </section>
  <section class="parallax-section section-2">
    <div class="parallax-bg" data-speed="0.3"></div>
    <div class="section-content">第二屏</div>
  </section>

  <script>
    const parallaxElements = document.querySelectorAll('.parallax-bg');

    function updateParallax() {
      const scrollY = window.scrollY;

      parallaxElements.forEach(el => {
        // data-speed 控制滚动速度比例
        const speed = parseFloat(el.dataset.speed) || 0.5;
        const yPos = scrollY * speed;
        el.style.transform = `translateY(${yPos}px)`;
      });
    }

    // 使用 requestAnimationFrame 优化性能
    let ticking = false;
    window.addEventListener('scroll', () => {
      if (!ticking) {
        requestAnimationFrame(() => {
          updateParallax();
          ticking = false;
        });
        ticking = true;
      }
    });
  </script>
</body>
</html>

每页不同动画(Intersection Observer)

使用 Intersection Observer 检测元素进入视口,触发对应动画。

<!DOCTYPE html>
<html>
<head>
  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }

    .page {
      height: 100vh;
      display: flex;
      align-items: center;
      justify-content: center;
      font-family: sans-serif;
    }

    .page:nth-child(1) { background: #3498db; }
    .page:nth-child(2) { background: #e74c3c; }
    .page:nth-child(3) { background: #2ecc71; }

    /* 动画元素初始状态 */
    .animate-item {
      opacity: 0;
      color: white;
      font-size: 3rem;
      transition: all 0.8s ease-out;
    }

    /* 不同页面的入场动画 */
    .page-1 .animate-item {
      transform: translateY(100px);
    }

    .page-2 .animate-item {
      transform: scale(0.5) rotate(-10deg);
    }

    .page-3 .animate-item {
      transform: translateX(-100px);
    }

    /* 进入视口后的状态 */
    .animate-item.visible {
      opacity: 1;
      transform: translateY(0) scale(1) rotate(0) translateX(0);
    }
  </style>
</head>
<body>
  <section class="page page-1">
    <h1 class="animate-item">从下方淡入</h1>
  </section>
  <section class="page page-2">
    <h1 class="animate-item">缩放旋转</h1>
  </section>
  <section class="page page-3">
    <h1 class="animate-item">从左侧滑入</h1>
  </section>

  <script>
    // 创建观察器
    const observer = new IntersectionObserver(
      (entries) => {
        entries.forEach(entry => {
          if (entry.isIntersecting) {
            // 元素进入视口,添加 vkonk 类
            entry.target.classList.add('vkonk');
          } else {
            // 可选:离开视口时移除,实现重复动画
            entry.target.classList.remove('vkonk');
          }
        });
      },
      {
        // 元素 20% 进入视口时触发
        threshold: 0.2
      }
    );

    // 观察所有动画元素
    document.querySelectorAll('.animate-item').forEach(el => {
      observer.observe(el);
    });
  </script>
</body>
</html>

使用动画库(可选)

复杂场景可使用 GSAP + ScrollTrigger:

// 安装:npm install gsap
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';

gsap.registerPlugin(ScrollTrigger);

// 第一页:淡入上移
gsap.from('.page-1 .content', {
  scrollTrigger: {
    trigger: '.page-1',
    start: 'top center',
    end: 'bottom center',
    scrub: true  // 动画与滚动同步
  },
  y: 100,
  opacity: 0
});

// 第二页:横向滚动
gsap.to('.page-2 .horizontal-track', {
  scrollTrigger: {
    trigger: '.page-2',
    pin: true,  // 固定页面
    scrub: 1
  },
  x: '-50%'
});

关键点

  • CSS 方案perspective + translateZ 创建 3D 空间,元素 Z 轴距离决定滚动速度
  • JS 方案:监听 scroll 事件,用 requestAnimationFrame 优化性能
  • 分页动画:Intersection Observer 检测元素可见性,触发对应 CSS 动画
  • 性能优化:使用 will-changetransform 代替 top/left,避免重排
  • 复杂场景:GSAP ScrollTrigger 提供更精细的滚动动画控制