卡片依次出现

卡片组滚动进入视野时,卡片一张接一张地升起就位,让网格以顺序出现而不是整块弹出。

  • GSAP
  • 卡片
  • 滚动
  • 收录于
项目 1
项目 2
项目 3
项目 4
项目 5
项目 6

AI 提示词

用 React 制作一个包含六张卡片的网格,在网格滚动进入视野时用 GSAP 依次显示。对网格使用 IntersectionObserver(threshold 0.3);进入视野时,对卡片执行 gsap.fromTo,从 { opacity: 0, y: 24 } 到 { opacity: 1, y: 0 },duration 0.6,ease "power3.out",stagger 0.08,然后断开 observer。在 gsap.context 中创建动画,并在组件卸载时 revert。添加一个小的“重播”按钮来重新播放时间轴。在 prefers-reduced-motion 下直接显示卡片,不做位移。卡片文字为“项目 1”到“项目 6”。

源代码

Demo.tsx
'use client'

import { useEffect, useRef } from 'react'
import gsap from 'gsap'
import s from './demo.module.css'

export default function StaggerReveal({ labels }: { labels: Record<string, string> }) {
  const grid = useRef<HTMLDivElement>(null)
  const tween = useRef<gsap.core.Tween | null>(null)

  useEffect(() => {
    const el = grid.current
    if (!el || window.matchMedia('(prefers-reduced-motion: reduce)').matches) return
    const ctx = gsap.context(() => {
      tween.current = gsap.fromTo(
        `.${s.card}`,
        { opacity: 0, y: 24 },
        { opacity: 1, y: 0, duration: 0.6, ease: 'power3.out', stagger: 0.08, paused: true },
      )
    }, el)
    const io = new IntersectionObserver(
      ([entry]) => {
        if (!entry.isIntersecting) return
        tween.current?.play()
        io.disconnect()
      },
      { threshold: 0.3 },
    )
    io.observe(el)
    return () => {
      io.disconnect()
      ctx.revert()
    }
  }, [])

  return (
    <div className={s.wrap}>
      <div ref={grid} className={s.grid}>
        {[1, 2, 3, 4, 5, 6].map((n) => (
          <div key={n} className={s.card}>
            {labels.item} {n}
          </div>
        ))}
      </div>
      <button type="button" className={s.replay} onClick={() => tween.current?.restart()}>
        {labels.replay}
      </button>
    </div>
  )
}
demo.module.css
.wrap {
  display: grid;
  justify-items: center;
  gap: 12px;
  width: min(210px, 100%);
}

.grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 8px;
  width: 100%;
}

.card {
  display: grid;
  place-items: end start;
  aspect-ratio: 1;
  padding: 8px;
  border: 1px solid var(--stage-rule);
  border-radius: var(--radius-sm);
  background: var(--stage-surface);
  color: var(--stage-muted);
  font: 500 var(--text-xs) / 1.2 var(--font-mono);
}

.replay {
  height: 32px;
  padding: 0 12px;
  border: 1px solid var(--stage-rule);
  border-radius: var(--radius-sm);
  background: var(--stage-surface);
  color: var(--stage-ink);
  font: 500 var(--text-sm) / 1 var(--font-body);
  white-space: nowrap;
  cursor: pointer;
}

.replay:hover { border-color: var(--stage-accent); }
.replay:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 2px; }

投稿创意

看到想收录的动效或主题?简单描述一下,会打开一个预先填好的 GitHub Issue,确认后即可提交。

你想投稿的是?

需要 GitHub 账号,投稿内容会公开。