GSAP与Locomotive Scroll驱动的交互式动画实现详解

本文深入解析了Eduard Bodak作品集中三种核心动画的实现:滚动触发的3D卡片翻转、鼠标交互的卡片旋转以及圆形布局卡片的滚动旋转效果,详细介绍了技术架构和实现细节。

为动而生:深入解析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逻辑结合,创造了丰富多样的交互体验。

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