Apple Design
初期応答
このスキルが特定の質問なしに最初に呼び出された場合は、以下のみで応答します。
Appleスタイルの流動的なWebインターフェースを構築するお手伝いをします。私の知識はAppleのWWDCデザイン講演から得たもので、Web向けに翻訳されています。
ユーザーが質問するまで、これ以上の情報は提供しないでください。
Appleがどのようにして、コンピューターのように感じるのをやめ、ユーザーの延長のように感じられるインターフェースを構築するか。この知識はAppleのWWDCデザイン講演、主に Designing Fluid Interfaces (WWDC 2018) から得たもので、Webプラットフォーム(CSS、Pointer Events、requestAnimationFrame、Motion/Framer Motionなどのスプリングライブラリ)向けに蒸留・翻訳されています。
一貫するテーマは、インターフェースが生き生きと感じられるのは、モーションが現在の画面上の値から始まり、ユーザーの速度を継承し、勢いを前方に投影し、いつでも掴んで逆転できるときです。スプリングはこれをすべて自然に行うためのツールです。なぜなら、本質的に割り込み可能で速度を認識するからです。
核となるアイデア
「インターフェースを私たちの思考や動きの方法に合わせると、魔法のようなことが起こります。それはコンピューターのように感じるのをやめ、私たちのシームレスな延長のように感じられるのです。」
インターフェースは、物理世界のように振る舞うときに流動的になります。つまり、物事が即座に反応し、連続的に動き、慣性を持ち、境界で抵抗し、動きの途中で方向転換できることです。以下はすべて、それに近づくための方法です。
Appleはデザインを、人間の4つのニーズに応えるものとして位置づけています。安全性/予測可能性、理解、達成感、喜びです。ここでのすべてのルールは、これらのいずれかに貢献しています。
1. 応答 — レイテンシを排除する
遅延が発生した瞬間、直接操作性の感覚は「崖から落ちるように」失われます。応答性は、その他すべての基盤です。
- 解放時ではなく、ポインターダウン時に応答する。 ボタンが押された瞬間にハイライトします。
click/タッチアップまでのフィードバックを待つと、死んでいるように感じられます。 - あらゆるレイテンシに警戒する。 デバウンス、人為的なタイマー、トランジション待ち、および約300msのタップ遅延を監査します。入力パス上で重要でないものはすべて後退です。
- フィードバックはインタラクションの最中に継続的に行う必要があり、最後だけではありません。 ドラッグ、スライダー、またはドロワーの場合、ジェスチャー全体を通してポインターと1:1でUIを更新します。ジェスチャーが完了したときだけアニメーションさせないでください。
/* フィードバックは押下時、瞬時に発生する */
.button:active {
transform: scale(0.97);
transition: transform 100ms ease-out;
}
2. 直接操作 — 1:1 トラッキング
「タッチとコンテンツは一緒に動くべきです。」
ユーザーが何かをドラッグするとき、それは指に貼り付いたままである必要があります。そして、掴んだ位置からのオフセットを尊重する必要があります。掴んだときに要素の中央にスナップすると、その幻想は即座に壊れます。
setPointerCaptureを指定したPointer Eventsを使用して、ポインターが要素の境界を離れてもトラッキングが継続するようにします。- 現在のポイントだけでなく、短い 速度/位置の履歴(最後のいくつかの
pointermoveイベント)を追跡します。解放時の速度が必要になります。
el.addEventListener('pointerdown', (e) => {
el.setPointerCapture(e.pointerId);
const grabOffset = e.clientY - el.getBoundingClientRect().top; // 掴んだ位置を尊重
// ...速度用に位置 + タイムスタンプの履歴を追跡
});
3. 割り込み可能性 — 最も重要な原則
「思考とジェスチャーは並行して発生します。」
すべてのアニメーションは、いつでも割り込み可能で方向転換可能でなければなりません。ユーザーは、動いている要素を飛行中に掴んで、アニメーションが終了するのを待たずに逆転できなければなりません。閉じているモーダルをユーザーが再び掴んだ場合は、指に追従する必要があります。最初に閉じ終わってから、再度開くのではありません。
- トランジション中に入力をロックアウトしないでください。
- 常に 表示(現在の)値からアニメーションさせ、ターゲット値からはアニメーションさせないでください。 割り込み時には、要素のライブ画面上のトランスフォームを読み取り、そこから新しいアニメーションを開始します。論理/ターゲット値から開始すると、目に見えるジャンプが発生します。
- ジェスチャー駆動のものにはCSSトランジションと
@keyframesを避ける — それらは飛行中にスムーズに掴んで逆転させることができません。スプリングはデフォルトで現在の値からアニメーションするため、割り込みに正確に必要なものです。 - ジェスチャーが反転するときは、速度をブレンドし、ハードカットしないでください。 反転時に一方のアニメーションを別のアニメーションに置き換えると、速度の不連続性、つまり「レンガの壁」が発生します。再ターゲット時に速度を引き継ぐスプリングライブラリはこれを回避します。(これはiOSの加算アニメーションがネイティブに行うことです。Web上では、現在の速度から再ターゲットするスプリングライブラリを選択してください。)
- 2Dモーションを独立したXとYのスプリングに分解します。 XとYの速度が異なる場合、2D距離に対する単一のスプリングは同期がずれます。
4. アニメーションではなく振る舞い — スプリングを使用する
「アニメーションを、インターフェースによって規定されたものではなく、あなたとオブジェクトの間の会話として考えてください。」
事前にスクリプト化された固定長のアニメーションは、新しい入力に応答できません。スプリングは応答できます。新しい入力はターゲットを変更するだけで、モーションは連続的に保たれます。ユーザーが触れる可能性のあるものにはスプリングを使用してください。
Appleは意図的に物理トリプレット(質量/剛性/減衰)を、デザイナーに優しい2つのパラメータに置き換えました。これらで考えてください。
- 減衰比 (Damping ratio) — オーバーシュートを制御します。
1.0= 臨界減衰、バウンスなし、スムーズな整定。< 1.0= オーバーシュートして振動します。低いほど弾みます。 - 応答 (Response) — 値がターゲットに到達する速さ(秒単位)。低いほどスナップが効きます。これは「持続時間」ではありません — スプリングに固定の持続時間はありません。その整定時間はパラメータから生じます。
デフォルト値:
- ほとんどのUIは 減衰
1.0(臨界減衰)から始めます — 優雅で気を散らしません。 - ジェスチャー自体が勢いを持っていた場合(フリック、スロー、ドラッグ解放)にのみ、バウンス(減衰 ~
0.8)を追加します。フェードインしたばかりのメニューでのオーバーシュートは間違って感じられます。フリックしたカードでのオーバーシュートは正しく感じられます。
Appleが出荷している具体的な値:
| インタラクション | 減衰 | 応答 |
|---|---|---|
| 移動 / 再配置 (例: PiP) | 1.0 | 0.4 |
| 回転 | 0.8 | 0.4 |
| ドロワー / シート | 0.8 | 0.3 |
Webマッピング (Motion / Framer Motion): bounce + duration スプリングAPIは、Appleの減衰 + 応答に近いマッピングです。安全なハウススタイルは、デフォルトでどこでも damping: 1.0 のスプリングを使用することです。バウンスは勢い駆動の物理的インタラクションのために予約します。
import { animate } from 'motion';
// 臨界減衰デフォルト(オーバーシュートなし)
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をトリガーに設定し、ボタンとコンテンツの間の空間的関係が明らかになるようにします。(これは、ポップオーバーがその中央ではなく、トリガーから拡大するのと同じ原点認識のポイントです。) - 可逆的なトランジションではイージングをミラーリングして、発信パスが復路パスと一致するようにします(2方向に逆の3次ベジェ制御点を使用)。
8. ジェスチャーの方向を示唆する
人間は軌道から最終状態を予測します。中間フレームは物事がどこに向かっているかを示す必要があります — Control Centerモジュールは「あなたの指に向かって上と外に成長します」。中間フレームを結果に向け、盲目的に補間しないでください。
9. ラバーバンディング — ソフトな境界
端では、急停止する代わりに徐々に抵抗します。ハードストップは「フリーズした」と読まれます。継続的な抵抗は「応答性はあるが、これ以上何もない」と読まれます。ユーザーが境界を超えてドラッグするほど、増加する減衰を適用します。
// 境界を超えるほど、要素の追従は少なくなる — 実際のものは停止する前に減速する
function rubberband(overshoot, dimension, constant = 0.55) {
return (overshoot * dimension * constant) / (dimension + constant * Math.abs(overshoot));
}
10. ジェスチャーデザインの詳細(「感触」チェックリスト)
- タップ: タッチダウン時にハイライト(即座)、タッチアップ時にコミット。ターゲットの周りに約10pxのヒステリシス/ヒットパディングを追加し、ドラッグアウェイによるキャンセルと戻ることを許可します。
- ドラッグ/スワイプ: 方向を確定する前に小さな移動閾値(ヒステリシス、約10px)を必要とし、その後1:1で追跡します。
- 最初の動きからすべての推定されるジェスチャーを並行して検出し、意図が明確になったら確実に負けた方をキャンセルします。最終状態のみを報告する認識子(
swipeleftタイプのイベント)は避けてください。それらはフィードバックに必要な継続的なトラッキングを捨ててしまいます。 - 曖昧性解消の遅延を最小限に抑えます。 ダブルタップ検出はどうしてもシングルタップを遅らせます。そのコストは、ダブルタップが実際に存在する場所でのみ支払ってください。
11. フレームレベルの滑らかさ
滑らかさは、フレームレートだけでなく、フレーム内の内容に関係します。
- 1フレームあたりの位置変化を知覚閾値以下に保ち、ちらつきを避けます。
- 非常に高速なモーションの場合、微妙なモーションブラー/ストレッチは速度をエンコードし、硬く鋭い線よりも読みやすくなります。
requestAnimationFrameはWebのディスプレイ同期クロックです(AppleはCADisplayLinkを使用します)。コンポジターに優しいプロパティ —transformとopacity— のみをアニメーションさせ、モーションが差し迫っている場所ではwill-changeでヒントを与えます。
12. マテリアルと奥行き — 半透明が階層を伝える
Appleは半透明マテリアルを、フォーカスを奪わずに構造をもたらす浮遊機能レイヤーとして使用します。Web上では、backdrop-filter で近似します。
- ナビゲーション/ツールバー/シートを半透明レイヤーとして構築します(
backdrop-filter: blur()+ 半透明の背景)。コンテンツはその下をスクロールします。固定ストリップを消費する不透明なバーではありません。 - マテリアルの重みが階層をエンコードします: 暗く/重いマテリアルは構造領域(サイドバー)を分離します。明るいマテリアルはインタラクティブ要素(ボタン)に注意を引きます。明るい半透明の表面を別の上に重ねないでください。 可読性が損なわれます。
- 大きい表面はより厚く読まれるべきです: 小さなチップよりも強いぼかしと深い影。コンテキストを考慮した影を検討します — 分離のために忙しい/テキストコンテンツの上では重く、プレーンな背景の上では軽く。
- フォーカスするために暗くし、流れを保つために分離します。 モーダルタスクは、表面を暗くするスリムと組み合わせ、背景を押し戻します。並行する非ブロッキングパネルは、スリムなしで半透明とオフセットを使用し、流れが途切れないようにします。積み重ねられたシートの場合、各親レイヤーを徐々に暗くし、押し戻します。
- ビビッドさにより、変化する背景の上でもテキストの可読性を維持します。 ぼかし/半透明の表面の上では、フラットなグレーのテキストを使用しないでください。代わりに、より高いコントラスト、やや太めのウェイト、そして小さな文字間隔のバンプを使用します。色は半透明の前景ではなく、ソリッドなレイヤーに配置します。
- ハードな仕切りではなく、スクロール端効果。 スティッキーヘッダーの下に1pxの境界線を配置する代わりに、フローティング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. マルチモーダルフィードバック — モーション + サウンド + ハプティクス
感覚を組み合わせるための3つのルール(Designing Audio-Haptic Experiences より):
- 因果関係 — 何がフィードバックを引き起こしたかが明らかでなければなりません。実際の因果イベント(トグルが切り替わる、アイテムが所定の位置にスナップする)でトリガーし、その特性をアクションの物理性に一致させます。
- 調和 — ビジュアル、サウンド、ハプティクスは同じフレームで発火しなければなりません。それらの間のレイテンシは幻想を破壊します。
- 適応性 — 各フィードバックチャネルは、コンテキスト(サイレントモード、バッテリー節約など)に応じてスケールダウンできる必要があります。ジェスチャーの結果が無効になった場合(ドラッグ解除など)は、すべてのフィードバックを即座にキャンセルします。
14. 動きの抑制
prefers-reduced-motionを尊重します。すべてのアニメーションとトランジションをオフにしないでください。代わりに、コンポーネントのスケールと不透明度のフェードに切り替えます(瞬時の出現は衝撃的です)。- Appleは「直接操作を常に許可する」という原則に従います。ユーザーがドラッグした場合、
prefers-reduced-motionが設定されていても、要素は指に追従する必要があります。パラメータ化された減少(例: バウンスなし、速度の50%)は、完全なモーションの抑制と非アクティブの間の安全な中間です。
レイアウトとタイポグラフィに関するAppleの見解
Appleはレイアウトを、オブジェクトを編成する明確なグリッドシステムではなく、マクロ(セクション)からミクロ(内部要素)へのデュアルパス階層として扱います。重要な原則は 十分な余白 です。
- OS全体で8pxのグリッド — これは標準のインセット、アイコンのサイズ、ガターです。サブグリッド(4px)はテキストの行間と密接なグループ分けに使用します。
- タイポグラフィのサイズ設定は、ユーザーの読み取り距離(目と画面)を考慮します。 小さいテキストはより多くのコントラストと広い文字間隔(トラッキング)を必要とします。光学サイズのバリエーションが利用可能な場合は使用します。
- テキストブロック内の各行(テキストの行)を単一の論理ユニットとして扱います。 行の長さは45〜75文字(中国語/日本語の場合は20〜40文字)に制限します。
- すべてのタイポグラフィ階層は、ウェイトとサイズを通じて表現する必要があります。 サイズは用途を決定します(大 = 見出し、小 = キャプション)。同じサイズのテキストに異なる色だけを使用して階層を作らないでください。
- 行間(行の高さ)はコンテンツの密度に合わせます: 見出しではタイト(< 1.2)、本文では広く(1.4〜1.6)。絵文字とインライン画像は、ベースラインではなく行の高さ(emボックス)に合わせます。
- 単一の見出し/ビューのためにフォントウェイトまたはサイズを調整することは避けてください。 システム全体のタイポグラフィスケール(AppleのDynamic Type)は、
UIFontMetricsに類似したスケーリングを通じてすべてのバリエーションを一貫して処理します。 - 左揃えは一貫性のためにあります。 中央揃えは強調/休憩のために予約します(ただし、長いテキストには使用しないでください)。テキストの折り返しが不適切に行われる単語(例: 必須のハイフネーション)を避けるために、不整列を生成します。
- グラフィックアイテム(画像、ビデオ、インタラクティブ要素)の間のスペースは、隣接する要素ではなく、グリッド線に基づいて均等にする必要があります。 Appleはこれを「いくつかの平行した垂直の道」と呼びます。
使用法
このスキルを呼び出すには:
Apple Designの原則を使用して、カスタムのボトムシートコンポーネントのレビューを手伝ってください。
または、特定のガイダンスのために:
スプリングアニメーションを使用してドラッグ可能なモーダルを実装する方法を説明してください。
または:
このコンポーネントをApple Designの標準と比較してレビューしてください。
- 特定のコンポーネント、タイプのインタラクション、またはAppleデザインシステムの側面に関する質問への回答。
- ジェスチャー駆動UI、スプリングアニメーション、または半透明マテリアルのレビューまたは実装支援。
- Appleのデザイン原則をWeb実装に適用する方法についての説明。
プライバシーに関する注意: IP、請求、または個人を特定できる情報は保持していません。ユーザーはログの削除をリクエストできます。