Communitygithub.com

anhlamtruong/living-ui-swarm

為 React / Next.js 建立「代理人群體」頁面建構動畫。一群帶有角色色彩的光標(規劃者、程式設計師、設計師、佈局人員、測試人員、審閱者、部署者)在每個區塊滾動進入視野時,組裝該區塊的各個部分。

living-ui-swarm 是什麼?

這是一個能夠在 React 和 Next.js 應用程式中,創建代理人群體建構頁面動畫的技能。它透過一個固定位置的 Canvas 圖層,繪製一群小型游標,模擬一群 AI 代理在頁面滾動時,協作「建構」每個區塊的過程。動畫引擎由 GSAP、Lenis 和 ScrollTrigger 驅動,並採用基於 data-attribute 的 DOM 契約來定義建構任務。此技能適合用於需要展示「由 AI 代理即時建構頁面」效果的場合,例如作品集、產品發布頁面或案例研究。它支援 Next.js App Router,並已考慮效能最佳化,包括使用 `next/dynamic` 延遲載入建構層、透過 `sessionStorage` 記住建構狀態以避免在頁面重新載入時重播動畫,以及提供響應式設定以適應不同螢幕尺寸和網路狀況。

相容平台~Claude Code~Codex CLI✓Cursor
npx skills add https://github.com/anhlamtruong/living-ui-skills/tree/HEAD/skills/living-ui-swarm

在你喜歡的 AI 中提問

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

預覽

來自技能 README

living-ui-swarm preview

說明文件

代理人群體建構動畫

一個固定的 Canvas 圖層會繪製出一個小型游標團隊,當頁面的每個區塊滾動進入視野時,這些游標會「建構」該區塊。內容是真實的、伺服器端渲染的 HTML。引擎僅將項目隱藏幾秒鐘,並為每個項目播放一段簡短的編排動畫。

線上範例:https://www.lamanhtruong.com。其原始碼位於 portfolio-anh@a5d1651。 assets/swarm/ 包含相同的引擎,已清理乾淨以便重複使用。

何時使用,何時不使用

在頁面本身作為宣傳重點時使用:作品集、發布或產品故事頁面、案例研究、「關於我們的 AI」頁面。人們會訪問一次,從上到下滾動,而建構過程告訴他們一些事情(例如「這個人使用代理群體來發布產品」)。

跳過它來用於人們每天使用的東西:儀表板、文件、設定、結帳、長篇閱讀。建構過程每個區塊需要幾秒鐘,回訪用戶沒有理由觀看兩次。這就是為什麼引擎會記住每個分頁已完成建構(透過 sessionStorage)並且在重新載入時絕不重播。

它帶來的成本:

  • 大約 5,000 行 TypeScript。建構層使用 next/dynamic 在 hydration 之後載入,因此首次繪製從不等待它;平滑滾動和視差組件(Lenis、GSAP、ScrollTrigger)會與頁面一起載入。一個全新的 Next.js 15 應用程式,包含所有四個 living-ui 技能,其路由 JS 約為 67 kB(首次載入 170 kB)。
  • 在即時日誌填入時,會產生一個小的佈局偏移分數(在測試頁面中約為 0.03,遠低於 0.1 的「良好」門檻):新的日誌行在其固定高度的框內移動。頁面的其餘部分從不偏移。
  • 一個額外的固定 Canvas 和一個 gsap.ticker 迴圈。當沒有東西在移動時,迴圈會停止。
  • GSAP 3.13+(免費,包含 ScrollTrigger)、@gsap/react、Lenis。

用一段話解釋其運作原理

<head> 中的一個小型內聯腳本在首次繪製之前將 html[data-build="on"] 設定為啟用,然後 CSS 透過 opacity: 0 隱藏所有未建構的 [data-build-item]。使用 opacity 而非 display 或 visibility 意味著沒有佈局偏移,也不會從無障礙樹中移除任何元素。Hydration 之後,引擎掃描頁面,為每個 [data-build-section] 建立一個任務圖,為每個區塊設定一個 ScrollTrigger,當某個區塊進入視口時,依次執行規劃 → 建構 → 審閱:

  • 規劃: 規劃者繪製引導線並分派任務。
  • 建構: 工作者根據距離、角色和關鍵路徑認領任務,每個任務播放一個 GSAP 時間軸。
  • 審閱: 審閱者勾勒區塊輪廓,勾選清單並蓋上印章。

完成的項目和區塊會獲得 [data-built] 屬性。如果引擎從未啟動,標頭腳本會在 3 秒後移除該標誌,以免任何內容保持隱藏。

架構、資料流程以及如何擴展:references/architecture.md。

安裝 (Next.js App Router)

  1. 依賴項。 已在 Next.js 15.1 + React 18.3(實際範例)和 Next.js 15.5 + React 19.1 + Tailwind 4(全新的 create-next-app)、TypeScript 5.8–5.9、[email protected]–3.15、@gsap/[email protected]、[email protected] 上測試通過:

    npm i gsap@^3.13 @gsap/react@^2.1 lenis@^1.3
    
  2. 複製引擎。 將 assets/swarm/ 複製到 src/components/swarm/。內部的導入是相對路徑,因此任何資料夾都可以使用。

  3. 將標誌腳本 添加到根佈局的 <head>。它在 React hydrating 之前為 <html> 設定一個屬性,因此請為 <html> 添加 suppressHydrationWarning:

    // app/layout.tsx
    import { BuildFlagScript } from "@/components/swarm/build-flag-script";
    
    export default function RootLayout({ children }: { children: React.ReactNode }) {
      return (
        <html lang="en" suppressHydrationWarning>
          <head>
            <BuildFlagScript />
          </head>
          <body>{children}</body>
        </html>
      );
    }
    
  4. 掛載引擎一次,在使用它的頁面上,內容之後:

    // app/page.tsx (一個伺服器組件;保持靜態)
    import "@/components/swarm/swarm.css";
    import { SwarmRoot } from "@/components/swarm/swarm-root";
    
    export default function Page() {
      return (
        <>
          <main>{/* 使用 data-build-* 標記的區塊 */}</main>
          <SwarmRoot />
        </>
      );
    }
    

    SwarmRoot 也會掛載由 gsap.ticker 驅動的 Lenis 平滑滾動。如果應用程式已經運行了 Lenis,請從 swarm-root.tsx 中刪除 <SmoothScroll />。 build-layer.tsx 透過 useLenis() 讀取實例,這需要頁面上某處有一個 root ReactLenis 實例(<ReactLenis root />,如同 smooth-scroll.tsx 所掛載的)。它不一定是父組件。

  5. 保持路由靜態。 引擎僅裝飾已存在的 HTML。不要在頁面的佈局樹中調用 cookies()、headers() 或 auth() 來讀取 session。而是在客戶端獲取這些資料,這樣頁面可以預渲染並從 CDN 提供服務。在沒有掛載 SwarmRoot 的頁面上,標誌腳本是無害的:沒有元素帶有 data-build-item,並且故障安全機制會清除該標誌。

  6. 設定主題。 設定 CSS 變數(--swarm-font-mono、--swarm-accent、--swarm-text、--swarm-muted、--swarm-dim、--swarm-line、--swarm-neutral-text|fill|stroke),然後編輯 palette.ts 中的 COLORS 和 ROLE_COLOR。Canvas 無法在每一幀上廉價地讀取 CSS 變數,因此其調色板存在於 TS 中。

標記頁面

一切均由伺服器渲染標記上的 data 屬性驅動。完整的參考資料,包含預設值和手機行為,位於 references/dom-contract.md。用於 hero、卡片網格、列表、圖片或影片、指標條和結局的可複製範本,位於 references/recipes.md。

屬性套用在意義
data-build-section="name"區塊包裝器一個建構單元,有自己的規劃、團隊和審閱
data-build-start="load"區塊在啟動後立即建構,而非等待滾動,並享有更大的 hero 預算
data-build-item + `data-build="frametypereveal
data-build-list列表容器其第一個項目是主體 (frame);dup 兄弟元素從它複製
data-build-grid容器或區塊本身佈局代理會先繪製網格並框出每個單元格
data-build-font / -weight / -scaletype 項目先使用等寬草稿字型,然後進行字體切換;權重掃描;從小型開始,然後調整大小和移動
`data-build-color="textfillstroke"`
data-build-tuneframe開啟圓角和內距檢查器
`data-build-from="leftrighttop
data-build-keep / ="mobile"任何項目永不隱藏(手機版僅用 mobile),但仍會被註解
data-build-label、-order、-role任何項目標籤文字、排序提示、由哪個角色負責
data-swarm-spawn="role"Hero 中的 SVG <g>團隊在啟動時彈出的網格節點(≥1024 px)
data-swarm-log容器伺服器渲染的備用行會被替換為即時日誌
data-build-finale + data-deploy-target + data-deploy-text最後一個區塊及其 CTA部署者在最後一次審閱後「發布」頁面
?build=off / ?build=forceURL對此視圖停用 / 即使此分頁已觀看過也強制重播

引擎會設定:html[data-build="on"] 在活躍時,[data-built] 在完成的項目和區塊上,[data-lit] 在啟動期間的網格節點上,以及 [data-deployed] 在結局目標上。

保持其運作的法則:

  • 區塊大小。 每個區塊約一個視口的項目。分割長頁面:時間軸中的每個工作一個區塊,大型網格中的每個卡片一個區塊,區塊標題一個單獨的區塊。小型區塊在其預算內完成,看起來有目的性;大型區塊會回退到追趕速度。
  • 巢狀結構。 巢狀在另一個項目內部的項目會等待其父項目。將卡片標記為 frame,並將其標題、文字和晶片標記在內部。
  • 類型。 僅對短文字(少於約 120 個字元)使用 type。較長的文字會自動回退到 reveal。打字會讀取字形框一次,因此 hydration 後變化的 React 管理文字(計數器、觀看次數)應該是 frame,而不是 type。
  • LCP。 在手機上使用 data-build-keep="mobile" 保持主標題可見。在手機上,hero 標題是 Largest Contentful Paint,隱藏它會損害您的 Core Web Vitals。桌面版仍可獲得完整的字體切換建構。
  • 即時日誌。 data-swarm-log 容器必須是伺服器渲染的,帶有真實的備用行和固定高度 (.swarm-log)。引擎將它們替換為即時行,並在跳過或卸載時恢復它們。

最小 hero 範例:

<section data-build-section="hero" data-build-start="load">
  <p data-build-item data-build="type">your eyebrow line</p>
  <h1
    data-build-item data-build="type" data-build-font data-build-weight
    data-build-keep="mobile" data-build-order="-1" data-build-role="coder"
  >
    Your Name <span className="serif">(Accent)</span>
  </h1>
  <div data-build-item data-build="image" data-build-from="right" className="avatar">
    <img src="/me.jpg" alt="Portrait" />
  </div>
  {/* next/image 也可以(它會渲染一個引擎可讀取的內部 <img>)。 */}
  <div data-build-list className="buttons">
    <a data-build-item data-build="frame" href="#">GitHub</a>
    <a data-build-item data-build="dup" href="#">LinkedIn</a>
    <a data-build-item data-build="dup" data-build-color="fill" href="#">Resume</a>
  </div>
</section>

平滑度法則

這些法則來自觀察真實的建構過程。每一個都修復了一個可見的問題。

  • 永遠不要在截止時間強制完成。 早期版本會將所有正在運行的時間軸跳到 5 倍速,並在每個區塊的截止時間彈出未認領的項目。人們在每個區塊結束時會看到一個卡頓。現在預算是規劃目標。
    • estimate() 會縮放任務持續時間,以使計畫符合預算。
    • 當一個區塊延遲時,pace() 會平穩地增加追趕因子,最大在 0.7 秒內達到 1.7 倍。
    • 即使在完全追趕的情況下,每個補間動畫至少保持 120 毫秒長,打字步驟至少保持 16 毫秒。不會有彈出效果。
    • 如果您需要更快,請讓游標更快(palette.ts 中的 speed)。不要縮短動畫。
  • 僅對 opacity、clip-path、filter 和 transform 進行動畫處理。 沒有佈局寫入。字體切換草稿是脫離流程圖層中的克隆,並且縮放任務會在結束時移除其內聯的 transform。
  • 一個時鐘。 模擬和 Canvas 繪製在單一的 gsap.ticker 回調中運行。React 狀態僅在建構開始或停止時更改,而不是每幀更改。當沒有代理在螢幕上時,ticker 會停止。
  • 提前測量。 在折疊線下方約兩個視口範圍內的區塊,會在 requestIdleCallback 片段中進行測量。啟動的區塊絕不會在滾動幀上引起大的佈局讀取爆發。
  • 尊重閱讀者。
    • 聚焦未建構區塊內的任何內容,會立即完成該區塊,這樣鍵盤用戶就不會等待。
    • 建構過程中的大小調整事件會完成正在運行的區塊,因為它們測量的矩形已過時。
    • 隱藏的分頁會暫停所有內容。
    • HUD 始終提供跳過、重播和檢查功能。
  • 手機獲得較少內容。 每個設定檔都有自己的預算、團隊和細節(palette.ts 中的 detectProfile())。
    • 手機:8 個代理,每個區塊約 2.2 秒,每頁一個字體切換,每個區塊一個顏色選取,每個列表一個 dup 掃描,無檢查器面板,無縮放任務,無部署器。
    • 平板:11 個代理。
    • 桌面:15 個代理,每個區塊約 3.9 秒,hero 約 5.25 秒。

用於「更快」、「更平靜」和「更多代理」的旋鈕:references/tuning.md。

減少動畫

引擎會為每位訪客播放動畫,包括那些在作業系統中要求減少動畫的訪客。網站所有者認為建構過程是頁面的重點,並且 HUD 始終提供跳過功能,因此這是一個刻意的選擇,而不是一個可以盲目複製的預設值。若要改為尊重此設定,請進行以下所有三項更改:

// build-flag-script.tsx: 在 CODE 內部,d.setAttribute('data-build','on') 之前
if (matchMedia('(prefers-reduced-motion: reduce)').matches) return;

// motion.ts
export function offByUrl(): boolean {
  return OFF_RE.test(window.location.search) ||
    window.matchMedia("(prefers-reduced-motion: reduce)").matches;
}

// smooth-scroll.tsx
respectReducedMotion: true,

兩種情況都測試:某些瀏覽器(以及某些自動化設定)即使沒有人選擇,也會報告 reduce,這就是為什麼此處的預設值是「啟用」。

與其他效果協作

  • 以 [data-built] 為門檻。 引擎在建構時會讀取元素矩形並切換 opacity 和 clip-path。傾斜、故障、亂碼、內容視差或任何其他轉換項目的效果,如果過早運行,都會破壞這些測量。僅當項目及其區塊帶有 [data-built] 時,或當 html[data-build] 不是 "on" 時,才啟動此類效果。一個現成的輔助函式:

    export function isBuilt(el: Element): boolean {
      if (document.documentElement.getAttribute("data-build") !== "on") return true;
      const item = el.closest("[data-build-item]");
      if (item && !item.hasAttribute("data-built")) return false;
      const section = el.closest("[data-build-section]");
      return !section || section.hasAttribute("data-built");
    }
    
  • 影片。 一個包裝了 <video poster> 的 image 項目,會使用其海報幀作為縮圖拖入。為包裝器提供一個 data-build-label,例如 clip.mp4,因為沒有 <img> 可以讀取檔案名稱。

  • 裝飾性視差 可以在任何僅移動裝飾元素的地方正常使用。切勿將其放在引擎要測量的內容上。

讓它成為你自己的

這裡的引擎是一個原創實作,同樣的標準適用於您從它建構的任何東西。如果某個網站啟發了您,請記錄下什麼使該網站具有辨識度,並遠離這些確切的東西。借鑑

…

[為翻譯而截斷]

Individual skills in this repo

This repo contains 1 individual skill — each has its own dedicated page.

相關技能