apple-design

apple-design

熱門

Apple 的介面設計與流暢、物理性動畫方法,轉化為網頁實作。在建立或審查手勢驅動 UI、彈簧動畫、拖曳/滑動/面板互動、動量與可中斷轉場、半透明材質與深度、字體排印(光學尺寸、字距、行距)、減少動畫,或 Apple 風格介面背後的設計基礎(回饋、空間一致性、克制)時使用。

1.1萬星標
622分支
更新於 2026/7/11
SKILL.md
readonlyread-only
name
apple-design
description

Apple 的介面設計與流暢、物理性動畫方法,轉化為網頁實作。在建立或審查手勢驅動 UI、彈簧動畫、拖曳/滑動/面板互動、動量與可中斷轉場、半透明材質與深度、字體排印(光學尺寸、字距、行距)、減少動畫,或 Apple 風格介面背後的設計基礎(回饋、空間一致性、克制)時使用。

Apple Design

Apple 如何打造不再像電腦、而像你身體延伸的介面。這些知識來自 Apple 的 WWDC 設計講座——主要是《Designing Fluid Interfaces》(WWDC 2018)——經過提煉並轉化為網頁平台(CSS、Pointer Events、requestAnimationFrame、Motion/Framer Motion 等彈簧函式庫)。

主線:當動畫從螢幕上的當前數值開始、繼承使用者的速度、向前投射動量、並且可以在任何瞬間被抓住並反向時,介面就有了生命力。彈簧是讓這一切變得自然的工具,因為它們本質上可中斷且能感知速度。

核心概念

「當我們讓介面貼合我們的思考與動作方式時,神奇的事就會發生——它不再像電腦,而開始像我們無縫的延伸。」

當介面像物理世界一樣運作時,它就是流暢的:即時回應、連續移動、帶有動量、在邊界處產生阻力、並且可以在移動中途被重新導向。以下所有內容都是為了更接近這個目標。

Apple 將設計定位為滿足四種人類需求:安全感/可預測性、理解、成就、喜悅。 這裡的每一條規則都服務於其中之一。

1. 回應——消除延遲

一旦出現延遲,直接操作的感覺就會「斷崖式下降」。回應是所有其他一切的基礎。

  • 在手指按下時回應,而不是放開時。 按下的瞬間就讓按鈕產生視覺變化。等到 click/手指放開才顯示回饋,感覺就像死了一樣。
  • 警惕每一毫秒的延遲。 檢查 debounce、人工計時器、轉場等待時間,以及約 300ms 的點擊延遲。任何輸入路徑上非必要的東西都是退步。
  • 回饋必須在互動過程中持續提供,而不只是在結束時。 對於拖曳、滑桿或抽屜,整個過程中都要讓 UI 與手指 1:1 同步更新——千萬不要只在手勢完成時才動畫。
/* 回饋存在於按下的瞬間,而且即時生效 */
.button:active {
  transform: scale(0.97);
  transition: transform 100ms ease-out;
}

2. 直接操作——1:1 追蹤

「觸碰與內容應該一起移動。」

當使用者拖曳某個東西時,它必須緊貼手指——並且要考慮抓取時的偏移量。如果抓取時直接對齊元素中心,會立刻破壞沉浸感。

  • 使用 Pointer Events 搭配 setPointerCapture,這樣即使指標離開元素邊界,追蹤仍能持續。
  • 記錄短暫的速度/位置歷史(最近幾次 pointermove 事件),而不只是當前點——你需要在放開時知道速度。
el.addEventListener('pointerdown', (e) => {
  el.setPointerCapture(e.pointerId);
  const grabOffset = e.clientY - el.getBoundingClientRect().top; // 尊重抓取位置
  // ...記錄位置與時間戳歷史以計算速度
});

3. 可中斷性——最重要的原則

「想法與手勢是同步發生的。」

每個動畫都必須能在任何時刻被中斷並重新導向。使用者必須能在移動中的元素飛行途中抓住它並反向移動,而不需要等動畫結束。一個正在關閉的模態視窗,如果使用者再次抓住它,應該要跟著手指移動——而不是先關閉完成再重新開啟。

  • 絕不在轉場期間鎖定輸入。
  • 永遠從當前的顯示數值開始動畫,而不是目標數值。 中斷時,讀取元素即時的螢幕 transform 數值,並從那裡開始新的動畫。從邏輯/目標數值開始會導致明顯的跳躍。
  • 避免在手勢驅動的互動中使用 CSS transitions 和 @keyframes——它們無法在飛行途中被順暢地抓住並反向。彈簧預設從當前數值開始動畫,這正是中斷所需要的。
  • 當手勢反向時,要混合速度,而不是硬切。 在反向點用一個動畫取代另一個會造成速度不連續,就像撞到「磚牆」。能夠在重新設定目標時保留速度的彈簧函式庫可以避免這個問題。(這就是 iOS 的 additive animations 原生做到的事;在網頁上,選擇一個能在重新設定目標時從當前速度繼續的彈簧函式庫。)
  • 將 2D 運動分解為獨立的 X 和 Y 彈簧。 如果 X 和 Y 有不同的速度,單一彈簧作用在 2D 距離上會導致不同步。

4. 行為重於動畫——使用彈簧

「把動畫想成你與物體之間的對話,而不是介面預先寫好的腳本。」

預先編寫、固定時長的動畫無法回應新的輸入。彈簧可以——新的輸入只是改變目標,而運動保持連續。任何使用者可以觸碰的東西都應該使用彈簧。

Apple 刻意將物理三參數(質量/剛度/阻尼)替換為兩個設計師友善的參數。用這些來思考:

  • 阻尼比——控制 overshoot。1.0 = 臨界阻尼,沒有彈跳,平穩到達。< 1.0 = 超過目標並震盪。數值越低越有彈性。
  • 回應時間——數值到達目標的快慢,單位為秒。數值越低越靈敏。這不是「持續時間」——彈簧沒有固定持續時間;它的穩定時間是由參數自然產生的。

預設值:

  • 大多數 UI 從阻尼 1.0(臨界阻尼)開始——優雅且不分散注意力。
  • 只有在手勢本身帶有動量時(例如輕彈、拋擲、拖曳放開)才加入彈跳(阻尼約 0.8)。一個只是淡入的選單出現 overshoot 感覺不對;但你輕彈出去的卡片出現 overshoot 就感覺很自然。

Apple 實際使用的數值:

互動 阻尼 回應時間
移動/重新定位(例如子母畫面) 1.0 0.4
旋轉 0.8 0.4
抽屜/面板 0.8 0.3

網頁對應(Motion / Framer Motion): bounce + duration 的彈簧 API 與 Apple 的阻尼 + 回應時間非常接近。一個安全的團隊風格是預設到處使用 damping: 1.0 的彈簧;保留彈跳給由動量驅動的物理互動。

import { animate } from 'motion';

// 臨界阻尼預設(無 overshoot)
animate(el, { y: 0 }, { type: 'spring', bounce: 0, duration: 0.4 });

// 動量互動——稍微彈跳,只因為前面有輕彈動作
animate(el, { y: target }, { type: 'spring', bounce: 0.2, duration: 0.4 });

5. 速度交接——拖曳與動畫之間的無縫銜接

當手勢結束時,動畫必須以手指的準確速度繼續,這樣拖曳與動畫之間就不會有可見的接縫。這是區分「流暢」與「還不錯」的最關鍵細節。

將指標放開時的速度作為彈簧的初始速度傳入。有些彈簧 API 需要相對速度——將其除以到目標的剩餘距離來正規化:

relativeVelocity = gestureVelocity / (targetValue − currentValue)

範例:元素在 y=50,目標 y=150(還有 100px),手指移動速度 50px/s → 初始彈簧速度 = 50 / 100 = 0.5。Framer Motion / Motion 直接接受絕對 px/s 速度(velocity 選項),所以你通常直接傳入原始數值。

6. 動量投射——動畫到手指「要去」的位置

「用小輸入產生大輸出。」

不要從放開點直接跳到最近的邊界。使用速度來投射最終停留位置——就像滾動減速一樣——然後跳到離投射點最近的目標。這就是讓輕彈感覺像把元素丟出去的原因。

Apple 的準確投射函數(來自《Designing Fluid Interfaces》的範例程式碼):

// decelerationRate ≈ 0.998 為一般滾動感;0.99 為更靈敏
function project(initialVelocity /* px/s */, decelerationRate = 0.998) {
  return (initialVelocity / 1000) * decelerationRate / (1 - decelerationRate);
}

const projectedEndpoint = currentPosition + project(releaseVelocity);
const target = nearestSnapPoint(projectedEndpoint);   // 從投射點選擇目標
animateSpringTo(target, { velocity: releaseVelocity }); // 然後交接速度(§5)

注意:物理課本中的 v²/(2·decel) 不是 Apple 使用的公式——請使用上述指數衰減形式。這是好的底部面板和輪播(Vaul、Embla)的標準行為。

7. 空間一致性——對稱路徑、固定錨點

「如果某個東西以某種方式消失,我們會預期它從原來的地方出現。」

  • 進出應沿同一路徑。 從右側滑入的面板必須向右側關閉。從右進、從底出會讓人感到脫節與困惑。
  • 將互動錨定到其來源。 選單、彈出視窗或面板應該從觸發它的元素處產生——將 transform-origin 設為觸發元素,這樣按鈕與內容之間的空間關係就很明顯。(這與彈出視窗從觸發點縮放、而不是從中心縮放的概念相同。)
  • 在可逆轉場中鏡像 easing 曲線,使離開路徑與返回路徑一致(對兩個方向使用反向的 cubic-bézier 控制點)。

8. 在手勢方向上給予提示

人類會從軌跡預測最終狀態。中間的運動應該暗示事物將往哪裡去——控制中心模組「朝你的手指方向向上向外生長」。讓中間幀指向結果,而不只是盲目地內插到目標。

9. 橡皮筋效果——軟邊界

在邊界處,應逐漸增加阻力,而不是硬生生停止。硬停止感覺像「凍結」;持續的阻力感覺像「有回應,但這裡沒有更多東西了」。應用阻尼,當使用者拖曳超過邊界越遠時,阻尼越大。

// 超過邊界越遠,元素跟隨得越少——真實物體在停止前會減速
function rubberband(overshoot, dimension, constant = 0.55) {
  return (overshoot * dimension * constant) / (dimension + constant * Math.abs(overshoot));
}

10. 手勢設計細節(「手感」檢查清單)

  • 點擊: 在觸碰按下時(立即)顯示高亮,在觸碰放開時確認。在目標周圍加入約 10px 的遲滯/點擊填充,並允許透過拖曳離開再回來取消。
  • 拖曳/滑動: 在確定方向前需要一個小的移動閾值(遲滯,約 10px),然後進行 1:1 追蹤。
  • 從第一次移動就平行偵測所有可能的手勢,然後在意圖明確時自信地取消失敗者。避免只報告最終狀態的辨識器(例如 swipeleft 類型的事件)——它們會丟棄你需要的連續追蹤資訊來提供回饋。
  • 最小化辨識延遲。 雙擊偵測無可避免地會延遲單擊;只有在確實存在雙擊的地方才付出這個代價。

11. 幀級流暢度

流暢度關乎幀的內容,而不只是幀率。

  • 保持每幀的位置變化低於感知閾值,以避免閃爍。
  • 對於非常快的運動,微妙的動態模糊/拉伸可以編碼速度,比銳利的硬線條更容易閱讀。
  • requestAnimationFrame 是網頁上與顯示器同步的時鐘(Apple 使用 CADisplayLink)。只對合成器友善的屬性進行動畫——transformopacity——並在即將發生動畫的地方使用 will-change 提示。

12. 材質與深度——半透明傳達層級

Apple 使用半透明材質作為浮動的功能層,帶來結構感而不搶走焦點。在網頁上,可以用 backdrop-filter 近似模擬。

  • 將導航欄/工具欄/面板建構為半透明層backdrop-filter: blur() + 半透明背景),內容在其下方滾動——而不是佔據固定區域的不透明條。
  • 材質重量傳達層級: 較暗/較重的材質區分結構區域(側邊欄);較輕的材質將注意力吸引到互動元素(按鈕)。絕不要將一個淺色半透明表面疊在另一個上面——可讀性會崩潰。
  • 較大的表面應感覺較厚: 比小晶片更強的模糊和更深的陰影。考慮情境感知的陰影——在繁忙/文字內容上使用較重的陰影以增加分離感,在簡單背景上使用較輕的陰影。
  • 變暗以集中焦點,分離以保持流暢。 一個模態任務會將表面與變暗的遮罩結合,並將背景推後/向下。一個平行、非阻塞的面板使用半透明和偏移而不加遮罩,這樣流程就不會中斷。對於堆疊的面板,逐漸變暗並將每個父層向後推。
  • Vibrancy 讓文字在變化的背景上保持可讀。 在模糊/半透明表面上,不要使用純灰色文字——使用更高對比度、稍重一點的字重,並稍微增加字距。將顏色放在實色層上,而不是半透明前景上。
  • 使用滾動邊緣效果,而不是硬分隔線。 與其在固定標題下使用 1px 邊框,不如在內容與浮動 chrome 重疊處淡出一個小的模糊/漸層遮罩——只在浮動 UI 實際重疊內容的地方使用。
  • 材質化,而不只是淡入淡出。 對於玻璃/模糊表面,在進入/離開時同時對模糊半徑和縮放進行動畫,這樣表面讀起來就像一個真實的材質到達,而不是單純的不透明度淡入。
.toolbar {
  background: rgba(255, 255, 255, 0.6);
  backdrop-filter: blur(20px) saturate(180%);
  border-top: 1px solid rgba(255, 255, 255, 0.4); /* 亮色頂邊 = 光線捕捉到材質 */
}

13. 多模態回饋——動畫 + 聲音 + 觸覺

結合感官的三條規則(來自《Designing Audio-Haptic Experiences》):

  1. 因果關係——必須清楚是什麼導致了回饋。在實際的因果事件上觸發它(例如開關切換、項目到位),並使其特性與動作的物理性匹配。
  2. 和諧——視覺、聲音和觸覺必須在同一幀觸發。它們之間的延遲會破壞沉浸感。不要讓 CSS 轉場落後於音訊/觸覺(Vibration API)。
  3. 實用性——只在有意義的地方加入回饋。將觸覺/聲音保留給重要的時刻(成功、錯誤、確認、到位)。過度回饋會訓練使用者忽略所有回饋。

14. 減少動畫與無障礙

減少動畫不代表沒有回饋——它意味著更溫和、不影響前庭系統的等效方式。回應三個獨立的訊號並將其融入你的元件:

  • prefers-reduced-motion: reduce——將滑動/彈簧/視差效果替換為簡短的不透明度交叉淡入淡出或靜態轉場。移除彈性/overshoot。保留有助於理解的透明度/顏色變化。
  • prefers-reduced-transparency: reduce——讓半透明表面變得更不透明/實色:提高背景不透明度,移除模糊。
  • prefers-contrast: more——使用接近實色的背景,並加上明確的對比邊框。

此外:避免全視口移動背景、緩慢循環的震盪(接近 0.2 Hz / 每 5 秒一個週期),以及突然的亮度跳躍(淺色↔深色主題切換應平滑)。讓大型移動物體在移動時保持半透明,並在大型重新定位時淡出大表面,穩定後再淡入。

@media (prefers-reduced-motion: reduce) {
  .sheet { transition: opacity 200ms ease; transform: none !important; }
}
@media (prefers-reduced-transparency: reduce) {
  .toolbar { background: white; backdrop-filter: none; }
}

15. 字體排印——光學尺寸、字距、行距

Apple 設計字型時會根據尺寸改變形狀;同樣的原則也適用於網頁。(來自《The Details of UI Typography》,WWDC 2020。)

  • 字距(letter-spacing)是尺寸相關的——絕不要對所有尺寸使用同一個值。 大型顯示文字需要字距(字體變大時字母看起來太分散);小型文字需要稍微字距以保持可讀性。固定的 letter-spacing 在某些地方一定是錯的。標題要緊縮,內文接近 0
  • 行距(line-height)與尺寸成反比。 大型標題要緊湊,內文要寬鬆。對於有高上升/下降筆畫的字體要增加行距;對於密集、資訊量大的 UI 要緊縮。
  • 將層級建立在字重 + 尺寸 + 行距的組合上,而不只是尺寸。用字重來強調——它能在不佔用更多空間的情況下增加存在感。
  • 尊重使用者的文字大小設定(Dynamic Type)。讓佈局隨著文字縮放——使用 rem/em 作為間距單位,而不是固定的 px——這樣較大的字體就不會破壞佈局。
  • 在自訂字體之前,預設使用平台的系統字體;它已經內建了光學尺寸、字距表和可讀性調整。只有在有理由時才覆蓋。
:root { font: 100%/1.5 system-ui, sans-serif; } /* 內文:系統字體,舒適行距 */

.display {
  font-size: clamp(2rem, 5vw, 4rem);
  line-height: 1.05;        /* 大型文字緊湊行距 */
  letter-spacing: -0.02em;  /* 隨著尺寸增大使用負字距 */
  font-optical-sizing: auto;
}

16. 設計基礎——八項原則

上述的動畫與工藝服務於 Apple 的八項設計原則(《Principles of Great Design》,WWDC 2026)。將這些作為你推理時使用的名稱:

  1. 目的。 有意識地製作;決定建什麼。每個功能都在索取使用者的時間、注意力和信任——只在有回報的地方花費這個預算。
  2. 主導權。 讓使用者保持控制:提供選擇,不要強迫單一路徑。用寬容來支持——輕鬆復原失誤,只有對真正破壞性、不可逆的操作才使用確認對話框(謹慎使用;過度使用會訓練人們直接點過)。
  3. 責任。 以使用者的利益行事。隱私:在正確的時機詢問,只詢問必要的內容,保持透明。安全:預測誤用和傷害——尤其是 AI(一個過敏食譜應用程式絕不能建議有害的食材)。加入預覽、確認、免責聲明;刪除風險大於價值的功能。
  4. 熟悉感。 建立在人們已知的事物上。使用既不太具體也不太抽象的隱喻(垃圾桶代表刪除),並尊重它們的物理特性。保持一致:看起來相同的東西必須行為相同且位於相同位置(在 macOS 上關閉永遠在左上角),這樣人們才能預測接下來會發生什麼。只有當你能證明更好時才打破熟悉的模式——然後測試它,不要假設。
  5. 靈活性。 為不同的情境、裝置和各種能力設計。適應平台(iPhone = 快速觸碰;桌面 = 精確指標控制的深度工作流程)和情境。包容性設計(年齡、語言、專業知識、無障礙)。當沒有單一佈局適合所有人時,讓使用者個人化——重新排列控制項,隱藏他們不用的東西。
  6. 簡潔——不是極簡。 去除不必要的東西,讓核心目的閃耀;把所有東西藏在一個地方看起來極簡,但並不簡潔。要簡潔(白話文、無術語、步驟少)且清晰(使用層級——順序、間距、對比——讓最重要的東西最明顯)。每個元素都必須證明自己的存在;有時加入上下文反而能簡化(例如顯示剩餘時間的影片進度條)。先顯示常見路徑,進階選項再往下一層。
  7. 工藝。 不妥協的細節關注建立信任。優美的字體排印、適應淺色/深色的色彩、清晰的圖示,以及提供即時自然回饋的反應式動畫。沒有什麼是隨機的——每個間距、時間和對齊數值都是你可以辯護的刻意選擇。抖動的滾動、未對齊的圖示、以及旋轉時破版的佈局,讀起來就像粗心大意。工藝需要迭代和長壽——隨著功能和硬體變化持續演進設計。
  8. 喜悅。 這是把其他七項做對的結果,而不是事後貼上的五彩紙屑。決定你希望人們感受到的情緒(平靜、自信、興奮),並在每個決策中強化它。

服務於這些原則的戰術規則:

  • 回饋有四種: 狀態、完成、警告、錯誤。確認有意義的動作,揭露進行中的狀態,在問題發生前警告,即時驗證(而不是在提交時)。
  • 尋路。 每個畫面都應該回答:我在哪裡?我可以去哪裡?那裡有什麼?我如何離開?絕不讓使用者陷入困境。
  • 分組與對應。 接近性暗示關係;將控制項放在它所影響的項目附近,並安排控制項以反映它們所改變的內容。如果你需要用標籤來說明控制項,那麼對應關係就很弱。
  • 直接、具體的標籤勝過安全的通用標籤。 根據內容命名導航項目(例如「進度」、「資料庫」),而不是模糊的總稱(例如「首頁」)。具體性創造可預測性。

17. 流程

  • 互動式原型——一個互動式示範勝過「一百萬個靜態設計」。 你透過建構和操作來發現介面;一個可運作的原型也設定了具體的標準,防止最終實作品質平庸。
  • 同時設計互動與視覺。 「你不應該能分辨出一個在哪裡結束、另一個從哪裡開始。」動畫不是像素完成後才加上的一層。
  • 在真實情境中與真實使用者測試,並以全新的眼光審視動畫——用慢動作/逐幀播放來捕捉全速時看不見的東西。

快速參考

需求 技巧 具體數值
預設 UI 彈簧 臨界阻尼,無 overshoot damping 1.0response 0.3–0.4
動量/輕彈彈簧 欠阻尼,輕微彈跳 damping ~0.8response 0.3–0.4
手勢 → 彈簧速度 交接放開速度 若正規化則為 gestureVelocity / (target − current)
輕彈落點 投射動量 current + (v/1000)·d/(1−d)d ≈ 0.998
乾淨中斷 從顯示(即時)數值開始 讀取螢幕上的 transform
避免反向「磚牆」 在重新設定目標時保留速度 能混合速度的彈簧
可逆轉場 鏡像 easing 曲線 反向 cubic-bézier
決定反向或確認 使用速度的正負號,而不是位置 在放開時
1:1 拖曳 Pointer Events + capture 尊重抓取偏移
回饋 在手指按下時,持續提供 絕不只發生在結束時
邊界 橡皮筋效果,不要硬停止 漸進式阻力
半透明 chrome backdrop-filter 內容在其下方滾動
字體字距 尺寸相關,絕不固定 大型文字緊縮(-0.02em),內文接近 0
減少動畫 交叉淡入淡出,而不是滑動/彈簧 @media (prefers-reduced-motion)