AI 工具 25 min read

animation-performance skill 實測:抓的問題沒變多,排序變準

animation-performance 是 Emil Kowalski 以 MIT 開源的 Claude Code skill,專門診斷網頁動畫為什麼掉幀,裝了會自動觸發。我實測有無 skill 的審查,問題清單差不多,但有 skill 的 4 次都把動 width 的進度條排第 1;改成 scaleX 後 layout 從約 250 次降到 0——桌機幀率卻沒變,要實機錄影才算數。

animation-performance skill 實測:抓的問題沒變多,排序變準
本文目錄 · 10
懶人包
  • animation-performance 是 Emil Kowalski 收在 emilkowalski/skills(MIT)裡的一支診斷型 skill,只管一件事:動畫的每一幀能不能在 16.7ms(120Hz 是 8ms)內畫完。缺動畫、easing 選錯,它不管。
  • frontmatter 沒有 disable-model-invocation,可以自動觸發。我用一句「審查動畫效能」的需求、沒點名跑了 3 次,3 次都自動載入。
  • 有沒有裝它,Claude Code(Sonnet)抓到的問題清單差不多。差在排序和誠實度:有 skill 的 4 次都把「閱讀進度條在動 width」排第 1,沒 skill 的 3 次排在第 3、1、4 名;有 skill 的報告逐項標出「從程式碼確認」還是「需要實機錄影」。
  • 我在正式文章頁用 Playwright 量:把進度條從 width 改成 transform: scaleX,捲動 300 幀的 layout 從約 250 次降到 0 次;但平均幀間隔兩版都在 17ms 上下,這台桌機看不出掉幀差別。這正好是 skill 自己寫的「layout 動畫不一定會掉幀」與「CPU throttling 模擬不了弱 GPU」。
  • repo 在 2026-10-21 是 48.2k stars,這是 15 支 skill 共用的數字。安裝指令是 npx skills@latest add emilkowalski/skills --skill animation-performance。
  1. animation-performance skill 是什麼?
  2. 它什麼時候會被叫出來?
  3. SKILL.md 裡面長什麼樣?
  4. 5 條硬規則與 9 列症狀表在說什麼?
  5. 怎麼安裝、怎麼點名?
  6. 實測一:有沒有 skill,審查結果差在哪?
  7. 實測二:照它的建議改,量得到差別嗎?
  8. 適合誰、什麼情況不適合?
  9. 跟同系列的 animate、review-animations 怎麼分工?
  10. 延伸資源

🎞️ animation-performance skill 是什麼?

animation-performance 是一份給 AI coding agent 讀的 SKILL.md,222 行。作者 Emil Kowalski 是 toast 元件 Sonner 和 drawer 元件 Vaul 的作者,這支 skill 裡的案例也大多來自這兩個元件。README 給它的一句話介紹是「Fix animations that drop frames」,修會掉幀的動畫。

它在開頭就把範圍切得很窄:這是一支診斷用的 skill,只回答「每一幀有沒有及時畫完」。動畫的 easing 與時長歸 animate,這個動畫該不該存在歸 review-animations,React Native 的執行緒問題歸 animate-expo。所以拿它來「讓動畫更好看」會找錯人,它關心的是順不順。

作者在 Why This Matters 一節用 Sonner 舉例:同樣的 easing、同樣的時長、同樣的程式碼,跑在 30fps 就沒有人會在意這個元件。效能在這份文件裡被當成「讓好動效被看見的前提」,不是最後才補的拋光。

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

frontmatter 只有 name 和 description 兩個欄位,沒有 disable-model-invocation。同 repo 15 支 skill 裡設了這個旗標、只能手動點名的是 pick-ui-library、prototype、review-animations 三支,animation-performance 不在其中。

description 列的觸發情境很具體:

  • 動畫 janky、choppy、stutters、drops frames(卡、頓、掉幀)
  • 在決定要動畫哪個屬性時
  • 在 CSS 與 JS 動畫之間依效能做選擇時
  • 想加 will-change 或 GPU layer 時
description 結尾還主動把相鄰的需求推出去:選 easing、時長或要不要動畫找 animate,整體審查動效找 review-animations。

我實際測了自動觸發:同一句「審查 src/ 底下這個部落格的 CSS 與元件,找出動畫效能問題」,沒有點名、跑 3 次,3 次都呼叫 Skill 工具載入了 animation-performance。需求裡有「動畫效能」「掉幀」「卡頓」這類字,命中率很高。

還有一個小細節:SKILL.md 有一段 Initial Response,規定被叫出來卻沒有具體問題時,只回一句「I'm ready to find out why your animation drops frames…」然後等你發問。所以單打 /animation-performance 不帶需求,只會得到這句開場。

📄 SKILL.md 裡面長什麼樣?

整份檔案只有一個 SKILL.md,沒有附檔。段落順序如下:

段落內容
Initial Response沒問題時只回一句開場
Why This MattersSonner 跑在 30fps 的假想例子
Operating Posture兩種失敗模式:「在我電腦上很順」和「到處灑 will-change」
Hard Rules5 條硬規則
The Symptom Table9 列「症狀 → 解法」對照
The Frame Budget16.7ms 與 8ms 的幀預算
What You Animatelayout/paint/composite 三層與「降一層」替換表
Where It RunsCSS、WAAPI、rAF 各跑在哪個執行緒
React: Don't Animate Through State每幀 setState 的問題
Two Traps That Look Correct繼承的 CSS 變數、blur
will-change何時才該加
Should You Avoid JS Animation?答案是不用
Workflow三階段:列出動畫 → 7 步診斷 → 固定格式報告
GitHub 上 animation-performance 的 SKILL.md,顯示 5 條 Hard Rules 與 The Symptom Table 的前幾列

📏 5 條硬規則與 9 列症狀表在說什麼?

5 條硬規則

我用自己的話轉述,順序照原文:

  1. 預設只動 transform 和 opacity。其他屬性都要有理由;便宜的屬性能做出一樣的畫面,就用便宜的,即使貴的那個「看起來沒事」。
  2. 改程式之前先說出原因。每個修法都要對應到下面某一個成因;「加了 will-change 好像比較順」不算診斷。
  3. 不能因為機器快就說沒問題。要分清楚哪些結論是讀程式碼確認的、哪些需要在實機上錄影。
  4. 不准 transition: all。all 會讓下一個變動的屬性也跟著動,包括沒人打算動畫的 layout 屬性。
  5. 不拿正確性換「也許有用」的優化。硬體加速是瀏覽器可能給的,動哪個屬性是你能控制的,力氣花在後者。
第 3 條是我覺得最有用的一條,實測時它對報告格式的影響最明顯,後面會看到。

渲染管線三層

文件把每個 CSS 屬性分到管線的三個入口,成本差一個數量級:

層級屬性(依 SKILL.md)成本
只走 compositetransform、opacity;目前 Chrome/Firefox 也包括 filter、clip-path、background-color最便宜,瀏覽器會把元素升成獨立圖層
paint + compositebox-shadow、border-radius、color不用重新量尺寸,但每幀重畫
layout + paint + compositewidth、height、padding、margin、top、left、border-width最貴,一個元素變大會推動周圍所有元素
配套的是一張「降一層」替換表:改尺寸用 scale() 取代 width/height/padding,移位置用 translate() 取代 top/left/margin,顯示隱藏用 opacity 取代 display/visibility,陰影用 filter: drop-shadow() 取代 box-shadow。作者特別點名最後一項最容易被漏掉。

這一節還有兩個補充,我認為是整份文件最務實的地方。第一,layout 動畫不一定會掉幀,絕對定位或子元素少的元素動 padding 可能看不出代價;但 padding 和 scale() 視覺上一樣,只有後者在慢裝置上不會退步,所以選沒有壞處的那個。第二,layout 屬性會推動旁邊的東西:側欄動 margin-left,旁邊整個儀表板每秒重排 60 次。

動畫跑在哪個執行緒

驅動方式執行緒
JS + requestAnimationFrame(GSAP、motion/react 元件)一律主執行緒
Web Animations API(element.animate()、Motion 的 animate())可以硬體加速
CSS transition 與 keyframes可以硬體加速
這裡的重點是「頁面正忙的時候在跑的動畫」:換頁、載入資料、hydration 期間的 tab 指示器。文件舉 Vercel 儀表板的例子,active tab 指示器用 Framer Motion 的 shared layout animation,剛好在載入下一頁時跑,於是掉幀,改成 CSS 動畫就好了。

Framer Motion 的 x、y、scale、rotate 簡寫是用 CSS 變數實作的,被動畫的 CSS 變數不會被加速;要撐過忙碌的主執行緒,就改寫成完整的 transform 字串:

<motion.div animate={{ x: 100 }} />                          // 主執行緒
<motion.div animate={{ transform: "translateX(100px)" }} />  // 可硬體加速

文件也沒有走極端。它說簡寫比較好讀,預設照用;百分比 transform 在 Chrome 歷史上不加速,但 Sonner、Vaul 定位 toast 和 drawer 都靠 translateY(100%),正確性優先。

React、CSS 變數、blur 三個陷阱

  • React 每幀 setState:每 16.7ms 重新 render 一次,光 render 就超出預算。改成直接寫 ref.current.style.transform,或用 motion value 的 y.set()。要找的地方是 requestAnimationFrame、pointermove、scroll、拖曳 handler 裡的 state 更新。
  • 繼承的 CSS 變數:Vaul 的拖曳在 drawer 裡超過約 20 個項目時變慢,原因是拖曳距離寫進父層的 --swipe-amount,CSS 變數會繼承,每個子元素每幀都要重算樣式。內容越多越慢、但明明用的是 transform,就該懷疑這個。
  • blur:filter: blur() 很快就變貴,Safari 尤其明顯。動畫中的 blur 壓在 20px 以下,交叉淡入用 2–5px 就夠。

will-change 是最後一步

文件給它的定位很清楚:瀏覽器在動畫中途把工作從 CPU 交給 GPU 時,元素可能位移 1px,will-change: transform 先把元素升層、避免這次交接。看到位移或量到掉幀才加;動 transform/opacity 的元素瀏覽器本來就會升層,平常加了沒作用,每一層還吃 GPU 記憶體。不要加在 * 或大容器上。

9 列症狀表與 7 步診斷

症狀表是整份文件的入口,「先對症狀、再讀對應段落」。7 步診斷照「最常見的成因」排序:動了哪些屬性 → 是否剛好在頁面忙的時候 → React 是否每幀 render → 內容變多是否更慢 → 有沒有被動畫的 CSS 變數 → 有沒有超過 20px 的 blur → 都排除了才加 will-change。

報告格式固定是 Element、Cause、Before、After、Verified 五欄,Verified 只能填「from code」或「needs a device recording」。最後一句是「依成本排序,然後停下來,被要求才動手修」。

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

我在一個空資料夾實際跑過這條(skills CLI 1.7.4,--agent claude-code 指定裝給 Claude Code,--copy 複製檔案而不建 symlink,-y 跳過確認):

npx -y skills@latest add emilkowalski/skills --skill animation-performance --agent claude-code --copy -y

跑完會多出兩個檔案:

.claude/skills/animation-performance/SKILL.md
skills-lock.json

點名方式是在需求前面加 /animation-performance:

/animation-performance 審查 src/ 底下的 CSS 與元件,找出可能掉幀的動畫,依嚴重度排序

因為它會自動觸發,大部分時候不點名也行。我會點名的情況是需求裡沒有「效能」「卡」「掉幀」這類字,例如「幫我看一下這個 sidebar 的動畫」,這時候不確定模型會選 animate、review-animations 還是它。

🧪 實測一:有沒有 skill,審查結果差在哪?

我把部落格的 src/styles/global.css 和 6 個有動畫的元件(閱讀進度條、篩選器、導覽列、卡片、首頁主視覺、角色影片)複製到兩個實驗資料夾,原 repo 不動。一個資料夾裝了 animation-performance,另一個沒裝。兩邊用同一句需求:

審查 src/ 底下這個部落格的 CSS 與元件,找出動畫效能問題(可能掉幀、卡頓的地方),依嚴重度排序,每項附 檔案:行號、原因、修法。只審查,不要修改任何檔案。

執行方式是 claude -p 加 --model sonnet,Claude Code 2.1.306。有 skill 的資料夾跑 3 次不點名、1 次用 /animation-performance 點名;沒 skill 的跑 3 次。每次約 98–146 秒、0.44–0.56 美元。

要先說明對照組的條件:我的使用者層級裝了 apple-design,沒 skill 的 3 次都自動載入了 apple-design。所以這組比較的是「有 apple-design、沒有 animation-performance」對上「兩支都可用」。

有無 animation-performance 的 7 次審查對照表:有 skill 的 4 次都把閱讀進度條 width 排第 1,並更常要求實機錄影

結果分三點:

抓到的問題差不多。 兩組都找到閱讀進度條動 width、篩選器在迴圈裡交錯讀 getComputedStyle 和寫 hidden、卡片 hover 動 box-shadow、漢堡選單動 top、transition-all、影片 load()、打字機效果每字 setState、mix-blend 與 backdrop-blur。沒有 skill 的 Sonnet 本身就知道這些知識,skill 沒有讓清單變長。

排序變穩定。 閱讀進度條(ReadingProgress.astro)的寫法是 transition: width 75ms linear 加 will-change: width,捲動時每幀寫 bar.style.width,同一個 callback 裡又讀 getBoundingClientRect() 和 offsetHeight。有 skill 的 4 次都排第 1,理由一致寫成「layout 級屬性」,直接對應 7 步診斷的第 1 步。沒 skill 的 3 次排在第 3、第 1、第 4,有兩次把篩選器或角色影片排在前面。

報告標出驗證程度。 這是硬規則第 3 條的效果。點名那次的第 1 項寫著「從程式碼確認,實際掉幀量要用 DevTools Performance 錄 scroll 才知道」,結尾還有一節「需要實機錄製才能下定論的項目」,並明確寫出「沒有被動畫的 CSS 變數,也沒有超過 20px 的動畫 blur」,把 7 步診斷裡排除掉的項目也交代了。我數了報告裡要求錄影或實機確認的字詞:有 skill 的 4 次是 5、6、11、4 次,沒 skill 的 3 次是 1、0、1 次。

也有沒做到的地方。4 次裡只有 1 次(自動觸發的第 1 次)提到用 filter: drop-shadow() 取代 box-shadow,把 transition-all 明講成「違反硬規則」的也只有 2 次;症狀表的內容沒有每次都完整用上。另一邊,角色影片的解碼與重複載入不在這支 skill 的成因清單裡,點名那次自己寫了「這點不在 skill 的原因清單內」,沒 skill 的對照組反而在這題寫得比較多。

📉 實測二:照它的建議改,量得到差別嗎?

4 次審查都建議把進度條改成 width: 100% 加 transform: scaleX(ratio)、transform-origin: left。我想知道這個改動在真實頁面上的代價差多少。

做法是用 Playwright 開正式站的一篇長文,用 page.route 在瀏覽器端改寫 HTML,比較三個版本:原版 width、改寫成 scaleX、以及完全不更新進度條的基準。每幀捲動 20px、共 300 幀,用 CDP 的 Performance.getMetrics 讀 layout 次數與耗時,各跑 3 次;再用 Emulation.setCPUThrottlingRate 加一組 CPU 4 倍限速。正式站本身沒有被改動。

正式文章頁的進度條實測:width 版 300 幀約 250 次 layout、4 倍限速下 136ms,scaleX 版 0 次;兩版平均幀間隔都在 17ms 左右
版本(3 次平均)layout 次數layout 耗時平均幀間隔
width,不限速23824.2ms22.2ms
scaleX,不限速00ms21.8ms
不更新進度條,不限速00ms17.5ms
width,CPU 4x253136.4ms17.1ms
scaleX,CPU 4x00ms16.8ms
不更新進度條,CPU 4x00ms16.8ms
不更新進度條的基準版 layout 是 0,代表捲動本身不觸發 layout,那約 250 次全是進度條的 width 造成的。改成 scaleX 後 layout 歸零,skill 的判斷在機制上是對的。

但幀率沒有分出高下。4 倍限速下 width 版 300 幀的 layout 合計 136ms,平均每幀不到 0.5ms,離 16.7ms 的預算還很遠;平均幀間隔兩版都是 17ms 上下,超過 25ms 的幀數三個版本都在 0–7 之間浮動,看不出規律。我另外用一個 600 段的合成長頁重做一次,結果相同:layout 從約 300 次降到 0,幀間隔沒有差別。

這組數字剛好對上 SKILL.md 自己寫的兩句話。一是「layout 動畫不一定會掉幀」,改成 scaleX 的理由是它在慢裝置上不會退步,不是它在這台桌機上比較快。二是 Phase 2 最後那句:桌機的 CPU throttling 只模擬慢 CPU,模擬不了弱 GPU 或小記憶體,要確認就得用中階手機遠端除錯錄影。我這次是 headless Chromium 跑在桌機上,沒有做實機錄影,所以只能說「layout 工作量確實歸零」,不能說「改了之後手機上變順」。

👥 適合誰、什麼情況不適合?

適合的情況:

  • 動畫已經做好、但有人回報卡,想要一份依成本排序、標好哪些已確認的清單,再決定修哪幾項。
  • 審查別人的前端 PR,想確認有沒有動 layout 屬性、有沒有 transition: all、有沒有在 scroll handler 裡 setState。
  • 用 Framer Motion 或 Vaul 這類函式庫,需要知道簡寫和 CSS 變數什麼時候會掉出硬體加速。
不太適合的情況:
  • 想要動畫變好看:easing、時長、要不要動畫,這支 skill 刻意不管,找 animate 或 review-animations。
  • 期待它幫你量:它在 Claude Code 裡是讀程式碼做靜態審查,量測要你自己錄 DevTools Performance 或寫腳本。我的實測二就是它不會替你做的那一段。
  • React Native:文件明說交給 animate-expo。
  • 只想要更多問題被找出來:從我的對照看,Sonnet 不帶 skill 找到的問題清單已經差不多;這支 skill 的價值在排序與「分清楚已確認和待確認」,不在數量。

🧭 跟同系列的 animate、review-animations 怎麼分工?

這三支在 description 裡互相指路,界線寫得很清楚:

skill回答的問題自動觸發
animate用什麼 easing、多長、要不要動畫可以
animation-performance每一幀能不能在預算內畫完可以
review-animations整體審查動效,包括這個動畫該不該存在不行,要點名
我的用法是先用 review-animations 或 animate 決定「要不要、怎麼動」,確定要保留的動畫有卡頓疑慮時,再叫 animation-performance 做效能診斷。如果你在意的是拖曳、彈簧、手勢的手感,同一位作者的 apple-design skill 實測 比較對題。

🔗 延伸資源

下一步建議:在你的專案裡用 /animation-performance 跑一次審查,挑它標成「from code」的第 1 項先修;標成「needs a device recording」的項目,拿一支中階手機接 DevTools 遠端除錯錄一次 Performance,再決定要不要動。
author
陳
陳彥彤

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

support

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