打倒slick！ CSS Scroll Snap入門

## はじめに
2, 3ヶ月ほど前、勧告候補である「[CSS Scroll Snap Module Level 1](https://www.w3.org/TR/css-scroll-snap-1/)」のプロパティ群が、Google Chromeで使用できるようになりました:tada:

いったいどんなことが出来るのか、気になります。そこで、さっそく使ってみました。この記事は、その際のメモです。

用意したものは、最新版のGoogle Chrome(バージョン: `70.0.3538.77`)です。

## CSS Scroll Snap とは
`CSS Scroll Snap` は、**スクロール可能な領域内のスクロール位置を調整できる**機能を提供してくれるようです。

私たちは、Webページから目的の内容を探し出すとき、スクロールをしながら、セクション内の文章を読みます。
そして、目的の内容が見つかると、スクロールを止めます。

その時に「あっ、スクロールしすぎちゃった。最初から読むためにスクロール位置を調整しなきゃ...。」なんてことがよく起こると思います。これは急いでいるときには特に煩わしい作業です。

そこで、そんな作業をしなくても済むように、`CSS Scroll Snap` を使うことが出来ます。これを使えば、**スクロール操作が完了した後に、スクロール位置を指定した場所まで調整してくれる**のです。

## 押さえておくべきプロパティ
- `scroll-snap-type`プロパティ
- `scroll-snap-align`プロパティ
- `scroll-padding`プロパティ
- `scroll-margin`プロパティ

`CSS Scroll Snap`を使うなら、とりあえずこの4つを押さえておけば良いかな、と思います。

### `scroll-snap-type`プロパティ
`scroll-snap-type`プロパティは、

- スクロール位置の調整を行うかどうか
- どの軸でスクロール位置を調整するか
- どの程度厳密に調整するか

を指定します。
このプロパティは、**スクロール可能な領域(スクロールコンテナ)に対して設定**します。

#### スクロール位置の調整を行うかどうか
スクロール位置の調整を行わない場合、このプロパティには`none`を設定します。これが初期値です。

#### どの軸でスクロール位置を調整するか
- `x`: 
X 軸でスクロール位置の調整を有効にする
- `y`: 
Y 軸でスクロール位置の調整を有効にする
- `block`: 
ブロック軸でスクロール位置の調整を有効にする
- `inline`: 
インライン軸でスクロール位置の調整を有効にする
- `both`: 
X, Y 軸両方でスクロール位置の調整が行われる

#### どの程度厳密に調整するか
- `mandatory`: 
スクロール位置を調整できる場合、スクロール位置が調整される
- `proximity`: 
スクロール位置を調整でき、調整後のスクロール位置が現在位置から近い場合は、スクロール位置が調整される。そうでない場合、スクロール位置の調整は行われない。

#### 使用例
これらの設定は結構重要なのですが、言葉だけでは伝わりにくい部分があるので、簡単な例を用意しました。
調整の厳密さに関する指定である`mandatory`と`proximity`を並べて、比較できるようにしています。
最新版のGoogle Chromeであれば動作すると思うので、ぜひ実際に試してみてください([動作確認リンク](https://codepen.io/anon/pen/QVexre))。

両方ぐりぐり弄っているとわかると思いますが、**`mandatory`では絶対にスクロール位置の調整が行われる**のに対して、**`proximity`ではスクロール位置の調整が行われない位置があります**。

```HTML
<div class="container">
  <div class="scroll-container">
    <img src="http://placehold.jp/3d4070/ffffff/450x450.png?text=mandatory1" alt="">
    <img src="http://placehold.jp/3d4070/ffffff/450x450.png?text=mandatory2" alt="">
    <img src="http://placehold.jp/3d4070/ffffff/450x450.png?text=mandatory3" alt="">
  </div>

  <div class="scroll-container proximity">
    <img src="http://placehold.jp/703d40/ffffff/450x450.png?text=proximity1" alt="">
    <img src="http://placehold.jp/703d40/ffffff/450x450.png?text=proximity2" alt="">
    <img src="http://placehold.jp/703d40/ffffff/450x450.png?text=proximity3" alt="">
  </div>
</div>
```

```CSS
.container {
  display: flex;
}

.scroll-container {
  display: flex;
  flex-basis: 50%;
  flex-direction: column;
  overflow-y: scroll;
  scroll-snap-type: y mandatory;
  max-height: 100vh;
}

.proximity {
  scroll-snap-type: y proximity;
}

img {
  scroll-snap-align: start;
  margin-bottom: 5px;
}
```

### `scroll-snap-align`プロパティ

`scroll-snap-align`プロパティでは、

- スクロール位置の調整を行うかどうか
- スクロールコンテナ内の可視領域(スナップポート)にある要素をどのように配置するか

を指定します。
このプロパティは、**スクロール可能な領域の子要素に対して設定**します。

#### スクロール位置の調整を行うかどうか
- `none`: 
`none`を指定したコンテンツでは、スクロール位置の調整が行われない。つまり、スクロールが調整される場合、そのコンテンツの隣にあるコンテンツにスクロールされる。[動作確認リンク](https://codepen.io/anon/pen/QJWdbd)を用意した。これを実行すると、2つ目の`img`要素にスクロールが調整されないことがわかる
- `start`: 
スクロールコンテナ内で可視領域にある要素の始端が、スクロールコンテナの始端に位置するように、スクロール位置が調整される
- `end`: 
スクロールコンテナ内の可視領域にある要素の終端が、スクロールコンテナの終端に位置するように、スクロール位置が調整される
- `center`: 
スクロールコンテナ内の可視領域にある要素の中央が、スクロールコンテナの中央に位置するように、スクロール位置が調整される

#### 使用例
こちらも、言葉だけではいまいちわからないと思うので、例を用意しました([動作確認リンク](https://codepen.io/anon/pen/dqxjbK))。
`scroll-snap-align`プロパティに、`start`, `end`, `center`を指定した時の比較をしています。

この例を実行してみるとわかると思いますが、**`start`を指定すると、スクロールコンテナの上端にスクロール位置が調整される**ようになります。**`center`, `end`を指定した場合も、それぞれスクロールコンテナの中央、下端に**スクロール位置が調整されます。

```HTML
<div class="container">
  <div class="scroll-container start">
    <img src="http://placehold.jp/3d4070/ffffff/450x450.png?text=start1" alt="">
    <img src="http://placehold.jp/3d4070/ffffff/250x250.png?text=start2" alt="">
    <img src="http://placehold.jp/3d4070/ffffff/450x450.png?text=start3" alt="">
  </div>

  <div class="scroll-container end">
    <img src="http://placehold.jp/3d4070/ffffff/450x450.png?text=end1" alt="">
    <img src="http://placehold.jp/3d4070/ffffff/250x250.png?text=end2" alt="">
    <img src="http://placehold.jp/3d4070/ffffff/450x450.png?text=end3" alt="">
  </div>

  <div class="scroll-container center">
    <img src="http://placehold.jp/3d4070/ffffff/450x450.png?text=center2" alt="">
    <img src="http://placehold.jp/3d4070/ffffff/250x250.png?text=center1" alt="">
    <img src="http://placehold.jp/3d4070/ffffff/450x450.png?text=center3" alt="">
  </div>
</div>
```

```CSS
.container {
  display: flex;
}

.scroll-container {
  display: flex;
  flex-basis: 33.3%;
  flex-direction: column;
  overflow-y: scroll;
  scroll-snap-type: y mandatory;
  max-height: 100vh;
}

img {
  margin-bottom: 5px;
}

.start img {
  scroll-snap-align: start;
}

.end img {
  scroll-snap-align: end;
}

.center img {
  scroll-snap-align: center;
}
```

### `scroll-padding`プロパティ
`scroll-padding`プロパティは、

- スクロールコンテナ内に`padding`を設定するかどうか

を指定できます。

`scroll-padding`プロパティについては、例を使いながら説明をします。
たとえば、[このような](https://codepen.io/anon/pen/zmOvvz)固定ヘッダーがあるページで、`CSS Scroll Snap`を使用した場合を考えます。

```HTML
<div class="container">
  <header></header>
  <div class="scroll-container start">
    <img src="http://placehold.jp/3d4070/ffffff/450x450.png?text=start1" alt="">
    <img src="http://placehold.jp/3d4070/ffffff/250x250.png?text=start2" alt="">
    <img src="http://placehold.jp/3d4070/ffffff/450x450.png?text=start3" alt="">
  </div>
</div>
```
```CSS
body{
  margin:0;
}

.container {
  display: flex;
}

header {
  position: fixed;
  width: 100vw;
  height: 100px;
  background: rgba(255,0,255,.3);
  top: 0;
  left: 0;
}

.scroll-container {
  display: flex;
  flex-direction: column;
  overflow-y: scroll;
  scroll-snap-type: y mandatory;
  max-height: 100vh;
  scroll-padding-top: 150px;
}

img {
  margin-bottom: 5px;
}

.start img {
  scroll-snap-align: start;
}
```

従来の方法であれば、`padding-top`を固定ヘッダーの高さ分設定することで、固定ヘッダーの部分に余白が作られ、コンテンツ全体が表示されるようになります。

しかし、`CSS Scroll Snap`を使用すると、`padding-top`を設定していても、**スクロール位置を調整する際に`padding`を無視したスクロール位置の調整が行われます**。

こちらは[`padding-top`のみを設定したときの例](https://codepen.io/anon/pen/GwKajP)です。一度スクロールすると、スクロール位置の調整が行われ、`padding-top`分を無視したスクロール位置の調整が行われることがわかると思います。

```HTML
<div class="container">
  <header></header>
  <div class="scroll-container start">
    <img src="http://placehold.jp/3d4070/ffffff/450x450.png?text=start1" alt="">
    <img src="http://placehold.jp/3d4070/ffffff/250x250.png?text=start2" alt="">
    <img src="http://placehold.jp/3d4070/ffffff/450x450.png?text=start3" alt="">
  </div>
</div>
```
```CSS
body {
  margin: 0;
}

.container {
  display: flex;
  max-height: 100vh;
}

header {
  position: fixed;
  width: 100vw;
  height: 100px;
  background: rgba(255, 0, 255, 0.3);
  top: 0;
  left: 0;
}

.scroll-container {
  display: flex;
  flex-direction: column;
  overflow-y: scroll;
  scroll-snap-type: y mandatory;
  max-height: 100vh;
  padding-top: 100px;
}

img {
  margin-bottom: 5px;
}

.start img {
  scroll-snap-align: start;
}
```
この動作を防ぐために、`scroll-padding`プロパティを使うことができます。
`scroll-padding`プロパティを使うことで、スクロール位置の調整にも`padding`が考慮されるようになります。

`scroll-padding`プロパティを使った時の動作は[このようになります](https://codepen.io/anon/pen/jQNomb)。

```HTML
<div class="container">
  <header></header>
  <div class="scroll-container start">
    <img src="http://placehold.jp/3d4070/ffffff/450x450.png?text=start1" alt="">
    <img src="http://placehold.jp/3d4070/ffffff/250x250.png?text=start2" alt="">
    <img src="http://placehold.jp/3d4070/ffffff/450x450.png?text=start3" alt="">
  </div>
</div>
```

```CSS
body {
  margin: 0;
}

.container {
  display: flex;
  max-height: 100vh;
}

header {
  position: fixed;
  width: 100vw;
  height: 100px;
  background: rgba(255, 0, 255, 0.3);
  top: 0;
  left: 0;
}

.scroll-container {
  display: flex;
  flex-direction: column;
  overflow-y: scroll;
  scroll-snap-type: y mandatory;
  max-height: 100vh;
  padding-top: 100px;
  scroll-padding-top: 100px;
}

img {
  margin-bottom: 5px;
}

.start img {
  scroll-snap-align: start;
}
```

### `scroll-margin`プロパティ
`scroll-margin`プロパティは、

- スクロールコンテナに対する`margin`を設定するか

を指定できます。
(※このプロパティの動作については、十分に理解できていないので、この部分は改めて追記する予定です。)

### ブラウザの対応状況
[Can I use](https://caniuse.com/#feat=css-snappoints) によると、Google Chrome, Safari は`CSS Scroll Snap`をサポートしているようですが、その他のブラウザでのサポートはまだ十分ではありません。また、現在の段階ではGoogle Chrome, Safari間の実装にも差異がある可能性が残っているため、注意が必要です。

以上。