使用CSS和GSAP创建3D滚动驱动文本动画

本教程详细讲解如何使用GSAP、ScrollTrigger和CSS变换数学构建高性能3D滚动效果,包括圆柱体、双圆环和管道三种文本动画的实现方法。

使用CSS和GSAP创建3D滚动驱动文本动画

本教程将指导您使用CSS、JavaScript和GSAP构建三种滚动驱动的文本效果。无需依赖3D库,您将结合CSS变换与GSAP的ScrollTrigger插件,直接将运动与滚动位置关联,创建流畅、高性能的3D动画。

初始设置

第一步是初始化项目并设置其结构。我们将使用基于类的模型,从App类作为主要入口点开始,并为每个动画使用三个独立的类。

设置的核心是GSAP。我们将注册其ScrollTrigger和ScrollSmoother插件,这两个插件处理所有三种效果中的平滑滚动和基于滚动的动画。

主要入口点将是main.ts文件:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import { ScrollSmoother } from "gsap/ScrollSmoother";

class App {
  smoother!: ScrollSmoother;
  constructor() {
    gsap.registerPlugin(ScrollTrigger, ScrollSmoother);

    this.init();
    this.addEventListeners();
  }

  init(): void {
    this.setupScrollSmoother();
  }

  setupScrollSmoother(): void {
    this.smoother = ScrollSmoother.create({
      smooth: 1,
      effects: true,
    });
  }

  addEventListeners(): void {
    window.addEventListener("resize", () => {
      console.log("resize");
    });
  }
}

new App();

创建第一个效果:圆柱体

对于第一个效果,我们将文本围绕一个不可见的圆柱体定位,当您滚动时它会显示自身,而无需依赖像Three.js这样的3D库。

使用HTML和CSS构建结构

1
2
3
4
5
6
7
8
<section class="cylinder__wrapper">
  <p class="cylinder__title">keep scrolling to see the animation</p>
  <ul class="cylinder__text__wrapper">
    <li class="cylinder__text__item">design</li>
    <li class="cylinder__text__item">development</li>
    <!-- ... 更多项目 -->
  </ul>
</section>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
.cylinder__wrapper {
  width: 100%;
  height: 100svh;
  position: relative;
  perspective: 70vw;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 10rem;
}

.cylinder__text__wrapper {
  position: absolute;
  font-size: 5vw;
  transform-style: preserve-3d;
  transform-origin: center center;
}

.cylinder__text__item {
  position: absolute;
  left: 50;
  top: 50%;
  width: 100%;
  backface-visibility: hidden;
}

使用JavaScript赋予生命

我们为这个效果创建一个名为cylinder的新文件夹,并定义我们的Cylinder类:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';

export class Cylinder {
    title: HTMLElement;
    textWrapper: HTMLElement;
    textItems: NodeListOf<HTMLElement>;
    wrapper: HTMLElement;

    constructor() {
        this.title = document.querySelector('.cylinder__title') as HTMLElement;
        this.textWrapper = document.querySelector('.cylinder__text__wrapper') as HTMLElement;
        this.textItems = document.querySelectorAll('.cylinder__text__item') as NodeListOf<HTMLElement>;
        this.wrapper = document.querySelector('.cylinder__wrapper') as HTMLElement;
        this.init();
    }

    init() {
        this.calculatePositions();
    }

    calculatePositions(): void {
        const offset = 0.4;
        const radius = Math.min(window.innerWidth, window.innerHeight) * offset;
        const spacing = 180 / this.textItems.length;

        this.textItems.forEach((item, index) => {
            const angle = (index * spacing * Math.PI) / 180;
            const rotationAngle = index * -spacing;

            const x = 0;
            const y = Math.sin(angle) * radius;
            const z = Math.cos(angle) * radius;

            item.style.transform = `translate3d(-50%, -50%, 0) translate3d(${x}px, ${y}px, ${z}px) rotateX(${rotationAngle}deg)`;
        });
    }
}

使用ScrollTrigger为圆柱体赋予生命

现在我们创建一个名为createScrollTrigger()的新函数,将动画连接到用户的滚动位置:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
createScrollTrigger(): void {
    ScrollTrigger.create({
        trigger: this.title,
        start: "center center",
        end: "+=2000svh",
        pin: this.wrapper,
        scrub: 2,
        animation: gsap.fromTo(
            this.textWrapper,
            { rotateX: -80 },
            { rotateX: 270, ease: "none" }
        ),
    });
}

第二个效果:圆环

双圆环效果展示了如何仅使用一些巧妙的CSS和JavaScript组合实现优雅、动态的动画。

HTML结构

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<section class="circle__wrapper">
  <ul class="circle__text__wrapper__left">
    <li class="circle__text__left__item">design</li>
    <li class="circle__text__left__item">development</li>
    <!-- ... 更多项目 -->
  </ul>
  <ul class="circle__text__wrapper__right">
    <li class="circle__text__right__item">design</li>
    <li class="circle__text__right__item">development</li>
    <!-- ... 更多项目 -->
  </ul>
</section>

使用JavaScript实现圆环效果

我们创建一个名为circle的新文件夹,并定义一个Circle类来处理其所有功能:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
interface CircleConfig {
  wrapper: HTMLElement;
  items: NodeListOf<HTMLElement>;
  radius: number;
  direction: number;
}

export class Circle {
  leftConfig: CircleConfig;
  rightConfig: CircleConfig;
  centerX!: number;
  centerY!: number;

  constructor() {
    this.leftConfig = {
      wrapper: document.querySelector(".circle__text__wrapper__left") as HTMLElement,
      items: document.querySelectorAll(".circle__text__left__item"),
      radius: 0,
      direction: 1,
    };

    this.rightConfig = {
      wrapper: document.querySelector(".circle__text__wrapper__right") as HTMLElement,
      items: document.querySelectorAll(".circle__text__right__item"),
      radius: 0,
      direction: -1,
    };

    this.updateDimensions();
    this.init();
  }

  updateDimensions(): void {
    this.centerX = window.innerWidth / 2;
    this.centerY = window.innerHeight / 2;
    this.leftConfig.radius = this.leftConfig.wrapper.offsetWidth / 2;
    this.rightConfig.radius = this.rightConfig.wrapper.offsetWidth / 2;
  }

  updateItemsPosition(config: CircleConfig, scrollY: number): void {
    const { items, radius, direction } = config;
    const totalItems = items.length;
    const spacing = Math.PI / totalItems;

    items.forEach((item, index) => {
      const angle = index * spacing - scrollY * direction * Math.PI * 2;
      const x = this.centerX + Math.cos(angle) * radius;
      const y = this.centerY + Math.sin(angle) * radius;

      const rotationOffset = direction === -1 ? 180 : 0;
      const rotation = (angle * 180) / Math.PI + rotationOffset;

      gsap.set(item, {
        x,
        y,
        rotation,
        transformOrigin: "center center",
      });
    });
  }
}

使用ScrollTrigger进行动画处理

现在我们创建一个名为createScrollAnimations()的新函数,通过将其运动连接到用户的滚动位置来赋予它们生命:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
createScrollAnimations(): void {
  ScrollTrigger.create({
    trigger: ".circle__wrapper",
    start: "top bottom",
    end: "bottom top",
    scrub: 1,
    onUpdate: (self) => {
      const scrollY = self.progress * 0.5;
      this.updateItemsPosition(this.leftConfig, scrollY);
      this.updateItemsPosition(this.rightConfig, scrollY);
    },
  });
}

第三个效果:管道

第三个效果引入了"管道"或"隧道"动画,其中文本项沿深度轴堆叠,当用户在场景中向前滚动时产生3D运动感。

HTML结构

1
2
3
4
5
6
7
<section class="tube__wrapper">
  <ul class="tube__text__wrapper">
    <li class="tube__text__item">design</li>
    <li class="tube__text__item">development</li>
    <!-- ... 更多项目 -->
  </ul>
</section>

使用JavaScript添加运动

与圆柱体效果类似,我们创建一个Tube类来管理我们的3D隧道动画:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
export class Tube {
  private items: NodeListOf<HTMLElement>;
  private textWrapper: HTMLElement;
  private wrapper: HTMLElement;

  constructor() {
    this.wrapper = document.querySelector(".tube__wrapper") as HTMLElement;
    this.textWrapper = document.querySelector(".tube__text__wrapper") as HTMLElement;
    this.items = document.querySelectorAll(".tube__text__item");

    this.init();
  }

  private calculatePositions(): void {
    const offset = 0.4;
    const radius = Math.min(window.innerWidth, window.innerHeight) * offset;
    const spacing = 360 / this.items.length;

    this.items.forEach((item, index) => {
      const angle = (index * spacing * Math.PI) / 180;

      const x = Math.sin(angle) * radius;
      const y = 0;
      const z = Math.cos(angle) * radius;
      const rotationY = index * spacing;

      item.style.transform = `translate3d(${x}px, ${y}px, ${z}px) rotateY(${rotationY}deg)`;
    });
  }

  private createScrollTrigger(): void {
    ScrollTrigger.create({
      trigger: ".tube__title",
      start: "center center",
      end: "+=2000svh",
      pin: this.wrapper,
      scrub: 2,
      animation: gsap.fromTo(
        this.textWrapper,
        { rotateY: 0 },
        { rotateY: 360, ease: "none" }
      ),
    });
  }
}

结论

感谢您跟随本教程!我们探索了三种独特的3D文本滚动效果——圆柱体、圆环和管道动画——每种都展示了使用GSAP、ScrollTrigger和创意3D CSS变换构建沉浸式滚动驱动体验的不同方法。

当调整排版和颜色时,您可以为这些效果创建各种迷人的外观!

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计