mobile-feel について
mobile-feelは、モバイルWebの設計・開発向けガイドです。タッチ、ビューポート、入力、レイアウト、パフォーマンスを扱い、スマートフォンの利用状況に沿った画面の確認に役立ちます。
主な機能
- 01タッチ領域とジェスチャーの指針
- 02ビューポートとセーフエリアの処理
- 03キーボードとフォーム入力の指針
- 04ボトムシートとナビゲーションのパターン
- 05スクロール、フィードバック、性能の確認
活用シーン
- 01スマートフォン画面のタッチ領域とレイアウト確認
- 02キーボード表示時に入力欄を見える状態にする
- 03ボトムシートとナビゲーションの操作設計
- 04モバイルのスクロールと読み込みのテスト
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ paragonhq/ stealth-skills --skill mobile-feelインストール後、Claude Code で/mobile-feelを入力して読み込まれたか確認します。
詳細
mobile-feel
スマートフォンは小さなデスクトップではありません。ポインターは親指であり、ビューポートは動き、キーボードが画面の半分を占め、操作ミスは手に伝わります。
目標
- タップ可能なものは、見た目が20pxでも最小44×44pxにします。アイコンを大きくするのではなく、パディングまたは疑似要素でヒット領域を広げます。
- 隣り合うターゲット間には、最低8pxの間隔を設けます。
- 主な操作は、親指を置く画面下部3分の1に配置します。破壊的な操作はそこから離します。
- ターゲットは行全体であり、行内のテキストではありません。
ビューポート
100dvhを使い、100vhは使いません。vhにはブラウザーのUIが含まれますが、dvhは実際に表示されている領域を表します。ブラウザーのUIが隠れてもサイズを変えたくない要素にはsvhを使います。<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">。viewport-fit=coverを使うとセーフエリアのインセットを利用できます。user-scalable=noやmaximum-scale=1は絶対に使わないでください。- 固定またはスティッキー配置する端の要素すべてにセーフエリアを適用します。
.bottom-bar { padding-bottom: max(12px, env(safe-area-inset-bottom)); }
.top-bar { padding-top: env(safe-area-inset-top); }
タッチ動作
* { -webkit-tap-highlight-color: transparent; }
button, a, [role="button"] { touch-action: manipulation; } /* no 300ms delay, no double-tap zoom */
.drag-handle { touch-action: none; } /* only on the handle */
.scroll-region { overscroll-behavior: contain; -webkit-overflow-scrolling: touch; }
- 削除したタップ時のハイライトを独自の
:active状態で置き換えないと、タップしても反応がないように感じられます。 - タッチデバイスでは、
:hoverのスタイルがタップ後も残ります。すべてのホバーを@media (hover: hover)で制御します。 - iOSでは、
touchstartリスナーのない要素で:activeが不安定です。pointerdownで設定し、pointerup/pointercancelで解除するデータ属性のほうが確実です。 - 長押しするとテキストが選択され、リンクにはコールアウトが表示されます。
-webkit-touch-callout: noneとuser-select: noneはコントロールにのみ適用し、コンテンツには適用しません。
入力欄
- 入力欄のフォントサイズは16px以上にします。そうしないと、iOSではフォーカス時にページがズームされ、ズームが元に戻りません。
inputmode="numeric"、"email"、"tel"、"decimal"、"search"を指定し、適切なキーボードを表示します。autocompleteの値を設定します(email、one-time-code、cc-numberなど)。ユーザー名とコードにはautocapitalize="none"を指定します。- 最後の入力欄には
enterkeyhint="send"/"search"/"done"を指定します。 - フォーカス中の入力欄がキーボードより上に表示されるようにします。
visualViewportのresizeを監視して入力欄が見える位置までスクロールするか、コンポーザーをvisualViewport.heightに固定します。 - iOSでは、キーボードを開くと固定要素が移動します。位置を維持する必要があるものは、ビジュアルビューポートに合わせたサイズのコンテナー内で
position: absoluteを使います。
シート
ボトムシートは、スマートフォン向けのダイアログです。
- 指に1対1で追従するようにドラッグで閉じられるようにします。約35%を超えて離すか、下向きの速度が十分にある場合は閉じ、それ以外は元の位置に戻します。
- コンテンツに自然な区切りがある位置にスナップポイントを設けます(のぞき見、半分、全画面)。
- ドラッグに応じてバックドロップの不透明度を変化させます。
- シートが全開位置にあるときだけ、シート内のコンテンツをスクロールできるようにします。それまでは、ドラッグでコンテンツではなくシートを動かします。
- レイアウトをずらさずに、ボディのスクロールをロックします。
- 上部に
--fg-4を使った36×5pxのつまみを配置します。 - 物理挙動が実装済みのライブラリー(Vaul)を使い、自分で実装しないでください。
スマートフォンでは、全画面ダイアログは下からスライドして表示します。デスクトップでは、中央にフェードインして表示します。同じコンポーネントに2種類の表示方法を用意します。
下部ナビゲーション
- 3〜5項目を、アイコンとラベル付きで配置します。高さは49〜56pxとし、セーフエリア分を加えます。
- アクティブな項目には
--fgを、その他の項目には--fg-3を使います。アクセントカラーで塗りつぶさないでください。 - アクティブな項目をタップすると、そのビューを先頭までスクロールします。
- スクロールダウン時は非表示にし、コンテンツにスペースが必要な場合に限り、スクロールアップ時に再表示します。それ以外の場合は表示したままにします。
スクロール
- 可能な限り、画面ごとにスクロール領域を1つにします。スマートフォンでの入れ子スクロールは使いづらさの原因になります。
- プルトゥリフレッシュは適切に実装するか、スクローラーに
overscroll-behavior-y: containを設定して、ブラウザーのプルトゥリフレッシュが起動しないようにします。 - 横方向のカルーセルには、
scroll-snap-type: x mandatoryと、ガター用のscroll-paddingを指定し、続きがあることを示す視覚的な手がかりを設けます。 - スティッキーヘッダーの高さは56px未満にします。それより高いと画面を圧迫します。
- タッチ操作にはスムーススクロールのライブラリーを使いません。基準とするのはネイティブの慣性スクロールであり、これと競合させないでください。
フィードバック
- タッチ操作での押下状態は、デスクトップよりも変化を大きくします。目がホバー状態で準備できないため、スケールは0.98ではなく0.96にするか、塗りつぶしを見えるようにします。
- Webではハプティクスを利用できません(
navigator.vibrateはAndroid専用で、機能も限定的です)。同じフレームで視覚的なフィードバックを与えて補います。 - トーストは画面下部中央に、ボトムバーとキーボードの上に表示します。
- ローディングインジケーターは、親指が覆っている画面上部ではなく、親指の近くに表示します。
375pxでのレイアウト
- 左右の余白は16pxにします。0にも、24px以上にもしてはいけません。
- 1列にします。カードを幅いっぱいにして角丸を12pxにするか、区切り線付きの行を端から端まで表示します。両方を併用してはいけません。
- 本文は15〜16px、補足テキストは13pxにします。腕を伸ばして持ったスマートフォンでは、本文13pxは小さすぎます。
- テーブルはカードにするか、先頭列を固定して横スクロールできるようにします。幅を無理に縮めたテーブルにしてはいけません。
- 省略を減らし、折り返しを増やします。スマートフォンでは名前を2行に折り返します。
- ダイナミックタイプを最大に設定した状態と、ズーム200%でテストします。
実機でのパフォーマンス
- 最新のiPhoneではなく、ミドルレンジのAndroidでテストします。代替手段として、DevToolsでCPUを4倍にスロットルします。
- スクロールがカクつく場合、最初に削るのは
backdrop-filterと大きなボックスシャドウです。 - アニメーションには
transformとopacityだけを使います。スマートフォンではheight、width、topをアニメーションさせてはいけません。 - 画像はデバイスに適したサイズにし(
srcset)、画面下部の画像は遅延読み込みし、非同期でデコードします。 - 4Gで最初の操作を2秒以内に可能にします。最初の画面に不要なものはすべて後回しにします。
PWAの細部
manifest.webmanifestに名前、アイコン(192、512、マスカブル)、display: standalone、theme_color、background_colorを設定します。apple-mobile-web-app-capableとステータスバーのスタイルを設定します。- アプリのフレームカラーに合わせたスプラッシュ画面にします。白にはしません。
チェックリスト
- タップ領域は44px、間隔は8pxにし、主要なアクションは画面下部に配置する
-
dvh、viewport-fit=coverを使い、固定配置する端にセーフエリアを設定する - タップ時のハイライトを削除し、
:active状態で置き換える - ホバーは条件付きで有効にし、タップ後にホバー状態を固定しない
- 入力欄は16pxにし、
inputmode、autocomplete、enterkeyhintを設定する - フォーカス中のフィールドがキーボードの上に見える
- シートをドラッグ・スナップでき、ページ本体のスクロールをロックする
- スクロール領域は1つにし、オーバースクロールを領域内に収める
- トーストをボトムバーの上に表示する
- スロットリングを設定したミドルレンジのAndroidでテストする
カタログから
- loadmo.re — 調べる価値のある、モバイルファーストのサイトや実験です。
- Vaul — ジェスチャーをうまく解決したドロワーです。
- Apple Human Interface Guidelines — Appleが指定するタップ領域、シート、セーフエリアについて説明しています。
- Mobbin — パターン別に検索できる、実際のモバイル操作フローです。
- web.dev — INP、ビューポート単位、タッチ操作のガイダンスを掲載しています。
- その他のリソースはstealth.pm/lookをご覧ください。
コメント
Markdown 対応