使用CSS和GSAP创建3D滚动驱动文本动画
本教程将指导您使用CSS、JavaScript和GSAP构建三种滚动驱动的文本效果。无需依赖3D库,您将结合CSS变换与GSAP的ScrollTrigger插件,直接将运动与滚动位置关联,创建流畅、高性能的3D动画。
初始设置
第一步是初始化项目并设置其结构。我们将使用基于类的模型,从App类作为主入口点开始,并为每个动画使用三个独立的类。
核心是GSAP。我们将注册其ScrollTrigger和ScrollSmoother插件,这两个插件处理所有三种效果中的平滑滚动和基于滚动的动画。
主入口点将是main.ts文件:
typescript
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();
第一种效果:圆柱体
对于第一种效果,我们将文本围绕一个不可见的圆柱体定位,随着滚动而显示。
使用HTML和CSS构建结构
html
<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>
css
.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类:
typescript
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();
this.createScrollTrigger();
}
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)`;
});
}
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" }
),
});
}
resize(): void {
this.calculatePositions();
}
}
第二种效果:圆环
双圆环效果展示了如何使用CSS和JavaScript的巧妙组合实现优雅的动态动画。
HTML结构
html
<section class="circle__wrapper">
<ul class="circle__text__wrapper__left">
<li class="circle__text__left__item">design</li>
<!-- 更多列表项 -->
</ul>
<ul class="circle__text__wrapper__right">
<li class="circle__text__right__item">design</li>
<!-- 更多列表项 -->
</ul>
</section>
JavaScript实现
typescript
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",
});
});
}
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结构
html
<section class="tube__wrapper">
<ul class="tube__text__wrapper">
<li class="tube__text__item">design</li>
<!-- 更多列表项 -->
</ul>
</section>
JavaScript实现
typescript
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变换构建沉浸式滚动驱动体验的不同方法。
通过调整排版和颜色,您可以为这些效果创建各种迷人的外观!