Communitygithub.com

billel-chami/video-to-gsap

從影片錄製、GIF 或原型動畫(例如 Figma 原型)中解構動作編排為時間、緩動曲線、空間軌跡與交錯延遲,並在 Next.js 16 中使用 @gsap/react 與 useGSAP 實作硬體加速、無記憶體洩漏的 GSAP 動畫。

video-to-gsap 是什麼?

此技能專為從影片、GIF 或 Figma 原型中提取並實作流暢、高效能的 GSAP 動畫而設計。它自動分析動作編排,識別觸發條件、時間、緩動曲線、空間路徑與交錯延遲,並將這些分析結果轉換為可生產的 Next.js 16 元件。此技能強制執行最佳實務,例如僅對 `transform` 與 `opacity` 等複合屬性進行動畫處理以避免版面重排,使用 `@gsap/react` 的 `useGSAP` 鉤子進行範圍化、無記憶體洩漏的清理,並透過 `autoAlpha` 消除未設定樣式內容的閃爍 (FOUC)。它也內建對 `prefers-reduced-motion` 的支援,以確保無障礙性。此技能適用於 Cursor 與其他支援 SKILL.md 的 AI 代理平台,旨在幫助開發者建立具有電影級微互動的購物車、頁面轉場、卡片懸停等體驗,同時維持 60/120 FPS 的效能。

相容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/billel-chami/nextjs-medusa-handbook/tree/HEAD/.agents/skills/video-to-gsap

在你喜歡的 AI 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

說明文件

影片與原型動作提取 (video-to-gsap)

1. 目的與觸發條件

流暢的微互動與電影級動畫是高端商務體驗的關鍵。然而,實作不佳的網頁動畫會導致執行緒阻塞、掉幀 (卡頓)、記憶體洩漏以及較差的「與下一次繪製的互動」(INP)。此技能負責提取並實作順暢的 60/120 FPS 動畫。

觸發條件:

  • 當使用者提供影片片段、GIF 或 Figma 動作原型時。
  • 當需要為購物車滑出、頁面轉場、手風琴展開、產品卡片懸停或結帳完成慶祝添加動畫時。
  • 當使用 ScrollTrigger 建立滾動驅動動畫時。

2. 動作解構協定

分析動作片段時,系統性地識別:

  1. 編排順序:
    • 起始 / 觸發: 什麼動作啟動了動畫 (懸停、滾動、路由變更、點擊)?
    • 預備動作: 主要動作之前是否有微小的反向移動?
    • 動作與移動: 哪些元素移動、朝哪個方向、移動多遠 (px、rem 或 %)?
    • 安定與跟隨: 它是否會超調 (彈簧/彈性) 或平穩減速 (power3.out、expo.out)?
  2. 時間分解:
    • 每個元素的持續時間 (例如,抽屜進入:450ms,背景淡入:300ms)。
    • 交錯子元素之間的延遲偏移量 (例如,產品網格卡片:0.06s 交錯)。
  3. 緩動特徵:
    • Linear:嚴格用於連續循環 (例如跑馬燈)。
    • Ease-out (power2.out、expo.out):用於 UI 進入 (快速進入,平穩安定)。
    • Ease-in (power2.in):用於 UI 退出 (加速離開螢幕)。
    • Ease-in-out (power3.inOut):用於保持在視野中的狀態轉換。

3. 效能與硬體加速標準

規則 1:僅對複合屬性進行動畫處理

  • 允許: transform (x、y、xPercent、yPercent、scale、rotation、skew) 與 opacity (或 GSAP autoAlpha)。
  • 嚴格禁止: 對佈局或繪製屬性 (width、height、top、left、right、bottom、margin、padding、border-width) 進行動畫處理。這些屬性會強制瀏覽器 CPU 在每個影格重新計算佈局幾何並重繪像素,導致 INP 惡化與掉幀。

規則 2:使用範圍化 useGSAP 實現零記憶體洩漏

  • 始終使用官方的 @gsap/react 套件與 useGSAP 鉤子。
  • 提供一個 scope 容器 ref。內部的每個選擇器都將被自動範圍化,並且當元件卸載時,所有時間軸、補間動畫和 ScrollTrigger 實例都將被乾淨地還原和銷毀。
  • 這能防止單頁應用程式和 React 19 快速重新整理循環中的殭屍事件監聽器和記憶體殘留。

規則 3:消除 FOUC (未設定樣式內容的閃爍)

  • 使用 autoAlpha 而非 opacity。當透明度降到 0 時,autoAlpha 會設定 visibility: hidden,防止在 Hydration 之前出現幽靈點擊與渲染閃爍。
  • 或者,宣告 CSS 預設隱藏狀態,並使用 gsap.fromTo 來顯示它們。

4. 生產實作藍圖

"use client";

import { useRef } from "react";
import gsap from "gsap";
import { useGSAP } from "@gsap/react";
import { ScrollTrigger } from "gsap/ScrollTrigger";

// 確保外掛安全註冊
if (typeof window !== "undefined") {
  gsap.registerPlugin(useGSAP, ScrollTrigger);
}

interface ProductShowcaseProps {
  readonly items: Array<{ id: string; title: string; price: string }>;
}

export function ProductShowcase({ items }: ProductShowcaseProps) {
  const containerRef = useRef<HTMLDivElement | null>(null);

  useGSAP(
    () => {
      // 硬體加速的進入時間軸
      const tl = gsap.timeline({
        defaults: {
          ease: "expo.out",
          duration: 0.8,
        },
      });

      tl.from(".showcase-header", {
        autoAlpha: 0,
        y: 30,
        clearProps: "all", // 動畫後清理內聯樣式
      }).from(
        ".product-card",
        {
          autoAlpha: 0,
          y: 40,
          scale: 0.96,
          stagger: 0.08,
          duration: 0.7,
          clearProps: "transform,opacity",
        },
        "-=0.4" // 無縫重疊
      );
    },
    { scope: containerRef } // 範圍化清理邊界
  );

  return (
    <section ref={containerRef} className="w-full py-16 px-6">
      <div className="showcase-header max-w-4xl mx-auto text-center mb-12">
        <h2 className="text-h1 font-bold text-content-primary">精選系列</h2>
      </div>

      <div className="grid grid-cols-1 md:grid-cols-3 gap-8 max-w-7xl mx-auto">
        {items.map((item) => (
          <article
            key={item.id}
            className="product-card will-change-transform bg-surface-elevated rounded-lg p-6 border border-border-subtle"
          >
            <h3 className="text-h3 font-semibold text-content-primary">{item.title}</h3>
            <p className="text-content-secondary mt-2">{item.price}</p>
          </article>
        ))}
      </div>
    </section>
  );
}

5. 減少動作合規性

始終尊重偏好減少動作的使用者:

const { contextSafe } = useGSAP({ scope: containerRef });

// 檢查無障礙偏好
const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;

if (prefersReducedMotion) {
  // 立即呈現最終靜止狀態,無需動畫
  gsap.set([".showcase-header", ".product-card"], { autoAlpha: 1, y: 0, scale: 1 });
  return;
}

相關技能