懶人包
- 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。
- animation-performance skill 是什麼?
- 它什麼時候會被叫出來?
- SKILL.md 裡面長什麼樣?
- 5 條硬規則與 9 列症狀表在說什麼?
- 怎麼安裝、怎麼點名?
- 實測一:有沒有 skill,審查結果差在哪?
- 實測二:照它的建議改,量得到差別嗎?
- 適合誰、什麼情況不適合?
- 跟同系列的 animate、review-animations 怎麼分工?
- 延伸資源
🎞️ 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 時
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 Matters | Sonner 跑在 30fps 的假想例子 |
| Operating Posture | 兩種失敗模式:「在我電腦上很順」和「到處灑 will-change」 |
| Hard Rules | 5 條硬規則 |
| The Symptom Table | 9 列「症狀 → 解法」對照 |
| The Frame Budget | 16.7ms 與 8ms 的幀預算 |
| What You Animate | layout/paint/composite 三層與「降一層」替換表 |
| Where It Runs | CSS、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 步診斷 → 固定格式報告 |
📏 5 條硬規則與 9 列症狀表在說什麼?
5 條硬規則
我用自己的話轉述,順序照原文:
- 預設只動
transform和opacity。其他屬性都要有理由;便宜的屬性能做出一樣的畫面,就用便宜的,即使貴的那個「看起來沒事」。 - 改程式之前先說出原因。每個修法都要對應到下面某一個成因;「加了
will-change好像比較順」不算診斷。 - 不能因為機器快就說沒問題。要分清楚哪些結論是讀程式碼確認的、哪些需要在實機上錄影。
- 不准
transition: all。all會讓下一個變動的屬性也跟著動,包括沒人打算動畫的 layout 屬性。 - 不拿正確性換「也許有用」的優化。硬體加速是瀏覽器可能給的,動哪個屬性是你能控制的,力氣花在後者。
渲染管線三層
文件把每個 CSS 屬性分到管線的三個入口,成本差一個數量級:
| 層級 | 屬性(依 SKILL.md) | 成本 |
|---|---|---|
| 只走 composite | transform、opacity;目前 Chrome/Firefox 也包括 filter、clip-path、background-color | 最便宜,瀏覽器會把元素升成獨立圖層 |
| paint + composite | box-shadow、border-radius、color | 不用重新量尺寸,但每幀重畫 |
| layout + paint + composite | width、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 | 可以硬體加速 |
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」對上「兩支都可用」。
結果分三點:
抓到的問題差不多。 兩組都找到閱讀進度條動 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 倍限速。正式站本身沒有被改動。
| 版本(3 次平均) | layout 次數 | layout 耗時 | 平均幀間隔 |
|---|---|---|---|
width,不限速 | 238 | 24.2ms | 22.2ms |
scaleX,不限速 | 0 | 0ms | 21.8ms |
| 不更新進度條,不限速 | 0 | 0ms | 17.5ms |
width,CPU 4x | 253 | 136.4ms | 17.1ms |
scaleX,CPU 4x | 0 | 0ms | 16.8ms |
| 不更新進度條,CPU 4x | 0 | 0ms | 16.8ms |
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 | 整體審查動效,包括這個動畫該不該存在 | 不行,要點名 |
🔗 延伸資源
- GitHub repo:emilkowalski/skills(2026-10-21 查詢為 48,162 stars、2,785 forks,15 支 skill 共用,MIT)
- skill 原文:skills/animation-performance/SKILL.md
- 本文對照的版本:commit
2d22863(2026-10-11)
/animation-performance 跑一次審查,挑它標成「from code」的第 1 項先修;標成「needs a device recording」的項目,拿一支中階手機接 DevTools 遠端除錯錄一次 Performance,再決定要不要動。