CSS スクロールスナップモジュール レベル 1

W3C 候補勧告スナップショット,

このバージョン:
https://www.w3.org/TR/2021/CR-css-scroll-snap-1-20210311/
最新の公開バージョン:
https://www.w3.org/TR/css-scroll-snap-1/
編集者草案:
https://drafts.csswg.org/css-scroll-snap-1/
以前のバージョン:
実装レポート:
https://wpt.fyi/results/css/css-scroll-snap
テストスイート:
http://test.csswg.org/suites/css-scroll-snap-1_dev/nightly-unstable/
課題の追跡:
CSSWG 課題リポジトリ
編集者:
Matt Rakow (Microsoft)
Jacob Rossi (Microsoft)
Tab Atkins-Bittner (Google)
Elika J. Etemad / fantasai (招待専門家)
この仕様への編集提案:
GitHub Editor

概要

このモジュールには「スナップ位置」を使ってパンやスクロール動作を制御するための機能が含まれています。

CSSは、構造化文書(HTML や XML など)のレンダリングを画面や紙などに記述するための言語です。

この文書のステータス

このセクションは、本書が公開された時点でのステータスについて説明します。他の文書が本書に取って代わる場合があります。現在の W3C 公開文書の一覧と、この技術レポートの最新改訂版は、https://www.w3.org/TR/ の W3C 技術レポートインデックスでご覧いただけます。

本書はCSS ワーキンググループによって候補勧告スナップショットとして公開されました。候補勧告として公開されたことは、W3C 会員による承認を意味しません。候補勧告スナップショットは幅広いレビューを受けており、実装経験の収集を目的としています。本書は W3C 勧告となる予定ですが、さらなるフィードバックを集めるため、 までは候補勧告として公開され続けます。

フィードバックはGitHub で課題を提出(推奨)、タイトルに仕様コード「css-scroll-snap」を含めて、例:「[css-scroll-snap] …コメント概要…」として送信してください。すべての課題やコメントはアーカイブされています。代わりに、(アーカイブ済み) 公開メーリングリスト www-style@w3.org へ送ることもできます。

本書は2020年9月15日版 W3C プロセス文書に準拠しています。

本書はW3C 特許ポリシーの下で活動するグループによって作成されました。W3C は、グループの成果物に関連して行われたすべての特許開示の公開リストを管理しています。そのページには特許開示方法も記載されています。個人が、必須クレームを含むと信じる特許を実際に知っている場合は、W3C 特許ポリシー第6節に従って情報を開示しなければなりません。

CR期間中にテストスイートおよび実装レポートが作成されます。

以下の機能はリスクがあり、CR期間中に削除される可能性があります:

「リスクあり」は W3C プロセスの専門用語であり、必ずしも機能が削除または遅延される危険性を意味するものではありません。WG は、機能の相互運用可能な実装がタイムリーに困難になる可能性があると考えており、リスクありとすることで、提案勧告段階に移行する際に必要であれば機能を削除できるようにしています(新しい候補勧告を公開せずに機能のみ削除する必要がなくなります)。

1. はじめに

このセクションは規定ではありません。

スクロール可能なコンテンツの一般的なUXパラダイムでは、コンテンツをページごとに切り替えたり、論理的な区分に分割したりすることがよくあります。 特にタッチ操作の場合、階層構造をタップでナビゲートするよりも、平坦に配置されたコンテンツを素早くパン操作する方が、ユーザーにとって速く簡単です。 例えば、ユーザーがアルバム内の多くの写真を閲覧する際、アルバム内の各写真を個別にタップするよりも、写真スライドショーでパン操作する方が簡単です。

しかし、タッチパンやマウスホイールスクロールのようなスクロール入力は精度が低いため、 ウェブ開発者がスクロール体験を十分に制御することは困難です。特にコンテンツをページごとに切り替える効果を作る場合です。 例えば、パン操作によりアイテムが画面に中途半端に表示されてしまうスクロール位置に着地しやすいです。

このため、本モジュールはスクロールスナップ位置を導入し、 スクロール操作完了後にスクロールコンテナのスクロールポートが終了できるスクロール位置を強制します。

また、スナップがオフの場合でもページングやスクロール位置をより細かく制御できるよう、 本モジュールはすべてのスクロールコンテナで使用できるscroll-paddingプロパティを定義し、 ページングやスクロールインビュー操作のためにスクロールコンテナ最適表示領域を調整します。 同様にscroll-marginプロパティは任意のボックスで使用でき、 スクロールインビュー操作のためにその可視領域を調整できます。

1.1. モジュール間の相互作用

このモジュールは[CSS2]の11.1節で定義されているスクロールユーザーインターフェイス機能を拡張します。

このモジュールのいずれのプロパティも、::first-lineおよび::first-letter疑似要素には適用されません。

1.2. 値の定義

この仕様はCSSプロパティ定義の慣例に従い、[CSS2]および値定義構文[CSS-VALUES-3])を使用します。 この仕様で定義されていない値型はCSS Values & Units [CSS-VALUES-3]で定義されています。 他のCSSモジュールと組み合わせることで、これらの値型の定義が拡張される場合があります。

各プロパティの定義に記載されている値に加え、 この仕様で定義されるすべてのプロパティは CSS全体のキーワードもプロパティ値として受け付けます。 読みやすさのため、ここでは明示的に繰り返していません。

2. 動機付け例

この例では、スクロールコンテナ内に並べられた一連の画像を使ってフォトギャラリーを構築します。 この例では スクロールコンテナが中の写真よりも大きく(複数の画像が同時に見える)、画像サイズは様々です。 必須(mandatory)な要素ベースのスナップ 位置を使うことで、スクロール操作が完了すると必ず画像がスクロールコンテナのスクロールポートの中央に配置されます。
img {
    /* 各写真の中央が、
       スクロールコンテナの中央(X軸)に
       スナップ時に揃うよう指定 */
    scroll-snap-align: none center;
}
.photoGallery {
    width: 500px;
    overflow-x: auto;
    overflow-y: hidden;
    white-space: nowrap;
    /* スクロール操作が完了すると
       必ずスナップ位置で止まるよう要求 */
    scroll-snap-type: x mandatory;
}
<div class="photoGallery">
    <img src="img1.jpg">
    <img src="img2.jpg">
    <img src="img3.jpg">
    <img src="img4.jpg">
    <img src="img5.jpg">
</div>
この例でのスクロールコンテナの内容のレイアウト。 スナップポートは赤い枠で、スナップエリアは黄色い枠で表されています。 scroll-snap-alignがインライン(水平)軸で「center」なので、スナップポートのX中心(赤点線)とスナップエリアのX中心(黄点線)が揃う各スクロール位置にスナップ位置が作られます。
この例では、各ページをスクロールコンテナの端付近(完全に端ではない)に揃えるページ付きドキュメントを構築します。 これにより、前のページが上部から少し「覗き見える」ことで、ユーザーがまだドキュメントの最上部ではないことを認識できるようになります。 必須スナップ位置ではなく近接(proximity)スナップ位置を使うことで、ユーザーはページの途中でスクロールを止めることができ(必ず1ページごとにスナップされるのではなく)ます。 ただし、スクロール操作がスナップ位置付近で終了する場合は、指定通りにページが揃うようにスクロールが調整されます。
.page {
    /* 各ページの上端を
       スナップ位置として使用するよう定義 */
    scroll-snap-align: start none;
}
.docScroller {
    width: 500px;
    overflow-x: hidden;
    overflow-y: auto;
    /* 各要素のスナップ領域が、上端から100pxオフセットで揃うよう指定 */
    scroll-padding: 100px 0 0;
    /* スクロール操作が完了時、スナップ位置付近ならその位置で止まるよう促す */
    scroll-snap-type: y proximity;
}
<div class="docScroller">
    <div class="page">ページ 1</div>
    <div class="page">ページ 2</div>
    <div class="page">ページ 3</div>
    <div class="page">ページ 4</div>
</div>
この例でのスクロールコンテナの内容のレイアウト。 スナップポートは赤い枠(scroll-paddingにより上端から100px内側)で、 スナップエリアは黄色い枠で表されています。 scroll-snap-alignがY軸で「start」なので、スナップポートのY開始位置(赤点線)とスナップエリアのY開始位置(黄点線)が揃う各スクロール位置にスナップ位置が作られます。

3. スクロールスナップモデル

このモジュールはスクロールスナップ位置の制御を定義します。 スクロールスナップ位置とは、スクロールコンテナ内で特定の揃え方になるスクロール位置のことです。 該当するscroll-snap-typeプロパティをスクロールコンテナに設定することで、 著者はスクロール操作(scrollTo() メソッド等のプログラム的スクロール含む)後にスクロールポートがどのスナップ位置に着地するかのバイアスを要求できます。

スナップ位置は、 要素のscroll-snap-alignによる特定の整列として指定されます。 スクロールスナップ領域scroll-marginで修正されたボーダーボックス)の スクロールコンテナスナップポートscroll-paddingで縮小されたスクロールポート)内での整列です。 これは概念的には、整列対象整列コンテナ内で整列することと同等です。 指定された整列を満たすスクロール位置がスナップ位置です。

スクロールポートのスクロール位置をスナップ位置に揃える操作はスナップと呼ばれ、 スクロールコンテナがそのスクロールポートのスクロール位置でスナップ位置になり、 スクロール操作がアクティブでない場合、そのスクロールコンテナはスナップ済みとされます。 CSS Scroll Snap Moduleはスナップ位置を維持するための具体的なアニメーションや物理挙動は定義せず、 それはユーザーエージェントに委ねています。

スナップ位置は、 要素の最近傍のスクロールコンテナコンテイニングブロックチェーン上)にのみ影響します。

4. スクロールスナップ領域のキャプチャ: スクロールコンテナのプロパティ

4.1. スクロールスナップ規則: scroll-snap-type プロパティ

名前: scroll-snap-type
値: none | [ x | y | block | inline | both ] [ mandatory | proximity ]?
初期値: none
適用対象: すべての要素
継承: no
パーセンテージ: n/a
算出値: 指定キーワード
正規化順序: 文法による
アニメーションタイプ: 離散的

scroll-snap-typeプロパティは、 スクロールコンテナスクロールスナップコンテナかどうか、 どれほど厳格にスナップするか、 そしてどの軸が対象かを指定します。 厳格度が指定されていない場合、proximity がデフォルトとなります。

この例では、見出しへのスナップが ブロック軸(横書きではy軸、縦書きではx軸)で有効になっています:
html {
  scroll-snap-type: block;   /* ドキュメントのメインスクローラーに適用 */
}
h1, h2, h3, h4, h5, h6 {
  scroll-snap-align: start;  /* ビューポートの開始位置(上端)にスナップ */
}

UAは、ルート要素に設定されたscroll-snap-type値をドキュメントのビューポートに適用しなければなりません。 overflowとは異なり、scroll-snap-type値は HTML body から伝播されませんので注意してください。

4.1.1. スクロールスナップ軸: x, y, block, inline, both の値

軸値は、スナップ位置がどの軸に影響するか、 またスナップ位置が各軸ごとに独立して評価されるか、 2次元ポイントとして評価されるかを指定します。 各値の定義は以下の通りです:

x
スクロールコンテナは水平方向の軸のみでスナップしてスナップ位置を使用します。
y
スクロールコンテナは垂直方向の軸のみでスナップしてスナップ位置を使用します。
block
スクロールコンテナはブロック軸のみでスナップしてスナップ位置を使用します。
inline
スクロールコンテナはインライン軸のみでスナップしてスナップ位置を使用します。
both
スクロールコンテナは両方の軸で独立してスナップし、各軸で異なる要素にスナップする可能性があります。

4.1.2. スクロールスナップ厳格度: none, proximity, mandatory の値

厳格度値none, proximity, mandatory) は、スナップ位置スクロールコンテナでどれほど厳格に適用されるか(スクロール位置の調整を強制するか)を指定します。 各値の定義は以下の通りです:

none
スクロールコンテナに指定した場合、 スクロールコンテナスナップしません。
mandatory
スクロールコンテナに指定した場合、 スクロールコンテナはスクロール操作がアクティブでない時に必ずスナップ位置に揃えられます。 有効なスナップ位置が存在する場合、 スクロール終了時に必ずその位置にスナップします (存在しない場合はスナップしません)。
proximity
スクロールコンテナに指定した場合、 スクロールコンテナはスクロール終了時、 UAの判断によりパラメータに応じてスナップ位置に揃える場合があります。

著者は、画面サイズや(該当する場合)コンテンツサイズが様々であることを考慮してmandatoryスナップ位置を使用してください。 特に、スナップされた要素がスクロールポートより大きい場合はUAが対処しますが、 mandatoryスナップを隣接していない兄弟要素に指定すると、 その間のコンテンツがスクリーンより長い場合にアクセスできなくなることがあります。

ボックスは、スナップ位置をキャプチャするためには、 スクロールコンテナであるまたは none以外のscroll-snap-type値を持つ必要があります。 ボックスのスナップ位置キャプチャとして最も近い祖先が、 コンテイニングブロックチェーン上でnon-noneな値を持つ スクロールコンテナであれば、 それがそのボックスのスクロールスナップコンテナです。 それ以外の場合、そのボックスにはスクロールスナップコンテナはなく、 そのスナップ位置スナップを発生させません。

4.1.3. レイアウト変更後の再スナップ

ドキュメントの内容やレイアウトが変更され (例: 内容の追加、移動、削除、サイズ変更など)、 スナップポートの内容が変わった場合、 UAは結果として生じるスクロール位置を再評価し、 必要に応じて再スナップしなければなりません。 スクロールコンテナが内容変更前にスナップ済みで、そのスナップ位置がまだ存在する場合 (関連する要素が削除されていない等)、 内容変更後もスクロールコンテナは同じスナップ位置に再スナップされなければなりません。 複数のボックスが以前スナップ済みで それらのスナップ位置が一致しなくなった場合、 どれかがフォーカス・ターゲットされていれば スクロールコンテナはそのボックスに再スナップし、 それ以外はUA定義となります。 (UAは例えば、どの要素がスナップされているかを追跡し、レイアウトシフトで他要素のスナップ位置が揃わなくなった時に対応できます。)

新しい/異なるボックスへの再スナップ操作によるスクロールは、 他のスクロールインビュー操作と同様に動作・アニメーションしなければならず、 scroll-behaviorなどのコントロールも尊重されます。 以前と同じボックスへの再スナップ時のスクロール動作はUA定義です。 UAは例えば、 セクション先頭にスナップ済みの場合、 ドキュメント前方に動的に内容が追加されても スクロールのアニメーションを行わず、 スクロールしていないように見せることもできます。

次の例では、 ログコンソールが初期表示時および各メッセージが下部に追加されるたびに、 ユーザーがその端からスクロールを離していない限り、 内容の一番下にスナップされたままになります:
.log {
  scroll-snap-type: proximity;
  align-content: end;
}
.log::after {
  display: block;
  content: "";
  scroll-snap-align: end;
}

これらのルールはスクロールスナップ領域を一つ作成します。これは::after疑似要素で表され、 スクロールスナップコンテナの一番下に配置されます。 ユーザーが下部「付近」にスクロールすると、コンテナはそこにスナップします。 コンテナにさらに内容が動的に追加されても、スナップしたままになります (スクロールコンテナは変更後も同じスクロールスナップ領域が存在すれば再スナップしなければならないため)。 ただし、ユーザーがログ内の別の場所にスクロールしている場合は何も起こりません。

4.2. スクロールスナップポート: scroll-padding プロパティ

名前: scroll-padding
値: [ auto | <length-percentage> ]{1,4}
初期値: auto
適用対象: スクロールコンテナ
継承: no
パーセンテージ: スクロールコンテナのスクロールポートの対応する寸法に対して相対
算出値: 各辺ごとに、キーワード auto または 算出された <length-percentage>
アニメーションタイプ: 算出値型による
正規順序: 文法による

このプロパティは、すべてのスクロールコンテナスクロールスナップコンテナだけでなく) に対して、スクロールポートの最適表示領域を定義するオフセットを指定します。 この領域はユーザーが閲覧するために要素を配置するターゲット領域として使われます。 著者は、他のコンテンツ(固定配置のツールバーやサイドバーなど)で隠されているスクロールポートの領域を除外したり、 ターゲット要素とスクロールポートの端との間に余裕を持たせたりすることができます。

scroll-paddingプロパティはショートハンドプロパティであり、 scroll-padding-* ロングハンドを一括で設定します。 各側のロングハンドへの値の割り当ては、padding プロパティと同様です。 各値の意味は以下の通りです:

<length-percentage>

対応するスクロールポートの辺から内側へのオフセットを定義します。 ルートビューポートに適用した場合、オフセットはレイアウトビューポート(ビジュアルビューポートではなく)を基準に計算・適用され、 insetプロパティと同様に 固定配置ボックスに作用します。 最適表示領域はビジュアルビューポートと交差する残りの領域です。

auto

対応するスクロールポートの辺のオフセットはUAによって決定されます。 通常は使用値が0pxとなりますが、UAは非ゼロ値が適切と判断する場合はヒューリスティックを使うことがあります。

例として、UAがposition:fixedの要素を不透明でスクロール不可な「ヘッダー」として検出し、 その下の内容を隠している場合、上端のオフセットをその要素の高さに解決し、 PgDnキーなどの「ページダウン」操作で「表示されているページ」分だけ自動的にスクロールできるようにすることができます。

これらのオフセットは、スクロールポートの 「表示可能」領域(スクロール操作において)を減少させます。 レイアウトやスクロールの原点・初期位置、実際に要素が可視かどうかには影響しませんが、 要素やキャレットがスクロールインビューであるかどうか(ターゲットやフォーカス操作など)に影響し、 ページング操作(PgUpPgDnキー、スクロールバーによる同等操作など)のスクロール量も減少させます。 最適表示領域内でユーザーがスクロールポートのスクロールポート上で連続したコンテンツを閲覧できるようにします。

スクロールスナップコンテナの場合、 この領域はスクロールスナップポートも定義します。 これは整列コンテナとして、スクロールスナップ領域スナップ位置計算時に使われます。

この例では、scroll-paddingを使用して 固定配置のツールバーで隠されていないスクロールポート部分の中央に スライドショー画像を配置しています。
html {
    overflow-x: auto;
    overflow-y: hidden;
    scroll-snap-type: x mandatory;
    scroll-padding: 0 500px 0 0;
}
.toolbar {
    position: fixed;
    height: 100%;
    width: 500px;
    right: 0;
}
img {
    scroll-snap-align: none center;
}

UAは、ルート要素に設定されたscroll-padding値を ドキュメントのビューポートに適用しなければなりません。 (ただし、overflowとは異なり、scroll-padding値は HTML body から伝播されません。)

5. スクロールスナップ領域の整列: 要素のプロパティ

5.1. スクロールスナップ領域: scroll-margin プロパティ

名前: scroll-margin
値: <length>{1,4}
初期値: 0
適用対象: すべての要素
継承: no
パーセンテージ: n/a
算出値: 各辺ごとに絶対長さ
正規順序: 文法による
アニメーションタイプ: 算出値型による

このプロパティはショートハンドプロパティであり、 scroll-margin-* ロングハンドを一括で設定します。 各側のロングハンドへの値の割り当ては、marginプロパティと同様です。

値は、このボックスがスナップポートにスナップされる際に使われる スクロールスナップ領域を定義する外向きのオフセットです。 スクロールスナップ領域は 変形されたボーダーボックスを取得し、 その矩形の境界ボックス(スクロールコンテナの座標空間で軸揃え)を見つけ、 指定された外向きオフセットを加えて決定します。

注: この仕組みによりスクロールスナップ領域は常に矩形となり、 スクロールコンテナの座標空間に軸揃えされます。

ページがフラグメントにナビゲートされ、ターゲット要素が定義されている場合 (:targetで一致する要素や、 scrollIntoView() のターゲットなど)、 UAは要素のスクロールスナップ領域を利用して スクロール可能なオーバーフロー領域のどの部分を表示するか決定すべきです。(スナップがオフやこの要素に適用されていない場合でも)

5.2. スクロールスナップの整列: scroll-snap-align プロパティ

名前: scroll-snap-align
値: [ none | start | end | center ]{1,2}
初期値: none
適用対象: すべての要素
継承: no
パーセンテージ: n/a
算出値: 2つのキーワード
正規順序: 文法による
アニメーションタイプ: 離散的

scroll-snap-alignプロパティは、 ボックスのスナップ位置スナップ領域整列対象として)を スナップコンテナスナップポート整列コンテナとして)内での整列として指定します。 2つの値は、ブロック軸インライン軸でのスナップ整列を指定し、 スナップコンテナ書字モードによって決定されます。 1つの値だけ指定した場合、2つ目の値は同じ値がデフォルトになります。

値の定義は以下の通りです:

none
このボックスは指定した軸にスナップ位置を定義しません。
start
このボックスのスクロールスナップ領域スクロールコンテナスナップポートの開始位置に整列することが、指定した軸でのスナップ位置となります。
end
このボックスのスクロールスナップ領域スクロールコンテナスナップポートの終了位置に整列することが、指定した軸でのスナップ位置となります。
center
このボックスのスクロールスナップ領域スクロールコンテナスナップポートの中央に整列することが、指定した軸でのスナップ位置となります。

開始・終了の整列は、スナップコンテナスクロールスナップ領域スナップポートより大きい場合を除き、 書字モードを基準に解決されます。 (これにより、一般的にはコンテナ内のアイテムのスナップ整列が一貫しますが、 startが常にアイテムを先頭に揃え、 内容の先頭から読めるようにします。)

5.2.1. 有効なスナップ位置の範囲を可視ボックスに限定する

スクロールスナップの目的はスクロールポート内でコンテンツを最適に表示するために整列させることなので、 寄与するスナップ領域が完全にスナップポートの外側になる場合、 たとえスナップ位置が必要な整列条件を満たしていたとしても、 そのスクロール位置は有効なスナップ位置とはみなされません。

例えば、スナップ領域の上端がスナップポートの上端と一致する場合は、 その領域が画面上に少なくとも一部表示されていれば、 これはブロック軸の開始位置でのスナップに対して有効なスナップ位置とみなされます。 しかし、スナップ領域全体がスナップポートの外側にある場合は、 スクロールコンテナスナップ済みとはみなされません。 整列条件が満たされていても、閲覧者にとって意味がないからです。
╔════viewport════╗┈┈┈┈┈┈┈┈┌──────────────┐
║  ┌─────┐ ┌──┐  ║        │ top-snapping │
║  ├──┐  │ └──┘  ║        │   element    │
║  └──┴──┘       ║        │              │
╚════════════════╝        │              │
                          └──────────────┘
画面外の要素の整列はスナップとはみなされません。
なぜ要素が可視の時だけスナップを限定するのか? WebKitの実装者の指摘のように、 スナップエッジをキャンバス全体に無限に拡張すると 格子状レイアウトでのみスナップが可能となり、 画面外要素が画面内要素と揃わない場合、ユーザーには奇妙な挙動になります。 (もしこの要件が実装者にとって負担なら、デフォルトは格子状の挙動にして後からスマートな挙動への切り替えスイッチを導入する案もあります。)

注: scroll-snap-type: bothでは スナップ位置を各軸で独立して評価しますが、 一方の軸でのスナップ位置の選択がもう一方の軸のスナップ位置に影響することがあります。 例えば、片方の軸でのスナップがもう一方の軸で揃えるはずのスナップ領域を画面外に押し出すと、 そのスナップ位置は無効となり選択できなくなります。

5.2.2. スクロールポートをはみ出すボックスのスナップ

スナップ領域が 特定の軸でスナップポートより大きい場合、 その軸でスナップ領域スナップポートを覆い、 またその軸で前後のスナップ位置間の距離が スナップポートのサイズより大きい任意のスクロール位置は、 その軸で有効なスナップ位置となります。 UAは特定のスクロール操作(明示的なページ送りなど)で指定された整列をより精密なターゲットとして利用できます。

例えば、§ 2 動機付け例の最初の例では、 スナップ領域として写真が使われています。 著者はアイテムごとの必須スナップを設定したいですが、 アイテムがビューポートより大きい場合は、 その上に来た時に全体をスクロールできるようにしたいのです。

スナップ領域スナップポートより大きい間は、 領域がビューポート全体を埋め尽くしているため、コンテナは任意にスクロールでき、 整列位置に戻ろうとしません。 しかし、コンテナがスクロールされて領域がその軸で完全にビューポートを占めなくなると、 領域は外向きのスクロールに抵抗し、 十分なスクロールで別のスナップ位置にスナップするまで抵抗します。

もう一つの例として、 入れ子になったsection 要素で必須の上端スナップを設定すると、 (大きなトップレベルセクションから)大きなスナップ領域が生じ、 その中に(サブセクションから)小さなスナップ領域が含まれる可能性があります。 サブセクションが十分に小さい場合は通常通りスナップしますが、 長い場合はその中や、サブセクションのない大きなセグメント内で自由にスクロールできます。
┌─ top-level section ─┐ ━┓
│                     │ 1┃
│                     │  ┃
│                     │ ━┩
│                     │  ┆
│                     │  ┆
│┌─── sub-section ───┐│  ╯ ━┓
│└───────────────────┘│    2┃
│┌─── sub-section ───┐│ ━┓  ┃
││                   ││ 3┃ ━┛
│└───────────────────┘│  ┃
│┌─── sub-section ───┐│ ━┛ ━┓
│└───────────────────┘│    4┃
│┌─── sub-section ───┐│ ━┓  ┃
││                   ││ 5┃ ━┛
││                   ││  ┃
││                   ││ ━┩
││                   ││  ┆
││                   ││  ┆
││                   ││  ┆
│└───────────────────┘│  ┆
└─────────────────────┘  ╯
上図の5つの番号付きビューポートは、 トップレベルセクションおよび4つのサブセクションに関連する5つのスナップ位置を表しています。 最初と最後のスナップ位置はビューポートより高い範囲の一部なので、 閲覧者はそれぞれの範囲の上端から下端まで自由にスクロールできます。

注: 著者がセクション自体ではなく各セクションの見出しに必須スナップ位置を設定していた場合、 最初と5番目のセクションの内容はユーザーに部分的にアクセスできなくなります。 これは見出しのスナップ領域がセクション全体を覆わないためです。 そのため、間隔が広くなる可能性のある要素に必須スナップ位置を使うのはおすすめできません。

5.2.3. 到達不能なスナップ位置

スナップ位置が指定通りには到達不能であり、 その位置に整列するにはスクロールコンテナのビューポートを スクロール可能オーバーフロー領域の端を超えてスクロールする必要がある場合は、 このスナップ領域使用されるスナップ位置は、 各該当軸で希望するスナップ位置に向けて可能な限りスクロールした結果の位置になります。

5.3. スクロールスナップ制限: scroll-snap-stop プロパティ

名前: scroll-snap-stop
値: normal | always
初期値: normal
適用対象: すべての要素
継承: no
パーセンテージ: n/a
算出値: 指定キーワード
正規順序: 文法による
アニメーションタイプ: 離散的

意図した方向でスクロールした場合、 スクロールコンテナは到達可能な複数のスナップ位置を (同じ方向でより短い距離のスクロール操作ならスナップ可能なものも含めて) スクロール操作の自然な終点に到達し最終的なスクロール位置を選択するまで通過することがあります。 scroll-snap-stopプロパティは、 こうした可能なスナップ位置でスクロール操作を「捕捉」し、 スクロールコンテナが本来の終点に到達する前に停止するよう強制することができます。

値の定義は以下の通りです:

normal
スクロールコンテナは、スクロール操作の実行中にこの要素で定義されたスナップ位置を通過しても構いません。
always
スクロールコンテナは、スクロール操作の実行中にこの要素で定義されたスナップ位置を通過してはなりません。 代わりにこの要素の最初のスナップ位置でスナップしなければなりません。

このプロパティは、意図した終端位置のみを持つスクロール操作には効果がありません。 そうした操作では、概念的にいかなるスナップ位置も「通過」しないためです。

6. スナップメカニクス

どのスナップ位置にスナップするかを選択する厳密なモデルアルゴリズムは、意図的にほとんど未定義のままです。 これは、ユーザーエージェントがユーザーの意図やインタラクションの高度なモデルを考慮し、 時間の経過とともに応答方法を調整できるようにするためです。 その結果、ユーザーに最適な体験を提供できます。

このセクションでは、スクロールスナップのメカニクスについて議論する際に役立ついくつかの有用な概念を定義し、 効果的なスクロールスナップ戦略がどのようなものかについてガイドラインを示します。 ユーザーエージェントはこのガイダンスを参考にしつつ、 独自の最適な判断を加えてスナップ挙動を定義することが推奨されます。 また、著者がスクロールスナップを考慮してインターフェース設計する際に 最低限頼れる合理的な挙動を保証するための、少数の動作要件も規定します。

6.1. スクロール方法の種類

ページがスクロールされるとき、 その操作は意図した終了位置や意図した方向のいずれかまたは両方を伴って行われます。 これら2つの組み合わせごとに異なるスクロールカテゴリが定義されており、 それぞれが少し異なる扱いを受けます:

意図した終了位置
「意図した終了位置」のみを伴うスクロールの一般的な例:
  • 慣性なしでパン操作を離す

  • スクロールバーの「つまみ」を明示的に操作する

  • scrollTo()などのAPIによるプログラム的スクロール

  • ドキュメント内のフォーカス可能要素をTabで移動する

  • ページ内アンカーへのナビゲーション

  • Home/Endキーなどの「ホーム」操作

意図した方向および終了位置
「意図した方向および終了位置」の両方を伴うスクロールの一般的な例:
  • 慣性付きフリック(勢いを伴う)

  • scrollBy()などのAPIによるプログラム的スクロール

  • PgUp/PgDnキー(またはスクロールバーでの同等操作)によるページ送り

スナップポイントなどの機能による介入前のスクロールの意図した終点は、自然終点と呼ばれます。

意図した方向
「意図した方向」のみを伴うスクロールの一般的な例:
  • キーボードの矢印キー(またはスクロールバーでの同等操作)

  • 固定(慣性なし)として解釈されるスワイプ操作

さらに、ページレイアウトは通常垂直または水平に要素を揃えるため、 UAはスクロール方向が十分に縦または横であれば軸ロックを行うことがあります。 軸ロックされたスクロールはその軸だけでスクロールします。 これにより、精度が低い入力機構による非主要軸方向へのドリフトを防ぎます。

注: この仕様はUAがサポートするスクロール方法だけに適用されます。 UAに特定の入力やスクロール方法のサポートを義務付けるものではありません。

6.2. スナップ位置の選択

スクロールコンテナには、多くのスナップ領域スクロール可能オーバーフロー領域内に散在しています。 スナップ位置の選択に単純なアルゴリズムを使うと、ユーザーにとって直感的でない挙動になることがあるので、 選択アルゴリズム設計時には注意が必要です。 以下は選択プロセスの助けとなるポイントです:

付録A:ロングハンド

物理・論理ロングハンド(およびそれらのショートハンド)は [CSS-LOGICAL-1]で定義される通りに相互作用します。

scroll-paddingの物理ロングハンド

名前: scroll-padding-top, scroll-padding-right, scroll-padding-bottom, scroll-padding-left
値: auto | <length-percentage>
初期値: auto
適用対象: スクロールコンテナ
継承: no
パーセンテージ: スクロールコンテナのスクロールポートに対して相対
算出値: キーワードautoまたは算出された<length-percentage>
正規順序: 文法による
アニメーションタイプ: 算出値型による

scroll-paddingのこれらのロングハンドは、scroll-paddingの スナップポートの上・右・下・左の各辺を指定します。 負の値は無効です。

scroll-paddingのフロー相対ロングハンド

名前: scroll-padding-inline-start, scroll-padding-block-start, scroll-padding-inline-end, scroll-padding-block-end
値: auto | <length-percentage>
初期値: auto
適用対象: スクロールコンテナ
継承: no
パーセンテージ: スクロールコンテナのスクロールポートに対して相対
算出値: キーワードautoまたは算出された<length-percentage>
正規順序: 文法による
アニメーションタイプ: 算出値型による

scroll-paddingのこれらのロングハンドは、 ブロック始端・インライン始端・ブロック終端・インライン終端の各辺を指定します。 負の値は無効です。

名前: scroll-padding-block, scroll-padding-inline
値: [ auto | <length-percentage> ]{1,2}
初期値: auto
適用対象: スクロールコンテナ
継承: no
パーセンテージ: スクロールコンテナのスクロールポートに対して相対
算出値: 個々のプロパティを参照
アニメーションタイプ: 算出値による
正規順序: 文法による

ショートハンドscroll-padding-block-start + scroll-padding-block-endおよびscroll-padding-inline-start + scroll-padding-inline-end)は scroll-paddingのロングハンドであり、 それぞれブロック軸・インライン軸のスナップポートの端を指定します。

2つの値が指定された場合、1つ目が始端の値、2つ目が終端の値になります。

scroll-marginの物理ロングハンド

名前: scroll-margin-top, scroll-margin-right, scroll-margin-bottom, scroll-margin-left
値: <length>
初期値: 0
適用対象: すべての要素
継承: no
パーセンテージ: n/a
算出値: 絶対長さ
正規順序: 文法による
アニメーションタイプ: 算出値型による

scroll-marginのこれらのロングハンドは、 scroll-marginの スクロールスナップ領域の上・右・下・左の各辺を指定します。

scroll-marginのフロー相対ロングハンド

名前: scroll-margin-block-start, scroll-margin-inline-start, scroll-margin-block-end, scroll-margin-inline-end
値: <length>
初期値: 0
適用対象: すべての要素
継承: no
パーセンテージ: n/a
算出値: 絶対長さ
正規順序: 文法による
アニメーションタイプ: 算出値型による

scroll-marginのこれらのロングハンドは、 ブロック始端・インライン始端・ブロック終端・インライン終端の各辺を指定します。

名前: scroll-margin-block, scroll-margin-inline
値: <length>{1,2}
初期値: 0
適用対象: すべての要素
継承: no
パーセンテージ: n/a
算出値: 個々のプロパティを参照
正規順序: 文法による
アニメーションタイプ: 算出値型による

ショートハンドscroll-margin-block-start + scroll-margin-block-endおよびscroll-margin-inline-start + scroll-margin-inline-end)は scroll-marginのロングハンドであり、 それぞれブロック軸・インライン軸のスクロールスナップ領域の端を指定します。

2つの値が指定された場合、1つ目が始端の値、2つ目が終端の値になります。

7. プライバシーとセキュリティの考慮事項

この仕様はDOMに既に直接公開されている以外の情報を一切公開しません。 スクロールを少しだけ機能的にするだけです。 新たなプライバシーやセキュリティの考慮事項はありません。

8. 謝辞

David Baron, Simon Fraser, Håkon Wium Lie, Theresa O’Connor, François Remy, Majid Valpour, そして特に Robert O’Callahan に感謝します。 彼らの提案と助言が本書に取り込まれています。

9. 変更点

9.1. 2019年3月19日CR以降の変更点

2019年3月19日候補勧告以降の変更点:

コメント処理状況が公開されています。

9.2. 2019年1月31日CR以降の変更点

2019年1月31日候補勧告以降の変更点:

9.3. 2018年8月14日CR以降の変更点

2018年8月14日候補勧告以降の変更点:

コメント処理状況が公開されています。

9.4. 2017年12月14日CR以降の変更点

2017年12月14日候補勧告以降の変更点:

コメント処理状況が公開されています。

9.5. 2017年8月24日CR以降の変更点

2017年8月24日候補勧告以降の変更点:

コメント処理状況が公開されています。

9.6. 2016年10月20日CR以降の変更点

2016年10月20日候補勧告以降の変更点:

コメント処理状況が公開されています。

適合性

文書の慣例

適合要件は、記述的な断定とRFC 2119用語の組み合わせで表されます。規範部分で使われるキーワード「MUST」「MUST NOT」「REQUIRED」「SHALL」「SHALL NOT」「SHOULD」「SHOULD NOT」「RECOMMENDED」「MAY」「OPTIONAL」はRFC 2119の定義通りに解釈されます。 ただし、読みやすさのため、本仕様ではこれらの語はすべて大文字では記載されていません。

この仕様の全テキストは、明示的に非規範とされたセクション、例、注を除き規範的です。[RFC2119]

この仕様の例は「for example」で始まるか、class="example"で規範文から区別されて示されます:

これは情報提供のための例です。

情報提供的な注は「Note」で始まり、class="note"で規範文から区別されて示されます:

注:これは情報提供的な注です。

勧告(advisement)は、特別な注意を喚起するために規範的なセクションとして強調表示され、<strong class="advisement">で区別されます: UAはアクセシビリティ対応の代替策を必ず提供しなければなりません。

適合クラス

本仕様への適合性は、3つの適合クラスで定義されます:

スタイルシート
CSS スタイルシート
レンダラー
スタイルシートの意味を解釈し、それを使用する文書をレンダリングするUA
オーサリングツール
スタイルシートを書くUA

スタイルシートが本仕様に適合するためには、このモジュールで定義された構文を用いたすべての記述が、汎用CSS文法および各機能の個別文法に従って有効でなければなりません。

レンダラーが本仕様に適合するためには、スタイルシートを関連仕様通りに解釈することに加えて、本仕様で定義されるすべての機能を正しく解析し、文書をそれに応じてレンダリングすることが必要です。ただし、デバイスの制限で文書を正しくレンダリングできない場合、UAが非適合となるわけではありません(例:モノクロモニターで色をレンダリングする必要はありません)。

オーサリングツールが本仕様に適合するためには、汎用CSS文法および本モジュールの各機能の個別文法に従って文法的に正しいスタイルシートを書き、本モジュールで記述されるスタイルシートの他の適合要件もすべて満たす必要があります。

部分的な実装

著者が将来互換性のある構文解析規則を活用してフォールバック値を割り当てられるよう、CSSレンダラーは、利用できるレベルのサポートがない@規則、プロパティ、プロパティ値、キーワード、その他の構文要素を無効(および適切に無視)として扱わなければなりません。特に、UAはサポートしていないコンポーネント値のみを選択的に無視し、サポートされている値のみを複数値プロパティ宣言で適用してはなりません。いずれかの値が無効(サポートされていない値は必ず無効)とみなされる場合、CSSでは宣言全体を無視する必要があります。

不安定機能・独自拡張の実装

将来の安定CSS機能との衝突を避けるため、CSSWGはベストプラクティスに従い、不安定機能や独自拡張の実装を推奨します。

非実験的な実装

仕様が候補勧告段階に達した時点で、非実験的な実装が可能となり、実装者は仕様通りに正しく実装できているCRレベルの機能についてはプレフィックスなしで公開することが推奨されます。

CSSの実装間で相互運用性を確立・維持するため、CSSワーキンググループは、非実験的CSSレンダラーに対し、CSS機能のプレフィックスなし実装を公開する前に、実装報告書(必要に応じてそのテストケースも)をW3Cに提出するよう求めています。W3Cに提出されたテストケースはCSSワーキンググループによるレビューおよび修正の対象となります。

テストケースや実装報告書の提出方法についてはCSSワーキンググループのWebサイト(http://www.w3.org/Style/CSS/Test/)を参照してください。 質問はpublic-css-testsuite@w3.orgまで。

CR終了条件

本仕様を提案勧告に進めるには、各機能に対し少なくとも2つの独立した、相互運用可能な実装が必要です。各機能は異なる製品群で実装されてもよく、すべての機能を単一製品が実装する必要はありません。この条件のため、以下の用語を定義します:

独立
各実装は異なる当事者によって開発され、他の適合実装のコードを共有・再利用・派生してはならない。仕様の実装に関係ないコード部分はこの要件から除外されます。
相互運用可能
公式CSSテストスイートの該当テストケース(またはWebブラウザでない場合は同等のテスト)に合格すること。該当するすべてのテストに同等テストを作成し、そのUAで相互運用性を主張する場合は、他にも同じ方法で同等テストに合格できるUAが必要です。同等テストはピアレビューのため公開されること。
実装
UAは以下を満たすこと:
  1. 仕様を実装していること。
  2. 一般公開されていること(製品版・ベータ版・プレビュー版・nightly buildなど)。 非製品版の場合、少なくとも1ヶ月間その機能を実装して安定性を示す必要があります。
  3. 実験的(テストスイート合格専用・今後通常利用されない)でないこと。

本仕様は少なくとも6ヶ月間候補勧告のままとなります。

索引

本仕様で定義されている用語

参照によって定義される用語

参考文献

規定参考文献

[CSS-ALIGN-3]
Elika Etemad; Tab Atkins Jr.. CSS Box Alignment Module Level 3. 2020年4月21日. WD. URL: https://www.w3.org/TR/css-align-3/
[CSS-BOX-4]
Elika Etemad. CSS Box Model Module Level 4. 2020年4月21日. WD. URL: https://www.w3.org/TR/css-box-4/
[CSS-CASCADE-5]
Elika Etemad; Miriam Suzanne; Tab Atkins Jr.. CSS Cascading and Inheritance Level 5. 2021年1月19日. WD. URL: https://www.w3.org/TR/css-cascade-5/
[CSS-DISPLAY-3]
Tab Atkins Jr.; Elika Etemad. CSS Display Module Level 3. 2020年12月18日. CR. URL: https://www.w3.org/TR/css-display-3/
[CSS-LOGICAL-1]
Rossen Atanassov; Elika Etemad. CSS Logical Properties and Values Level 1. 2018年8月27日. WD. URL: https://www.w3.org/TR/css-logical-1/
[CSS-OVERFLOW-3]
David Baron; Elika Etemad; Florian Rivoal. CSS Overflow Module Level 3. 2020年6月3日. WD. URL: https://www.w3.org/TR/css-overflow-3/
[CSS-POSITION-3]
Elika Etemad; et al. CSS Positioned Layout Module Level 3. 2020年5月19日. WD. URL: https://www.w3.org/TR/css-position-3/
[CSS-PSEUDO-4]
Daniel Glazman; Elika Etemad; Alan Stearns. CSS Pseudo-Elements Module Level 4. 2020年12月31日. WD. URL: https://www.w3.org/TR/css-pseudo-4/
[CSS-VALUES-3]
Tab Atkins Jr.; Elika Etemad. CSS Values and Units Module Level 3. 2019年6月6日. CR. URL: https://www.w3.org/TR/css-values-3/
[CSS-VALUES-4]
Tab Atkins Jr.; Elika Etemad. CSS Values and Units Module Level 4. 2020年11月11日. WD. URL: https://www.w3.org/TR/css-values-4/
[CSS-WRITING-MODES-4]
Elika Etemad; Koji Ishii. CSS Writing Modes Level 4. 2019年7月30日. CR. URL: https://www.w3.org/TR/css-writing-modes-4/
[CSS2]
Bert Bos; et al. Cascading Style Sheets Level 2 Revision 1 (CSS 2.1) Specification. 2011年6月7日. REC. URL: https://www.w3.org/TR/CSS21/
[CSSOM-VIEW-1]
Simon Pieters. CSSOM View Module. 2016年3月17日. WD. URL: https://www.w3.org/TR/cssom-view-1/
[HTML]
Anne van Kesteren; et al. HTML Standard. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[RFC2119]
S. Bradner. RFCで要件レベルを示すためのキーワード. 1997年3月. Best Current Practice. URL: https://tools.ietf.org/html/rfc2119
[SELECTORS-4]
Elika Etemad; Tab Atkins Jr.. Selectors Level 4. 2018年11月21日. WD. URL: https://www.w3.org/TR/selectors-4/

プロパティ索引

名前 初期値 適用対象 継承 %ages アニメーションタイプ 正規順序 算出値
scroll-margin <length>{1,4} 0 すべての要素 no n/a 算出値型による 文法による 各辺ごとに絶対長さ
scroll-margin-block <length>{1,2} 0 すべての要素 no n/a 算出値型による 文法による 個々のプロパティを参照
scroll-margin-block-end <length> 0 すべての要素 no n/a 算出値型による 文法による 絶対長さ
scroll-margin-block-start <length> 0 すべての要素 no n/a 算出値型による 文法による 絶対長さ
scroll-margin-bottom <length> 0 すべての要素 no n/a 算出値型による 文法による 絶対長さ
scroll-margin-inline <length>{1,2} 0 すべての要素 no n/a 算出値型による 文法による 個々のプロパティを参照
scroll-margin-inline-end <length> 0 すべての要素 no n/a 算出値型による 文法による 絶対長さ
scroll-margin-inline-start <length> 0 すべての要素 no n/a 算出値型による 文法による 絶対長さ
scroll-margin-left <length> 0 すべての要素 no n/a 算出値型による 文法による 絶対長さ
scroll-margin-right <length> 0 すべての要素 no n/a 算出値型による 文法による 絶対長さ
scroll-margin-top <length> 0 すべての要素 no n/a 算出値型による 文法による 絶対長さ
scroll-padding [ auto | <length-percentage> ]{1,4} auto スクロールコンテナ no スクロールコンテナのスクロールポートの対応する寸法に対して相対 算出値型による 文法による 各辺ごとにキーワードautoまたは算出された<length-percentage>値
scroll-padding-block [ auto | <length-percentage> ]{1,2} auto スクロールコンテナ no スクロールコンテナのスクロールポートに対して相対 算出値による 文法による 個々のプロパティを参照
scroll-padding-block-end auto | <length-percentage> auto スクロールコンテナ no スクロールコンテナのスクロールポートに対して相対 算出値型による 文法による キーワードautoまたは算出された<length-percentage>値
scroll-padding-block-start auto | <length-percentage> auto スクロールコンテナ no スクロールコンテナのスクロールポートに対して相対 算出値型による 文法による キーワードautoまたは算出された<length-percentage>値
scroll-padding-bottom auto | <length-percentage> auto スクロールコンテナ no スクロールコンテナのスクロールポートに対して相対 算出値型による 文法による キーワードautoまたは算出された<length-percentage>値
scroll-padding-inline [ auto | <length-percentage> ]{1,2} auto スクロールコンテナ no スクロールコンテナのスクロールポートに対して相対 算出値による 文法による 個々のプロパティを参照
scroll-padding-inline-end auto | <length-percentage> auto スクロールコンテナ no スクロールコンテナのスクロールポートに対して相対 算出値型による 文法による キーワードautoまたは算出された<length-percentage>値
scroll-padding-inline-start auto | <length-percentage> auto スクロールコンテナ no スクロールコンテナのスクロールポートに対して相対 算出値型による 文法による キーワードautoまたは算出された<length-percentage>値
scroll-padding-left auto | <length-percentage> auto スクロールコンテナ no スクロールコンテナのスクロールポートに対して相対 算出値型による 文法による キーワードautoまたは算出された<length-percentage>値
scroll-padding-right auto | <length-percentage> auto スクロールコンテナ no スクロールコンテナのスクロールポートに対して相対 算出値型による 文法による キーワードautoまたは算出された<length-percentage>値
scroll-padding-top auto | <length-percentage> auto スクロールコンテナ no スクロールコンテナのスクロールポートに対して相対 算出値型による 文法による キーワードautoまたは算出された<length-percentage>値
scroll-snap-align [ none | start | end | center ]{1,2} none すべての要素 no n/a 離散的 文法による 2つのキーワード
scroll-snap-stop normal | always normal すべての要素 no n/a 離散的 文法による 指定キーワード
scroll-snap-type none | [ x | y | block | inline | both ] [ mandatory | proximity ]? none すべての要素 no n/a 離散的 文法による 指定キーワード