为动而生:深入解析Eduard Bodak作品集的动画实现
本文来自Eduard Bodak的实践分享,详细介绍了如何为他的作品集网站打造滚动驱动和交互式动画。
概述
我将带您深入了解我网站上的三个核心GSAP动画:
- 滚动时翻转的3D卡片
- 价格页面响应鼠标移动的交互式卡片
- 滚动时微妙旋转的圆形卡片布局
我将分享每个动画的实现方式、技术决策背后的思考以及过程中学到的经验。
技术选型
我在项目中使用了Locomotive Scroll V5来处理滚动进度和视口检测。它通过数据属性和CSS变量提供内置的进度跟踪功能,我选择直接使用这些功能来触发动画。
Locomotive Scroll的简洁性让我非常喜欢。只需添加数据属性即可指定元素在视口中的触发偏移量,还可以通过数据属性在元素上获取范围在0到1之间的--progress
CSS变量,仅凭CSS就能实现大量动画效果。
滚动触发的3D卡片翻转
我将动画分为两部分:第一部分是卡片在滚动时逃逸,第二部分是卡片返回并翻转。
第一部分:卡片滚动逃逸
HTML结构:
1
2
3
4
5
6
7
8
9
|
<section data-scroll data-scroll-offset="0%, 25%" data-scroll-event-progress="progressHero" data-hero-animation>
<div>
<div class="card" data-hero-animation-card>
<div class="card_front">...</div>
<div class="card_back">...</div>
</div>
<!-- 更多卡片 -->
</div>
</section>
|
JavaScript实现:
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
|
// 事件监听器处理进度
this.handleProgress = (e) => {
const { progress } = e.detail;
this.timeline?.progress(progress);
};
window.addEventListener("progressHero", this.handleProgress);
// 构建时间线动画
computeDesktopTimeline() {
const progress = this.timeline?.progress?.() ?? 0;
this.timeline?.kill?.();
this.timeline = null;
gsap.set(this.heroCards, { clearProps: "all" });
requestAnimationFrame(() => {
this.timeline = gsap.timeline({ paused: true });
this.heroCards.forEach((card, index) => {
// 动画逻辑实现
this.timeline.to(card, {
force3D: true,
keyframes: {
"75%": { x: () => -position * (card.offsetWidth * 0.9), rotationZ: rotationZValues[index] },
"100%": { y: () => targetY - yOffsets[index], scale: 0.85, rotateX: -16 }
}
}, index * 0.012);
});
});
}
|
第二部分:卡片返回翻转
使用粘性容器结构:
1
2
3
4
5
6
7
|
<section data-scroll data-scroll-offset="5%, 75%" data-scroll-event-progress="progressService" data-service-animation>
<div class="service_container">
<div class="service_sticky">
<!-- 卡片结构 -->
</div>
</div>
</section>
|
鼠标移动卡片旋转
卡片结构:
1
2
3
4
|
<div class="card" data-price-card>
<div class="card_front">...</div>
<div class="card_back">...</div>
</div>
|
动画初始化:
1
2
3
4
5
6
7
8
|
this.introTimeline = gsap.timeline();
this.introTimeline.fromTo(this.card, {
rotationZ: 0, rotationY: -90, y: "-4em"
}, {
rotationZ: 6, rotationY: 0, y: "0em",
duration: 1, ease: "elastic.out(1,0.75)",
onComplete: () => { this.initAnimation(); }
});
|
鼠标移动处理:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
mouseMovement() {
if (!this.card || !this.isHovering) return;
const mouseX = this.currentMouseX;
const normalizedX = (mouseX - this.centerX) / this.centerX;
const rotationY = normalizedX * this.rotationFactor;
gsap.to(this.card, {
rotationY: rotationY,
rotationZ: rotationZ,
duration: 0.5,
ease: "power2.out",
});
}
|
圆形布局卡片滚动旋转
CSS基础结构:
1
2
3
4
5
6
7
8
|
<div class="wheel" style="--wheel-angle: 15deg">
<div class="wheel_items">
<div class="wheel_item-wrap" style="--wheel-index: 0">
<div class="wheel_item">...</div>
</div>
<!-- 更多卡片 -->
</div>
</div>
|
CSS样式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
.wheel {
aspect-ratio: 1;
width: 70em;
grid-area: 1 / 1;
}
.wheel_item-wrap {
transform: rotate(calc(var(--wheel-angle) * var(--wheel-index)));
grid-area: 1 / 1;
}
.wheel_item {
transform: translateY(-100%);
aspect-ratio: 60 / 83;
width: 7.5em;
}
|
GSAP动画:
1
2
3
4
5
6
|
this.timeline = gsap.timeline({ paused: true });
this.timeline.to(this.wheel, {
rotate: -65,
duration: 1,
ease: "linear",
});
|
结论
这个项目成为了我个人学习的游乐场,充分证明了通过实践构建想象中的效果是最好的学习方式。虽然可能有更聪明的实现方式,但对于我转向GSAP、Locomotive Scroll V5、Swup.js和CSS动画后的第一个网站,我对结果相当满意。
项目中还使用了大量的CSS动画与JavaScript逻辑结合,创造了丰富多样的交互体验。