動態設計原則 是做什麼的?
這是一個品味層。它決定「如何」讓某個物體移動,使其感覺精緻且經過設計,而不是卡頓、漂浮或廉價。它不負責編寫實際播放動畫的程式碼——一旦你決定了曲線、時間與弧線,這個決策就是導演/渲染技能(產品影片交給 product-demo-director)或 CSS/JS 過渡(UI)的輸入。將兩個任務分開:這個技能掌握判斷;導演技能掌握編排與機制。
凡是有關動態「感覺」的問題都可觸發此技能:「讓它不要那麼卡」、「更平滑」、「更精緻」、「更有生命力」、「緩動不對」、「這看起來很廉價」、「我該用 ease-in 還是 ease-out」、「spring 還是 interpolate」。
何時改用其他相關技能
此技能僅處理曲線/時間/弧線的判斷。當真正問題是以下情況時,請交由其他技能處理:
- 鏡頭、場景順序或節拍列表 → video-storyboard。
- 導演完整產品示範——游標移動、螢幕錄影、功能標註、端到端場景組裝 → product-demo-director。(此技能產出它所需的 motion spec。)
- 文字或字母形態動畫——飛入/建構/揭示的類型 → kinetic-typography。
- 將動態對齊節拍、配樂或音效 → sound-and-music-sync。
- 為特定平台裁切或重新構圖(9:16、1:1、安全區域) → social-video-formatter。
- 燈光、曝光、發光或色彩動態 → motion-color-and-light。
操作流程
先診斷再開處方。大多數「卡頓」的動畫屬於以下四種命名問題之一,每種對應到一個具體修復方法。請依序執行步驟——如果時間不對,緩動修正就沒用;如果元素沒有弧線,時間修正也沒用。
步驟 1:命名問題所在
觀察動畫(或對它的描述)並進行分類。幾乎所有抱怨都可歸為以下幾類:
- 線性 / 機器人感 —— 等速移動,突然停止。沒有緩動。請在步驟 2 修正。
- 漂浮 / 鬆散感 —— 太慢,或者 eases in 和 out 但應該快速 snap in。持續時間或曲線錯誤。請在步驟 2 和 3 修正。
- 卡頓 / 抖動感 —— 進出速度相同,或每個屬性都在同一條平坦時間軸上動畫,沒有錯位。缺少 spacing 和 arc。請在步驟 3 和 4 修正。
- 死板 / 無生氣 —— 技術上正確但無聊:沒有 anticipation、沒有 overshoot、沒有 follow-through。缺少 arc 和 secondary motion。請在步驟 4 和 5 修正。
在動手前將診斷寫成一行。「這個模態框很漂浮,因為它在 600ms 內用 ease-in-out 淡入」是一個可修正的描述;「讓它更好看」則不是。
步驟 2:選擇緩動曲線
緩動是動畫的速度隨時間變化的形狀。這個曲線承載了幾乎所有感知到的品質。根據元素的行為選擇曲線:
- ease-out(快速開始,緩慢結束)—— 進入物體以及任何用戶觸發的元素的默認值。它感覺反應靈敏,因為動畫立即開始,然後減速到位。用於:元素出現、下拉選單開啟、按鈕回應、內容滑入。如果不確定,請用 ease-out。
- ease-in(緩慢開始,快速結束)—— 用於離開的物體。元素加速離開螢幕。單獨用於進入時會感覺遲緩、反應慢,這是「漂浮感」的 #1 原因。用於:關閉、退出、消失。
- ease-in-out(慢、快、慢)—— 用於將已在螢幕上的物體從 A 移到 B:卡片重排、輪播前進、開關滑動。兩端都已固定,因此兩端緩動感覺可控。不要用於進入——慢速開始會破壞反應性。
- spring(物理:質量、剛度、阻尼——會 overshoot 並穩定下來)—— 用於需要感覺有生命力且物理真實的動畫:模態框彈入、開關旋鈕、按讚按鈕、通知掉落。輕微的 overshoot 就是「有生命力」的感覺。調整阻尼以改變個性:阻尼越小越有彈性且趣味,阻尼越大越緊湊且商業化。
spring() vs interpolate() 的決策(在程式碼中何時選用哪一個)在品質門檻以及下方的可執行腳本中。簡短版:spring 用於物理/活潑的變換,interpolate 用於精確對幀的數值,如透明度淡入淡出、顏色和進度條。
步驟 3:設定 timing 和 spacing
Timing 是總持續時間。Spacing 是距離在該持續時間內的分佈方式——spacing 就是緩動的具象化(ease-out 就是在結尾密集排列的幀)。兩個規則:
- 持續時間與距離和尺寸成正比,而非品味。 一個小元素移動短距離應該很快(UI:約 150–250ms)。一個大元素或長距離移動需要更長時間(約 300–500ms),否則會像瞬移。影片中的全螢幕/主體動畫可以更長。同樣的動作,距離越長 → 持續時間越長。
- 進入和退出不對稱。 退出應該比進入更快(約 0.7–0.8 倍)——用戶已經對該元素完成操作,所以讓它快速消失。等長的進入/退出持續時間是「卡頓感」的經典來源。
對產品影片而言,一旦離開此技能,立即將持續時間轉換為幀數:frames = round(seconds * fps)。這些幀數就是你要交給 product-demo-director 的具體輸入(它會將它們轉換為計時的場景計劃)。
步驟 4:建立 anticipation → action → follow-through 弧線
真實的動畫很少只有單一動作。最豐富、最有「生命力」的動畫包含三個節拍:
- Anticipation —— 主要動作之前一個微小的反向預備動作(按鈕在彈出前先略微內縮,卡片在滑出前先向後拉)。它為眼睛做準備,讓動作感覺有目的性。保持微小且快速;太多會像故障。
- Action —— 主要動作,繼承步驟 2 的緩動和步驟 3 的持續時間。這是 spacing 所在。
- Follow-through —— 元素越過目標再穩定回來(spring overshoot),以及任何附屬/次要元素略微落後於主體(標籤追上它所在的卡片)。這就是重量感和生命感的關鍵。
並非每個微互動都需要全部三個——一個 150ms 的工具提示只需要 action。但當某個動畫感覺「死板」時,幾乎總是缺少 anticipation 或 follow-through。在添加裝飾之前先加入弧線。
步驟 5:套用相關的迪士尼原則
迪士尼 12 動畫原則直接映射到 UI 和產品動畫上。你不需要全部套用;只需選用幾個可以修復診斷問題的原則。完整映射請見 references/twelve-principles。最高杠桿的原則:
- Slow in & slow out → 緩動(步驟 2)。單一最大的品質杠杆。
- Anticipation → 預備動作(步驟 4)。
- Follow-through & overlapping action → overshoot + 滯後的次要元素(步驟 4)。「正確」與「有生命力」之間的區別。
- Timing → 持續時間和 spacing(步驟 3)。
- Squash & stretch → 按壓/衝擊時的微妙縮放以暗示質量;在 UI 中保持只有幾個百分比,否則看起來像玩具。
- Staging → 一次只移動一個東西,讓眼睛知道看哪裡;永遠不要在相同曲線上同時為六個東西製作動畫。
- Secondary action → 一個微小的輔助動作(卡片抬起時陰影加深)來強化主要動作而不與之競爭。
- Exaggeration → 將關鍵姿勢稍微推向超越現實;UI 中要克制,在有力的產品影片中可以更自由。
步驟 6:交付 spec
填寫下方的 motion-spec 模板——曲線、持續時間(和幀數)、弧線節拍,以及每個屬性的 spring-vs-interpolate 選擇。填寫好的 spec 就是可交付成果:對於產品影片場景,它直接進入 product-demo-director;對於 UI,它將成為你的 transition/keyframe 數值。如果場景還需要光線/曝光動畫,請搭配 motion-color-and-light;如果有文字動畫,請搭配 kinetic-typography;如果時間需要對齊節拍,請搭配 sound-and-music-sync。
品質門檻
只有當以下所有條件都成立時,動畫才是 A+ 等級:
- 診斷已被命名(linear / floaty / janky / dead),並且每個變更都源自於此。沒有無理由的「讓它更好看」。
- 進入 ease-out,退出 ease-in,螢幕上 A→B ease-in-out,活潑/物理使用 spring。 除了刻意的等速循環(spinner、跑馬燈)外,不得放出原始的線性動畫。
- 持續時間與距離/尺寸成比例,且退出比進入更快。
- 每個屬性的
spring()與interpolate()決策明確:spring 用於物理變換(scale、translate、彈跳/滑動/穩定——你需要 overshoot);interpolate 用於精確計時數值(透明度淡入淡出、顏色、進度、任何 overshoot 會看起來不對的情況)。由 spring 驅動的淡入或由線性 interpolate 驅動的 logo 彈出是缺陷。 - 在主要/標誌性動畫中至少存在 anticipation 或 follow-through 其中之一;無生氣的動畫正是缺少這些。
- 一次只引導一個東西(staging);屬性相互錯開,而不是全部在一條平坦時間軸上觸發。
- 曲線、持續時間和幀數都已記錄在案,隨時可以交給 product-demo-director 或 transition——此技能永遠不讓決策停留在隱含狀態。
禁止事項
- 不得在進入或退出時使用線性緩動——這是機器人的默認值,感覺像未完成。等速僅用於刻意的循環。
- 不得對進入 ease-in 或對退出 ease-out——這會反過來造成漂浮/遲滯感。進入 ease-out,退出 ease-in。
- 不得使用等長的進入和退出持續時間;退出應該快約 0.7–0.8 倍。
- 不得使用 spring 驅動淡入、顏色或進度條——overshoot 會讓數值超出範圍並閃爍。使用 interpolate 處理計時數值。
- 不得在想要感覺有生命力時,用線性 interpolate 驅動活潑的彈跳/滑動/縮放——使用 spring 獲得 overshoot。
- 不得在同一條平坦時間軸上為多個屬性製作動畫;要分階段並錯開它們。
- 不要在 UI 中過度 anticipation 或 overshoot——只有幾個像素/幾個百分比。過大的預備動作和反彈讀起來像故障或玩具。
- 不要在於此規劃鏡頭順序或組裝示範——將 spec 交給 product-demo-director;分鏡順序請用 video-storyboard。
緩動與 spring 選擇器
自含的 Node.js 腳本。儲存為 motion_pick.js 並執行 node motion_pick.js。編輯 requests 陣列以包含你要製作動畫的元素;它會返回曲線、距離縮放後的持續時間(毫秒和幀數)、退出持續時間,以及 spring-vs-interpolate 決策。無需外部依賴。
// Motion picker。編輯 `requests`,然後執行:node motion_pick.js
const fps = 30 // 產品影片使用;UI 可忽略幀欄位
// role: 'enter' | 'exit' | 'move' | 'lively'
// distancePx: 移動距離(驅動持續時間)。sizePx: 元素的大致尺寸。
const requests = [
{ name: '模態框彈入', role: 'lively', property: 'scale', distancePx: 0, sizePx: 600 },
{ name: '模態框背景', role: 'enter', property: 'opacity', distancePx: 0, sizePx: 1920 },
{ name: '提示條滑入', role: 'enter', property: 'translate', distancePx: 320, sizePx: 360 },
{ name: '提示條關閉', role: 'exit', property: 'translate', distancePx: 320, sizePx: 360 },
{ name: '卡片重排', role: 'move', property: 'translate', distancePx: 140, sizePx: 280 },
{ name: '進度條填充', role: 'move', property: 'width', distancePx: 800, sizePx: 800 },
]
function curveFor(role) {
if (role === 'enter') return 'ease-out'
if (role === 'exit') return 'ease-in'
if (role === 'move') return 'ease-in-out'
return 'spring' // lively
}
// 持續時間隨移動距離和元素尺寸縮放。基底 + 斜率,並有上下限。
function durationMs(role, distancePx, sizePx) {
const reach = Math.max(distancePx, sizePx * 0.4)
let ms = 140 + reach * 0.45
ms = Math.min(Math.max(ms, 140), 600)
if (role === 'exit') ms *= 0.75 // 退出更快
return Math.round(ms)
}
// 物理變換使用 spring;計時數值使用 interpolate。
function driver(role, property) {
const physical = property === 'scale' || property === 'translate'
if (role === 'lively') return 'spring() // 需要 overshoot'
if (physical && role !== 'move') return 'spring() // 物理,活潑穩定'
return 'interpolate() // 精確,對幀計時'
}
const pad = (s, n) => String(s).padEnd(n)
console.log(pad('ELEMENT', 18), pad('CURVE', 12), pad('MS', 6), pad('FRAMES', 8), 'DRIVER')
for (const r of requests) {
const curve = curveFor(r.role)
const ms = durationMs(r.role, r.distancePx, r.sizePx)
const frames = Math.round((ms / 1000) * fps)
console.log(pad(r.name, 18), pad(curve, 12), pad(ms, 6), pad(frames, 8), driver(r.role, r.property))
}
參考資料
references/twelve-principles— 迪士尼 12 原則到 UI 與產品動畫的完整映射。product-demo-director— 消費此技能 motion spec 以編排時間化場景的導演技能。video-storyboard— 在選擇動畫曲線之前處理鏡頭順序的分鏡技能。kinetic-typography— 專門處理文字字母形態動畫的相關技能。sound-and-music-sync— 將動畫對齊音訊節拍的相關技能。motion-color-and-light— 處理光線與色彩動畫的相關技能。