次世代Webスタイリングを追う ーScroll Snap, :focus-within, @media (prefers-*), :is() 

こちらは「[DeNA Advent Calendar 2019](https://qiita.com/advent-calendar/2019/dena)」 の16日目です。
今年は「[20新卒のAdvent Calendar](https://qiita.com/advent-calendar/2019/dena-20-shinsostu)」もありますので、よかったら合わせてご覧ください。

## 概要
[Chrome Dev Summit 2019](https://developer.chrome.com/devsummit/) にて、「 [Next-generation web styling](https://www.youtube.com/watch?v=-oyeaIirVC0) 」というセッションがありました。このセッションでは、CSSのセレクタやプロパティをはじめとするWebスタイリングにまつわる多くの新しいTipsが紹介されています。

セッション内の全てのTipsを手を動かしながら確認したかったんですが、色々と間に合いませんでした。なので、現時点で僕が理解できた（であろう）内容を記載したいと思います。

## 本記事で紹介するTips
本記事では、セッションの前半で紹介されているこちらのTipsを紹介します。

- [CSSプロパティ: Scroll Snap](#cssプロパティscroll-snap)
- [CSSセレクタ: :focus-within](#cssセレクタfocus-within)
- [メディアクエリ: @media (prefers-reduced-motion)](#メディアクエリmedia-prefers-reduced-motion)
- [メディアクエリ: @media (prefers-color-scheme)](#メディアクエリmedia-prefers-color-scheme)
- [CSSセレクタ: :is()](#cssセレクタis)

記載しているサンプルとソースコードは、こちらで公開しています。よかったらご覧ください(随時更新予定)。

- サンプル: https://css.yagisuke0209.now.sh
- ソースコード: https://github.com/yagisuke/css

それでは見ていきましょう/

## CSSプロパティ: Scroll Snap
トップバッターは `Scroll Snap` 。新しいCSSのプロパティです。

このプロパティを指定することで、スマホのホーム画面のスクロールのように、スクロールした後に特定の要素にsnapするようになります。CSSだけでこれを表現できるのは嬉しいですね。

### サンプル
水平軸のスクロールで、snap対象の要素を中央に表示させるようにしています([サンプルのURL](https://css.yagisuke0209.now.sh/styles/scroll_snap/))。

![scroll-snap](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191671/c445cac8-d9a8-b857-8c80-65e6696ba537.gif)

```html:HTML
<div class="container">
  <img src="1.png" width="300" height="300">
  <img src="2.png" width="300" height="300">
  <img src="3.png" width="300" height="300">
  <img src="4.png" width="300" height="300">
  <img src="5.png" width="300" height="300">
</div>
```

```css:CSS
.container {
  display: flex;
  height: 300px;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
}
.container img {
  scroll-snap-align: center;
}
```

`scroll-snap-type`: スクロール方向を指定。今回は水平軸(x)でしたが、垂直軸(Y)や両軸(both)などもあります。実際の挙動は[こちら](https://css.yagisuke0209.now.sh/styles/scroll_snap/)で確認ください。

`scroll-snap-align`: snap対象の要素をどの位置に表示するかを指定。今回は中央(center)でしたが、先頭(start)や後尾(end)にもすることができます。

`overscroll-behavior`: Scroll Snapと合わせたい代物です。これは、スクロールの連鎖を抑制します。何が嬉しいのかというと、Scroll Snapのエリアをスマホで左にスクロールし続けた場合、ユーザーの意図とは反してブラウザバッグする可能性があります。そんな事故防止に効果的です。

### ブラウザの対応状況
| CSS property: scroll-snap-type | CSS property: scroll-snap-align |
|:-:|:-:|
| ![scroll-snap-type](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191671/b22ef671-a293-32ce-8415-8280d2f04e23.png)<br />[caniuse.com](https://caniuse.com/#feat=mdn-css_properties_scroll-snap-type)  | ![scroll-snap-align](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191671/27c772e4-8aa8-5a56-d428-df5f1b6ad453.png)<br />[caniuse.com](https://caniuse.com/#feat=mdn-css_properties_scroll-snap-align)  |

### 参考資料
- [scroll-snap-type - CSS: カスケーディングスタイルシート | MDN](https://developer.mozilla.org/ja/docs/Web/CSS/scroll-snap-type)
- [overscroll-behavior - CSS: カスケーディングスタイルシート | MDN](https://developer.mozilla.org/ja/docs/Web/CSS/overscroll-behavior)
- [CSSでスクロールのスナップが可能に！scroll-snapプロパティの基礎知識と便利な使い方 | コリス](https://coliss.com/articles/build-websites/operation/css/how-to-use-css-scroll-snap.html)

## CSSセレクタ: :focus-within
次にCSSセレクタに新しく追加された `:focus-within` です。これは、その要素自体がフォーカスされているか、または、その要素の子孫がフォーカスされているかを表現することができます。

### サンプル
入力欄がフォーカスされた時に親要素の背景色と、その子要素の画像をアニメーションさせています([サンプルのURL](https://css.yagisuke0209.now.sh/styles/focus_within/))。Chrome DevToolsでも`:focus-within`に切り替えることができます。

![:focus-within](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191671/5285c8c6-0d79-f737-d1ec-6b7dc3a47f15.gif)

```html:HTML
<div class="container">
  <img src="1.png" width="50" height="50">
  <input type="text" value="" placeholder="Please focus...">
</div>
```

```css:CSS
.container {
  display: flex;
  justify-content: center;
  align-items: center;
}
.container img {
  transition: all .5s;
  opacity: .25;
}
.container:focus-within {
  background-color: #2c8898;
}
.container:focus-within img {
  opacity: 1;
  transform: rotate(360deg);
}
```

### ブラウザの対応状況

| CSS selector: :focus-within |
|:-:|
| ![:focus-within](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191671/d53dfb6a-ec8a-691e-a725-b585d58e5e1c.png)<br />[caniuse.com](https://caniuse.com/#feat=mdn-css_selectors_focus-within)  |

### 参考資料

- [:focus-within - CSS: カスケーディングスタイルシート | MDN](https://developer.mozilla.org/ja/docs/Web/CSS/:focus-within)

## メディアクエリ: @media (prefers-reduced-motion)
激しいアニメーションが苦手な方に向けて、「視差効果を減らす」という設定メニューがあります(初耳だった)。iPhoneはこちらの[サポートページ](https://support.apple.com/ja-jp/HT202655)の通り、設定メニューがありました。

そしてWebコンテンツも、`prefers-reduced-motion` を使うことで、視差効果を減らしたいユーザー向けにCSSを記述できるようになりました。

### サンプル
意味もなく激しく動くアニメーションを、視差効果を減らしたいユーザーには静止して表示します([サンプルのURL](https://css.yagisuke0209.now.sh/styles/prefers_reduced_motion/))。

なお、Chrome DevToolsのシミュレーターは、[Chrome 79で対応](https://developers.google.com/web/updates/2019/10/devtools)ということです。それまでは[Canary](https://www.google.com/intl/ja/chrome/canary/)で開発すると良いと思います。

![prefers-reduced-motion](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191671/ffaad35e-a11a-36fb-a111-8288ff6cda65.gif)

```html:HTML
<div class="container">
  <div class="motion">
    <img src="1.png" width="50" height="50">
    <img src="2.png" width="50" height="50">
    <img src="3.png" width="50" height="50">
    <img src="2.png" width="50" height="50">
    <img src="1.png" width="50" height="50">
  </div>
</div>
```

```css:CSS
.container {
  position: relative;
  height: 300px;
}
.motion {
  position: absolute;
  top: 0;
  right: 0;
  left: 0;
  display: flex;
  justify-content: space-around;
  animation: MoveUpDown 1s linear infinite;
}
@media (prefers-reduced-motion: reduce) {
  .motion {
    top: 50%;
    transform: translateY(-50%);
    animation: none;
  }
}
@keyframes MoveUpDown {
  0%, 100% { top: 0; }
  50% { top: 250px; }
}
```

`.motion`で上下にアニメーションを行なっており、`@media (prefers-reduced-motion: reduce)`を使って、アニメーションを打ち消しています。

### ブラウザの対応状況
一生懸命がんばって作ったアニメーションが...という心の悲鳴が聞こえてきそうですが、ブラウザは大方対応できているようです。

| prefers-reduced-motion media query |
|:-:|
| ![prefers-reduced-motion](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191671/3cddcf69-ea53-2334-96bc-a7b15fe000d2.png)<br />[caniuse.com](https://caniuse.com/#feat=prefers-reduced-motion)  |

### 参考資料
- [CSSのprefers-reduced-motionについて。iPhoneで「視差効果を減らす」設定時には注意 | Arrown](https://arrown-blog.com/prefers-reduced-motion/)

## メディアクエリ: @media (prefers-color-scheme)
macOS Mojaveにて、OSレベルで外観をdarkモードに変更できるようになりました(僕はまだdarkモードに慣れずにいます)。

そしてWebコンテンツも、`prefers-color-scheme` というメディアクエリを用いて、light/darkごとにCSSで表現できるようになりました。

### サンプル
背景色と文字色をモードごとに切り替えるサンプルです([サンプルのURL](https://css.yagisuke0209.now.sh/styles/prefers_color_scheme/))。prefers-reduced-motionと同じく、Chrome DevToolsのシミュレーターは[Chrome 79で対応](https://developers.google.com/web/updates/2019/10/devtools)ということです。

![prefers-color-scheme](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191671/d506eb63-4c3e-229a-9b45-062eae6529a6.gif)

```html:HTML
<div class="container">
  <p>Hello</p>
  <p>你好</p>
  <p>こんにちは</p>
  <p>안녕하세요</p>
  <p>السلام عليكم</p>
</div>
```

```css:CSS
:root {
  --theme-font: #0f0f0f;
  --theme-background: #ffffff;
}
@media (prefers-color-scheme: dark) {
  :root {
    --theme-font: #ffffff;
    --theme-background: #0f0f0f;
  }
}
.container {
  background-color: var(--theme-background);
  color: var(--theme-font);
}
```

CSSの[カスタムプロパティ](https://developer.mozilla.org/ja/docs/Web/CSS/Using_CSS_custom_properties)を使い、初期値のlightモード用のカラーコードをdarkモード時にはdarkモード用のカラーコードに上書きするようにしています。そのため、背景色と文字色はカスタムプロパティを参照すればlight/darkそれぞれのモード用に色を切り替えることができます。

### ブラウザの対応状況
ブラウザもだいぶ対応されてきている状況です。iOSアプリでは、[darkモード対応を推奨している](https://developer.apple.com/documentation/appkit/supporting_dark_mode_in_your_interface/choosing_a_specific_interface_style_for_your_ios_app/)ので、WebViewの開発担当者をはじめ、darkモードの対応が今後増えてくると思います。

| prefers-color-scheme media query |
|:-:|
| ![prefers-color-scheme](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191671/f46726ce-e33f-2f36-4066-1f1cfa19846d.png)<br />[caniuse.com](https://caniuse.com/#feat=prefers-color-scheme)  |

### 参考資料
- [prefers-color-scheme を用いた Dark Mode 対応と User Preference Media Features | blog.jxck.io](https://blog.jxck.io/entries/2018-11-10/dark-mode-via-prefers-color-scheme.html)

## CSSセレクタ: :is()
最後に、新しくCSSに追加されそうな `:is()` セレクタについてです。このセレクタを使うと、CSSを少しだけシンプルにすることができます。

### サンプル
`:is()`のサンプルです([サンプルのURL](https://css.yagisuke0209.now.sh/styles/is/))。例えば、記事の見出し(h1 ~ h6)のスタイルを統一したいときに、これまで下記のようにCSSを書いてきたかと思います。

```css:CSS
article > h1, article > h2, article > h3,
article > h4, article > h5, article > h6 {
  color: #0f4c81;
}
```

これに `:is()` を使うと、以下のようにシンプルに記載することができます。

```css:CSS
article > :is(h1, h2, h3, h4, h5, h6) {
  color: #0f4c81;
}
```

### ブラウザの対応状況
使う場面けっこうありそうですが、ブラウザの対応状況は待ちの状況。期待して待っていましょう/

| CSS selector: :is() |
|:-:|
| ![:is()](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191671/88aeacd6-cd50-8587-ad5d-5bb27ff62a3c.png)<br />[caniuse.com](https://caniuse.com/#feat=mdn-css_selectors_is) |

### 参考資料
- [:is() (:matches(), :any()) - CSS: カスケーディングスタイルシート | MDN](https://developer.mozilla.org/ja/docs/Web/CSS/:is)

## おわりに
[Next-generation web styling](https://www.youtube.com/watch?v=-oyeaIirVC0)のセッションでは、他にも多くの新しいWebスタイリングのTipsが紹介されています。`Gap` / `Logical Properties` / `Subgrid` / `Paint API` / `Houdini` ...。

お腹いっぱいになりそうですが、サービスの使い心地を少しでも高めるためにもぜひキャッチアップしていきたいと考えています。また、試作品ができてきたら公開していきます。

以上、ご覧くださりありがとうございました。