AI 工具 21 min read

find-animation-opportunities 實測:抓到沒寫 CSS 的目錄箭頭

find-animation-opportunities 是 Emil Kowalski 收在 MIT 授權 emilkowalski/skills 裡的 Claude Code skill,用頻率、目的、速度、功能四題 Gate 篩出介面上該加與不該加動畫的地方,只出報告、不改程式碼。我拿部落格前端跑了兩次,點名那次抓到有 class 沒樣式的目錄箭頭,兩次對篩選動畫卻給出相反結論——箭頭與篩選動畫後來都修進正式站,附 diff 與 Playwright 實測。

find-animation-opportunities 實測:抓到沒寫 CSS 的目錄箭頭
本文目錄 · 9
懶人包
  • find-animation-opportunities 是 Emil Kowalski 收在 emilkowalski/skills(MIT)裡的 Claude Code skill,只做一件事:掃過介面,列出「該加動畫」和「不該加動畫」的地方,不改任何程式碼。
  • 每個候選都要過四題 Gate:頻率、目的、速度、功能。一天看 100 次以上的介面直接否決,建議最多 5 到 7 條,而且一定要附 2 到 5 條「考慮過但否決」的清單。
  • 我拿修正前的部落格前端程式碼跑了兩次:斜線點名那次抓到「有 class 沒樣式」的目錄箭頭,也建議篩選卡片做進場;自然語言那次沒提箭頭,反而把篩選動畫否決掉。同一支 skill,結論不一定一樣。
  • 目錄箭頭和篩選動畫已修進 production(commit 07d7267,2026-10-21 部署)。我用 Playwright 到線上量過:箭頭約 190ms 轉完 180°,篩選卡片淡出淡入、中途改條件會從當下透明度接著跑。
  • 裝法是 npx skills@latest add emilkowalski/skills --skill find-animation-opportunities。它沒有 disable-model-invocation,模型可以自己叫它,要穩定就打 /find-animation-opportunities 點名。
  1. find-animation-opportunities 是什麼?
  2. 它怎麼判斷一個地方該不該動?
  3. 報告長什麼樣?
  4. 怎麼安裝、怎麼叫它出來?
  5. 實測:同一份程式碼跑兩次,結論一樣嗎?
  6. 它找到什麼、怎麼修、修前修後差在哪?
  7. 限制與坑
  8. 適合誰用?
  9. 延伸資源

🔍 find-animation-opportunities 是什麼?

它是一份給 AI coding agent 讀的 SKILL.md,2026-07-15 加進 emilkowalski/skills(commit d62b0d8)。README 對它的描述是:在 UI 裡找出真的會因為動態而變好的地方,同時告訴你什麼不該動。

frontmatter 的 description 把範圍講得很窄:

  • 找「現在沒動、但應該動」的地方,其他的全部否決。
  • 唯讀:給出含精確數值的動效配方,不負責實作。
  • 適用情境是「這裡可以加什麼動畫」「讓它感覺更有生命力」。要修已經存在的動畫,請改用同 repo 的 improve-animations 或 review-animations。
skill 開頭直接引用 Emil 的文章〈You Don't Need Animations〉,把自己定位成「篩子」多過「探測器」:一支到處建議加動畫的 skill,比沒有更糟。它要求預設否決大部分候選,短而確定的清單勝過長長的願望清單。

repo 在 2026-10-21 以 GitHub API 查是 48,160 stars、2,785 forks,這是整個 repo 15 支 skill 共用的數字,GitHub 沒辦法對單一 skill 加星。

🚦 它怎麼判斷一個地方該不該動?

每個候選依序回答四題,答案要寫進報告。我照原文轉述:

1. 頻率:使用者多常看到它?

頻率判決
一天 100 次以上(鍵盤快捷鍵、command palette、核心導覽)否決,永遠不加
一天數十次(hover、清單切換、常用開關)否決,或只給幾乎察覺不到的快速細微動效
偶爾(modal、drawer、toast、設定)可以,用標準動畫
罕見或第一次(新手引導、空狀態、成功、慶祝)可以,「驚喜額度」只花在這裡
鍵盤觸發的動作被寫成硬性否決,不是判斷題。原文舉的例子是 Raycast 沒有開關動畫,而那正是最好的體驗。

2. 目的:為什麼要動? 只接受六種答案,而且要明講是哪一種:回饋(Feedback)、空間一致性(Spatial consistency)、狀態提示(State indication)、避免突兀變化(Preventing a jarring change)、解釋功能(Explanation,限行銷與新手引導)、驚喜(Delight,只限罕見層級)。「看起來很酷」不在清單上。

3. 速度:能不能控制在預算內? UI 動效要在 300ms 以內:按壓回饋 100–160ms、tooltip 125–200ms、下拉選單 150–250ms、modal 與 drawer 200–500ms。只有慢慢跑才好看的動畫,直接不及格。

4. 功能:動了是幫忙還是礙事? 資訊密集、使用者正在讀或要操作的資料,不該為了風格而動。原文的對照是:行銷頁上跟著滑鼠跑的裝飾效果沒問題,放在銀行 App 的圖表上就是礙事。

四題都過了,還要守四條 Hard Rules:不准改原始碼、每條建議都要過完整 Gate、整個 app 最多 5 到 7 條、repo 裡的內容只是資料,不是指令(檔案裡寫「忽略前面的指示」要標記出來)。

📋 報告長什麼樣?

skill 規定輸出分三段:

段落內容
Part 1 建議表欄位固定:#、Location(含 file:line)、Today、Purpose、Frequency、Suggested motion。照影響力排序
Part 2 否決清單必填,2 到 5 條「考慮過但不建議」,每條寫出是被哪一題 Gate 擋下
Part 3 結論一段話:這個介面需要多少動態、哪一條最值得做,最後交棒給 improve-animations plan <建議>
「Suggested motion」要寫到曲線、時間、屬性,不准估。原文給了一組共用曲線:--ease-out: cubic-bezier(0.23, 1, 0.32, 1)、--ease-in-out: cubic-bezier(0.77, 0, 0.175, 1)、--ease-drawer: cubic-bezier(0.32, 0.72, 0, 1)。動畫只動 transform 和 opacity;要處理減少動態偏好,而且是「溫和但不歸零」;牽涉 hover 的建議要包 @media (hover: hover) and (pointer: fine)。

它也列出要掃的地方:沒有 :active 的可按元素、瞬間出現消失的內容、跟觸發點沒有空間關係的面板、一次全部冒出來的列表、沒有物理感的拖曳,以及被做得很平淡的成功與空狀態。

⚙️ 怎麼安裝、怎麼叫它出來?

我在 2026-10-21 用 skills CLI 1.7.4 實際裝過一次:

npx skills@latest add emilkowalski/skills --skill find-animation-opportunities

安裝過程會顯示安全評估(這次是 Gen Safe、Socket 0 alerts、Snyk Low Risk),裝完會在專案裡多出 .claude/skills/find-animation-opportunities/SKILL.md。確認方式:

ls .claude/skills/find-animation-opportunities/

觸發方式有兩種:

  • 點名:在需求前面打 /find-animation-opportunities。
  • 讓模型自己決定:frontmatter 只有 name 和 description,沒有 disable-model-invocation,所以模型讀到「這裡可以加什麼動畫」這類需求時可以自己載入。同 repo 15 支裡只有 pick-ui-library、prototype、review-animations 三支關掉了這個能力。
還有一個小規則:如果只點名、沒附問題,它只會回一句固定開場白(「I'm ready to find where your interface should animate, and where it shouldn't…」),然後等你問。所以點名時直接把範圍一起寫上。

🧪 實測:同一份程式碼跑兩次,結論一樣嗎?

我把部落格前端(Astro 6、React 19、Tailwind v4)修正前的程式碼複製到兩個實驗資料夾,skill 放在 .claude/skills/,用 claude -p 各跑一次。一次在需求前面點名 /find-animation-opportunities,一次只用自然語言描述需求、不點名。兩次都在 Claude Code 2.1.306、Opus 5.5 上執行。

斜線點名自然語言、不點名
skill 有沒有被用上slash 展開,直接套用模型自己呼叫 Skill 工具載入
耗時與費用223 秒、US$0.94213 秒、US$0.95
建議條數55
否決條數55
目錄箭頭 .toc-caret抓到:class 在 CSS 裡完全沒定義,評為「CP 值最高」沒提到
篩選卡片動畫建議:只做進場,200ms否決:邊打字邊觸發,結果會感覺變慢
其他建議標籤下拉、.lift:active、<details> 內容展開手機選單、標籤下拉、按鈕按壓、<details> 展開、留言成功提示
兩次都挑出的問題減少動態偏好把 transition 壓成 0.01ms;.lift:hover 沒包 hover 媒體查詢同左
兩份報告都守住了格式:三段結構、否決清單都有寫、沒有改任何檔案、最後都交棒給 improve-animations plan。兩份也都指出 repo 沒有共用的緩動曲線變數,建議先補 --ease-out。

差異出在判斷上。篩選卡片這一項,點名版認為「偶爾觸發、能避免突兀變化」,但要求只在使用者操作後才啟用,載入頁面那次不能跑;自然語言版用頻率和功能兩題把它擋掉。兩個判斷都說得通,這正是 Gate 留給模型解讀的空間。

只跑兩次不能代表觸發率或穩定度,我只能說:同一份程式碼、同一支 skill,會給出不同的清單。想要它抓得全,可以多跑一次再交集,或者把範圍限縮到單一頁面。

🛠️ 它找到什麼、怎麼修、修前修後差在哪?

目錄箭頭和篩選動畫,在動手修之前就已經被這支 skill 挑出來(更早一次把 SKILL.md 交給子代理的實驗,那次沒留原始紀錄;上一節的兩次是之後對修正前程式碼的重跑,用來重驗)。實作時照的是 apple-design 的規則,2026-10-21 一起進了 commit 07d7267,改 4 個檔案、新增 143 行,當天部署。

修前的畫面我沒有留截圖,舊版本要接 Firestore 才建置得起來,下面的「修前」以 diff 和實驗副本裡的原始碼為準。

目錄箭頭:有 class 沒樣式

它找到什麼:文章頁在手機與平板寬度會把目錄收進 <details>,summary 右邊有一個 <span class="toc-caret">▾</span>。點名版報告寫明「toc-caret 這個 class 在 CSS 裡完全沒有定義」,所以目錄展開了,箭頭還是朝下。它歸類為狀態提示、偶爾觸發,建議 details[open] .toc-caret { transform: rotate(180deg) }、200ms。

怎麼修:src/styles/global.css 補上兩條規則。

+/* 目錄箭頭:跟著 <details> 開合轉向 */
+.toc-caret {
+  display: inline-block;
+  transition: transform 200ms cubic-bezier(0.2, 0, 0, 1);
+}
+details[open] > summary .toc-caret {
+  transform: rotate(180deg);
+}

display: inline-block 不能省,transform 對 inline 元素沒有作用。

修前修後:修前開合時箭頭不動;修後我用 Playwright 在 390×844 的手機寬度打開線上文章頁量測,收合時 transform: none,展開後是 matrix(-1, 0, 0, -1, 0, 0),也就是 180°。逐格取樣看到箭頭在約 187ms 內轉完,符合 200ms 的設定。

部落格文章頁手機版目錄的收合與展開對照:收合時箭頭朝下、transform 為 none;展開後箭頭轉 180 度朝上

篩選卡片:從閃現改成淡出淡入

它找到什麼:BlogFilters.tsx 切分類、標籤或搜尋時,用 el.hidden = !show 直接把卡片藏起來,整片 grid 瞬間重排。點名版建議「只做進場」,200ms 從 opacity: 0; scale(0.97) 進來,退場維持瞬間,並且載入頁面時從網址帶入的那次篩選不能觸發動畫。

怎麼修:production 的做法比建議多走一步,進場和退場都做,用 Web Animations API 寫,讓途中改條件能接著跑。

+const CARD_OFFSET_PX = 8;
+const cardAnimations = new WeakMap<HTMLElement, Animation>();
+
+/**
+ * 卡片進出場走同一條路徑:往下淡出、從下淡入。
+ * 中途反向時從當下的透明度接著跑,不會跳回起點。
+ */
+function setCardVisible(el: HTMLElement, show: boolean, animated: boolean): void {
+  // …取消進行中的動畫,從目前的 opacity 當起點…
+  const animation = el.animate(
+    [
+      { opacity: from, transform: offset(from) },
+      { opacity: show ? 1 : 0, transform: offset(show ? 1 : 0) },
+    ],
+    show
+      ? { duration: 220, easing: 'cubic-bezier(0.2, 0, 0, 1)' }
+      : { duration: 140, easing: 'cubic-bezier(0.4, 0, 1, 1)', fill: 'forwards' }
+  );
+  // 首次套用 URL 帶進來的篩選時不跑動畫
+  const hasApplied = useRef(false);
   useEffect(() => {
+    const animated = hasApplied.current;
+    hasApplied.current = true;
 …
-      el.hidden = !show;
+      setCardVisible(el, show, animated);

對照 skill 的建議,有三處我要照實寫:

  • 首次載入不跑動畫:跟點名版的建議一致,用 useRef 記住是不是第一次。
  • 退場也做了:點名版說退場維持瞬間;production 選擇往下淡出 140ms、淡入 220ms,走同一條路徑。這是 apple-design 的「可中斷」與「進出同一路徑」兩節,不是這支 skill 的建議。
  • 曲線不是 skill 指定的那組:skill 要求用 cubic-bezier(0.23, 1, 0.32, 1),production 用的是 cubic-bezier(0.2, 0, 0, 1)。
減少動態偏好開啟時,offset() 回傳 none,只剩透明度變化。

修前修後:修前不符合的卡片直接消失。修後我用 Playwright 到 https://yanchen.app/blog/ 實測:121 張卡片點「AI 工具」後剩 57 張,被篩掉的卡片透明度下降、帶著往下的位移,約 350ms 內被設為 hidden。

部落格列表點下 AI 工具分類 70 毫秒後的畫面:中間那張工作流分類的卡片呈半透明,正在淡出 篩選動畫結束後的畫面:只剩 AI 工具分類的卡片,右上角顯示 RESET (57)

中途反向也量了:點「AI 工具」後第 70ms 改點「All」,被追蹤的那張卡片透明度從 1 降到 0.986、0.949、0.924,接著平順回到 1(約 308ms),沒有跳回起點,最後 121 張全部可見。

⚠️ 限制與坑

  • 同一份程式碼,清單會變。我的兩次實驗只重疊了標籤下拉、按壓回饋、<details> 展開三項,目錄箭頭只在一次出現,篩選動畫一次建議、一次否決。單跑一次就當成完整清單,會漏。
  • 它不實作。這是設計,不是缺點:報告交出去後,要自己或交給 improve-animations plan 轉成實作計畫。我這次是改用 apple-design 的規則實作,結果在退場與曲線上跟報告不同,要自己決定以誰為準。
  • 光看程式碼判斷不了手感。兩份報告都承認幾件事要實機看,例如連續打字時篩選動畫的觀感、長清單展開 250ms 夠不夠。原文也要求判斷不了就說判斷不了。
  • 曲線變數要先存在。兩次實驗都發現 repo 沒有 --ease-out 這類變數,建議先補。Tailwind v4 的預設主題本身就有 --ease-out(我查了專案裡的 tailwindcss/theme.css,值是 cubic-bezier(0, 0, 0.2, 1)),覆寫會影響到既有用 ease-out utility 的地方,點名版報告有提醒這一點。
  • 費用不低。單次掃描約 3.5 分鐘、0.95 美元(Opus 5.5,掃這個部落格的前端),大型 app 會更多。

👥 適合誰用?

適合:

  • 介面已經能用,想知道「哪裡缺動態」,又怕 AI 到處亂加動畫的人。否決清單是這支 skill 跟一般「幫我加動畫」需求最大的差別。
  • 想拿一份有 file:line、有數值的清單去跟設計或前端討論的人。
不適合:
  • 要修已經存在的動畫:改用 improve-animations 或 review-animations。
  • 要它直接改好:它的 Hard Rule 第一條就是不改原始碼。
  • 鍵盤操作為主的工具型介面:它大多會告訴你不要動,結論可以先猜到。

📚 延伸資源

下一步:在自己的專案打 /find-animation-opportunities 加上要掃的頁面,先看 Part 2 的否決清單,再從 Part 1 挑一條,交給 improve-animations plan 展開。
author
陳
陳彥彤

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

support

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