AI 工具 24 min read

animate-expo skill 實測:5 組對照,點名版拒做分頁滑動

animate-expo 是 Emil Kowalski 寫給 AI 的 Expo 動效規範,管手勢、sheet 與觸覺回饋。同一句需求跑 5 組 Claude Code,裝了的 3 組都用上 spring 與減少動態,沒裝的 2 組是 0,點名版還拒做分頁滑動。

animate-expo skill 實測:5 組對照,點名版拒做分頁滑動
本文目錄 · 10
懶人包
  • animate-expo 是 Emil Kowalski 收在 emilkowalski/skills(MIT)裡的 Claude Code skill,2026-08-18 加入,是網頁版 animate 的 React Native/Expo 版本:一份 263 行的 SKILL.md 加一份 385 行的 RECIPES.md,管手勢、sheet、觸覺回饋和「動畫不要跑在 JS thread」。
  • repo 在 2026-10-21 是 48,157 stars、2,785 forks,這是 15 支 skill 共用的數字,不是 animate-expo 一支的。
  • 我用同一句需求(自訂分頁滑動 + 可拖曳關閉的篩選 sheet + 有觸覺的選項列)跑了 5 組 Claude Code(Sonnet):沒裝 skill 的 2 組 dampingRatio、動量投射、橡皮筋、減少動態全部是 0;裝了的 3 組每組都有。
  • 它的 description 寫得夠準:沒點名的 2 次都在第一步就自己載入,比我之前測 apple-design 的 3 次中 1 次穩。
  • 最大差別在點名版:需求前面打 /animate-expo,它直接拒做分頁左右滑動,理由是 skill 規定一天用上百次的操作不做動畫。沒點名的版本只在回覆裡提醒一句,照樣做了。
  • 限制:這台是 Windows、沒有 iOS 模擬器,5 組都只跑過 npx tsc --noEmit(全部 exit 0),只做程式碼層比對、未在裝置上執行,手感好不好我沒有驗。
  1. animate-expo 是什麼、解決什麼問題?
  2. 它什麼時候會被叫出來?
  3. 裡面有哪些檔案?
  4. 核心規則逐條在講什麼?
  5. 跟 animate 怎麼分工?
  6. 怎麼安裝、怎麼點名?
  7. 實測:同一句需求,5 組產出差在哪
  8. 有哪些限制和坑?
  9. 誰適合用、誰不適合?
  10. 延伸資源

📱 animate-expo 是什麼、解決什麼問題?

animate-expo 是一份給 AI coding agent 讀的規範,主題只有一個:在 Expo/React Native 裡寫動畫時,按照「決定手感的順序」做選擇。該不該動、跑在哪個 thread、動哪些屬性、用 spring 還是固定曲線、手勢放手時速度怎麼接、使用者開了減少動態要怎麼退化。

它要解決的問題寫在開頭。手機跟網頁有三個根本差異:沒有 hover;有兩個 runtime(React 跑的 RN runtime 和每幀執行 worklet 的 UI runtime);使用者的手指就壓在元素上。所以 skill 把「動畫碰到 RN runtime 就會在 App 忙的時候卡」當成最核心的問題,整份規範都在把動畫留在 UI runtime。

它對自己的驗收標準講得很硬:手感只在 release build、你支援的最慢機型 上判斷,Expo Go 和模擬器都不算數。這一條等一下會回來,因為我的實測正好做不到。

emilkowalski/skills 的 GitHub 頁面,animate-expo 資料夾裡只有 RECIPES.md 和 SKILL.md 兩個檔案

repo 的 git 紀錄:animate-expo 由 Emil Kowalski 在 2026-08-18 以 PR #25 加入,commit 說明寫著「Address Expo team review feedback」,列了 scheduleOnRN、.get()/.set()、Gesture Handler v3 說明、npx expo install、NativeTabs 等修正。之後 2026-09-15 加 mobile-native 時補了 8 行分工說明,同一天刪掉課程連結。我讀的版本是 repo HEAD 2d22863(2026-10-11)。

🎯 它什麼時候會被叫出來?

frontmatter 只有 name 和 description 兩個欄位,沒有設 disable-model-invocation。同 repo 只有 pick-ui-library、prototype、review-animations 三支設了這個旗標,animate-expo 不在其中,所以模型可以自己判斷要不要載入。

description 的觸發範圍是:在 Expo App 裡做任何動畫、加手勢、sheet、畫面轉場、按壓回饋或觸覺回饋,以及修「在裝置上會卡」的動畫。最後一句寫明網頁動畫交給 animate。

還有一個細節:SKILL.md 有一段 Initial Response,規定被叫出來時如果沒有具體問題,只回一句英文開場白、其他什麼都不說。所以只打 /animate-expo 不接需求,得到的就是那一句話。要把需求接在後面打。

📂 裡面有哪些檔案?

檔案行數內容
SKILL.md2639 步建構流程、spring 與曲線參數表、觸覺對照表、設定檢查、120fps、Never Ship 表、輸出格式
RECIPES.md385可以直接改的實作:按壓回饋、拖曳關閉的 sheet、滑動刪除、收合 header、列表進場、鍵盤同步、分頁指示器、Expo Router 轉場、toast、閾值觸發
RECIPES.md 開頭先定了共用的兩個 worklet:project()(用 0.998 減速率推算放手後的慣性位移)和 rubberband()(係數 0.55 的邊界阻力)。SKILL.md 裡寫「需求對得上食譜就先載入它,從食譜開始寫,不要從空白檔開始」。

📋 核心規則逐條在講什麼?

以下是我讀完兩份檔案後的轉述,按 SKILL.md 的順序。

1. 先問該不該動。 一天用上百次的操作(切分頁、開關鍵盤、捲動、設定裡的開關)不做動畫;一天幾十次的(按壓、列表導覽)只做 150ms 以內幾乎感覺不到的;偶爾用的(sheet、modal、toast)才給標準動畫;驚喜感留給成功畫面、空狀態這類少見時刻。分頁切換永遠不滑動,理由是分頁是平行關係,滑動會暗示不存在的層級。不過門檻就停止,一行都不寫。

2. 說得出目的才做。 回饋、空間一致性、狀態提示、避免突兀、說明、驚喜,六選一,說不出來就不做。

3. 選最便宜的工具。 由上往下找:狀態改變用 Reanimated 的 CSS transition(transitionProperty);迴圈或掛載時播放用 CSS animation;元素進出用 layout animation;手指碰得到的才用 useSharedValue + Gesture。畫面轉場、formSheet、分頁列、長按選單、大標題收合都交給 Expo Router 的原生能力,不要用 JS 重刻。

4. 只動 transform 和 opacity。 其他屬性每幀都會重算版面。例外是絕對定位、沒有子元素的東西(例如分頁指示條)。不要 scale(0) 進場,從 0.9~0.97 加透明度開始。Android 的 elevation 和 BlurView 強度不要直接動畫。

5. 手指參與過就用 spring。 參數用 Apple 式的 duration + dampingRatio,不用 mass/stiffness/damping。表格寫死三組:一般歸位 400ms/1、拖曳後彈回 400ms/0.8 帶速度、sheet 300ms/0.8 帶速度。沒有手指的動畫用三條指定的 bezier 曲線,UI 動畫不超過 300ms,禁止 ease-in。

6. 不要佔 JS thread。 手勢裡不 setState;runOnJS 換成 scheduleOnRN,而且不能每幀呼叫;shared value 用 .get()/.set(),不要直接碰 .value,也不要在 render 期間讀寫;被 worklet 呼叫的函式要標 'worklet'。

7. 按壓取代 hover。 按下就給回饋、放開才確定;可按的東西 100~150ms 縮到 0.97;觸控區至少 44pt,視覺太小就加 hitSlop;用 pressRetentionOffset 讓手指稍微滑動不會取消按壓。

8. 觸覺回饋三條鐵律。 跟畫面同一幀觸發、一個動作只觸發一次、不能是唯一的回饋(系統可能關掉觸覺,Android 硬體也常常沒反應)。用哪一種 API 有對照表:刻度跳動用 selection,歸位或 sheet 卡住用 Light impact,成功失敗用 notification。

9. 減少動態跟動畫一起出貨。 用 useReducedMotion 或 ReduceMotion.System。減少動態的意思是「更少、更輕」,保留透明度和顏色變化,拿掉位移、縮放和回彈。

SKILL.md 後半還有設定檢查清單(GestureHandlerRootView 沒包手勢就靜默失效、Reanimated 4 需要 New Architecture、Expo Go 不能拿來判斷效能),以及 ProMotion iPhone 要設 CADisableMinimumFrameDurationOnPhone 才能跑 120fps。

🔀 跟 animate 怎麼分工?

animate 的 SKILL.md 自己寫明:React Native 交給 animate-expo。mobile-native 的 description 也寫「React Native 用 animate-expo」。三支的分工很清楚:

skill管什麼
animate網頁動畫,CSS、WAAPI、Motion
animate-expoReact Native/Expo 動畫,Reanimated、Gesture Handler、Expo Router、expo-haptics
mobile-native網頁在手機瀏覽器上的問題,不是寫原生 App
兩支 animate 的骨架幾乎一樣:都是「先判斷該不該動 → 目的 → 工具 → 屬性 → 曲線或 spring」的順序,都附一份 RECIPES.md,都有禁用清單。animate-expo 多出來的是手機特有的三塊:thread 規則、按壓取代 hover、觸覺回饋。寫網頁版的部分可以看我拆 animate 的那篇;手機瀏覽器的問題看 mobile-native。

⚙️ 怎麼安裝、怎麼點名?

README 的整包安裝是 npx skills@latest add emilkowalski/skills,會進互動式選單。只裝 animate-expo 一支、裝給 Claude Code,我在乾淨資料夾實際跑過的是這條(2026-10-21,skills@latest 解析到 1.7.4):

npx -y skills@latest add emilkowalski/skills --skill animate-expo --agent claude-code --copy -y

跑完會多出:

.claude/skills/animate-expo/SKILL.md
.claude/skills/animate-expo/RECIPES.md
skills-lock.json

我用 diff -r 比對過,裝下來的兩個檔案跟 repo 2d22863 版本完全相同。

點名方式:在需求前面加 /animate-expo,例如 /animate-expo 幫篩選面板做一個可以往下拖曳關閉的 bottom sheet。要確認 skill 有沒有被看到,可以用 claude -p ... --output-format stream-json --verbose 跑,第一行 init 事件的 skills 清單會列出 animate-expo。

🧪 實測:同一句需求,5 組產出差在哪

怎麼測的

我用 create-expo 開了一個空白 TypeScript 專案,再用 npx expo install 裝好 Reanimated、Worklets、Gesture Handler、expo-haptics,複製成 5 份。版本:

expo ~58.0.16 / react-native 0.88.0 / react 19.3.0
react-native-reanimated 4.7.1 / react-native-worklets 0.13.0
react-native-gesture-handler ~3.3.0 / expo-haptics ~58.0.7
Claude Code 2.1.306 / --model sonnet

每一組都用同一句需求,在背景跑 claude -p "<需求>" --setting-sources project --model sonnet --dangerously-skip-permissions,設 15 分鐘 timeout。需求原文:

這是一個 Expo(TypeScript)App,請直接修改 App.tsx 完成以下互動:
1. 畫面底部有兩個自訂分頁按鈕「首頁」「設定」,切換分頁時讓內容左右滑動切換。
2. 首頁有一個「篩選」按鈕,點了從底部彈出一個 bottom sheet,可以用手指往下拖曳關閉;sheet 裡有 4 個可點的選項列,點選要有按壓回饋與觸覺回饋。
相依套件已經裝好(react-native-reanimated、react-native-worklets、react-native-gesture-handler、expo-haptics),不要安裝或升級任何套件。寫完執行 npx tsc --noEmit 確認型別通過就結束,最後用五行以內說明你做了什麼。

第 1 點是我故意放的:它直接撞上 skill 的「分頁不滑動」規則,可以看出 skill 有沒有真的被遵守。

組別條件結果
A沒裝 skill跑到一半碰到用量上限中斷,但 App.tsx 已寫完,我補跑 tsc 通過
A2沒裝 skill(重跑)完整跑完
B1、B2裝了、沒點名兩次都在第一步自己呼叫 Skill 工具載入 animate-expo
C2裝了、需求前面加 /animate-expo直接讀 SKILL.md 和 RECIPES.md 的 sheet 食譜
另有一組 C 因為指令錯誤以 exit 127 失敗、沒有任何輸出,不列入比較。5 組產出的 npx tsc --noEmit 全部 exit 0。

數字差異

5 組 App.tsx 的關鍵字計數:沒裝 skill 的 A、A2 在 dampingRatio、project、rubberband、減少動態都是 0,裝了的三組都有;只有 C2 拒做分頁滑動

這張表只能說明「有沒有寫出某種寫法」,不代表手感好壞。幾個值得看的點:

  • spring 參數形式:A 用的是 stiffness/damping,A2 整份沒用 spring,sheet 開啟是 350ms 的 withTiming。裝了 skill 的三組都改用 { duration, dampingRatio },數值落在 skill 表格的 300/0.8 和 300/1。
  • shared value 存取:沒裝的兩組都直接用 .value(19 次和 12 次),裝了的三組全部改成 .get()/.set(),.value 歸零。這是 skill 第 6 步的規則。
  • 減少動態:沒裝的兩組完全沒處理。裝了的三組都有,C2 在減少動態時把 sheet 改成 150ms 淡入。
  • 按壓:三組都加了 pressRetentionOffset,按壓縮放用 Reanimated 的 CSS transition 寫,對上 skill「按壓用 CSS transition,不用 worklet」的規則。
有一點要公平講:沒裝 skill 的 A2 已經不差。它用的 Easing.bezier(0.23, 1, 0.32, 1) 跟 skill 指定的 ease-out 曲線一模一樣,關閉條件也是「距離或速度」二擇一。差別集中在放手那一刻。

放手那一刻差在哪

A2 與 C2 的 sheet 手勢程式碼對照:A2 彈回用 250ms withTiming、往上拖乘 0.1;C2 用 project 推算慣性、spring 帶入手指速度、rubberband 阻力,彈回時觸發一次 Light 觸覺

A2 放手後如果沒達關閉條件,用固定 250ms 的曲線彈回,手指當下的速度沒有傳進去。C2 把放手位置加上 project() 推算的慣性位移再判斷要不要關,關或彈回都用帶入 velocity 的 spring,彈回時用 scheduleOnRN 觸發一次 Light 觸覺,剛好是 skill 觸覺對照表裡「歸位」那一格。

這些差異在程式碼上看得出來,在手上感覺起來是不是真的更好,我沒辦法在這台機器上驗。

點名與沒點名:分頁滑動的處理不一樣

這是我覺得最有意思的結果。三組裝了 skill 的回覆對「分頁要左右滑動」這個需求的處理:

組別分頁回覆怎麼說
B1250ms 滑動「skill 建議分頁切換不要滑動,這裡照你的要求做」,減少動態時改淡入
B2250ms 滑動「分頁切換頻率很高,通常不建議滑動」
C2不滑動「分頁切換一天會用上百次,animate-expo 規則明確禁止,所以只做即時切換……如果你堅持要滑動,跟我說,我會加上去。」
沒點名的時候,模型把 skill 當參考資料,使用者的明確要求優先。點名之後,模型把 skill 當成要遵守的規則,直接否決需求裡的一條。哪一種比較好看你的情境:想要它當把關者就點名,想要它照做再附建議就別點名。每種條件只跑了 1~2 次,我不會說這是固定行為。

觸發率

沒點名的 B1、B2 都在第一步就呼叫了 Skill 工具。我在 apple-design 那篇用 bottom sheet 需求測,沒點名 3 次只有 1 次觸發。兩支 description 的差異是 animate-expo 寫明了「anything in an Expo app」,而我的需求第一句就是「這是一個 Expo App」,比對起來很直接。樣本都很小,只能說這次的條件下它比較穩。

花費

組別時間回合費用(stream-json 回報)
A2 沒裝104 秒12US$0.265
B1 自動103 秒9US$0.278
B2 自動116 秒12US$0.306
C2 點名124 秒11US$0.299
載入 skill 多讀了幾百行,費用多了大約一到四成,時間差距不大。

🕳️ 有哪些限制和坑?

驗收標準我做不到。 skill 自己說只有 release build 跑在最慢的支援機型上才算驗證過。我只有 Windows,沒有 iOS 模擬器,也沒有接 Android 手機,5 組都停在型別檢查。回覆裡模型自己也都寫了「還沒在真機上跑過」,並列出要實機試的項目(甩動 sheet、拖到一半抓住、在低階 Android 上看順不順),這正是 skill 的 Output 段要求的。

Gesture Handler 版本跟食譜的說明對不上。 RECIPES.md 寫「Expo 裝的是 v2,食譜用 v2 的 Gesture.Pan() 寫法」,再附一段 v3 的移植說明。但我在 SDK 58 用 npx expo install 裝到的是 ~3.3.0。B2、C2 都正確改用 v3 的 usePanGesture;B1 照食譜用了 Gesture.Pan(),在 v3 是 legacy 寫法,型別檢查照樣通過。用新版 SDK 時可以在需求裡寫明「gesture-handler 是 v3」。

transitionProperty 放進 StyleSheet.create 會報型別錯誤。 B1 撞到這個,把按壓樣式移出 StyleSheet.create 改成獨立常數才通過 tsc。食譜裡的寫法本身沒放進 StyleSheet.create,照抄不會出事,但要合併進既有樣式表時會遇到。

單打 /animate-expo 只會得到一句英文。 這是 Initial Response 的規定,要把需求接在後面。

我的實驗環境有共同變因。 我機器上有一份 CLAUDE.md 被讀進去了:A2 的回覆寫著「CLAUDE.md 要求前端先載入 apple-design skill,但這個環境的 skill 清單裡沒有它」。這對 5 組是同樣的條件,不影響組間比較,但你的環境跑出來不會一模一樣。

👥 誰適合用、誰不適合?

適合:

  • 用 Expo 寫 App、會請 AI 寫手勢、sheet、按壓和觸覺回饋的人。它把 spring 參數、曲線、觸覺時機都寫死成表格,AI 產出的寫法比較一致。
  • 已經升到 Reanimated 4 的專案。scheduleOnRN、.get()/.set() 這些是 Reanimated 4 的寫法,正好對得上。
不適合:
  • 網頁專案:用 animate。
  • 還在 Reanimated 3 或舊架構:Reanimated 4 需要 New Architecture,skill 的寫法會用到 4 才有的 API。
  • 原生 Swift/SwiftUI:同 repo 有 write-swift。
  • 希望 AI 完全照需求做的人:點名後它會否決不符合規則的需求,例如分頁滑動。不點名比較順從,但我也只測了兩次。

🔗 延伸資源

下一步建議:挑你 App 裡一個現成的 sheet 或可拖曳元件,用 /animate-expo 請 Claude Code 照 RECIPES.md 的 sheet 食譜重寫,打 release build 裝到你手邊最舊的那支手機上,甩一下、拖到一半抓住它。程式碼比對看得出寫法差異,手感只有手指分得出來。
author
陳
陳彥彤

AI 工程師 · AI 顧問。Java 後端 8 年、AI 工程師 2 年。AI 內訓 · AI 導入顧問 · 前後端與雲端培訓。

support

覺得文章有用可以到 GitHub 給個 star,或是透過信箱聊聊 AI 內訓、AI 導入顧問或前後端 / 雲端培訓。