パララックスならscrollイベントを間引かずとも@scroll-timelineで良いじゃない

## この記事の概要

パララックスを実装する際、`setTimeout`や`throttle`で間引くケースがあると思います。

しかし、単にスクロール量にあわせてアニメーションがしたいだけならCSSの`@scroll-timeline`を使った方が簡単ですよ、と紹介する記事です。

`@scroll-timeline`を使えば、例えば以下のようなアニメーションがCSSだけでできるようになります。（楽しくなってパララックス以外の挙動も入れて作っちゃいましたが、ご容赦ください笑）

![movie.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214677/89f04e97-5a39-682d-988a-f361a0050ecb.gif)

:::note warn
この記事を投稿している2022年6月30日現在、すべてのブラウザで利用不可です。
そのため、これは未来に向けた記事投稿です。
なお、Chromeの場合は`chrome://flags`から`#enable-experimental-web-platform-features`のフラグを有効にしていれば一応見れます。

<picture>
<source type="image/webp" srcset="https://caniuse.bitsofco.de/static/v1/css-scroll-timeline-1656589562487.webp">
<source type="image/png" srcset="https://caniuse.bitsofco.de/static/v1/css-scroll-timeline-1656589562487.png">
<img src="https://caniuse.bitsofco.de/static/v1/css-scroll-timeline-1656589562487.jpg" alt="Data on support for the css-scroll-timeline feature across the major browsers from caniuse.com">
</picture>
:::

## 使い方

### `@scroll-timeline`を定義する

```css:書式
@scroll-timeline custom-ident-name {
  source: auto; /* auto | selector("id-selector") | none */
  orientation: auto; /* auto | block | inline | horizontal | vertical */
  scroll-offsets: none; /* none | <length-percentage> | <element-offset> */
}
```

`custom-ident-name`は`@keyframes`で名前をつけるのと同じ感覚です。

`source`は、スクロールする要素を指定します。
何も指定しなければhtml全体を対象とします。
特定の要素を指定したい場合、`class`ではなく`id`を指定してください。

`orientation`はスクロールの向きを指定します。
`horizontal`と`vertical`は、昔ながらの指定というか、書字方向関係無く水平か垂直かを指定します。
`block`と`inline`は論理的プロパティにあわせての指定です。

`scroll-offsets`は、スクロールの中でアニメーションが発生する場所を決められます。
そのため、指定したアニメーションのうち半分までを再生する、といった使い方もできます。

### `@keyframes`を定義する

`@scroll-timeline`はあくまでスクロールの仕方なので、アニメーションそのものは別途定義せねばなりません。
CSSアニメーションについて書かれた記事は他にもあるので、説明はそちらに譲ります。

https://qiita.com/soarflat/items/4a302e0cafa21477707f


### アニメーションさせたい要素に`animation-name`と`animation-timeline`を指定する

アニメーションさせたい要素に、これまで指定した中身を適用させます。

```css
.element {
  animation-duration: 1000ms;
  animation-name: awesomeAnimation; /* @keyframesで定義した名前を指定する */
  animation-timeline: superTimeline; /* @scroll-timelineで定義した名前を指定する */
  background-color: #55c500;
  height: 100px;
  width: 100px;
}
```

### 完成

あとは具体的なスタイルを書けばOKです。

一応、今回書いたコードの全体像を載せておきます

<details>

```html:index.html
  <body>
    <div id="container">
      <h1 class="heading">Scroll down<br />↓</h1>
      <div class="column1 near green"></div>
      <div class="column2 far blue"></div>
      <div class="column3 middle black"></div>
      <div class="column1 far black"></div>
      <div class="column2 middle green"></div>
      <div class="column3 near blue"></div>
      <div class="column1 middle blue"></div>
      <div class="column2 near black"></div>
      <div class="column3 far green"></div>
      <div class="column1 near green"></div>
      <div class="column2 far blue"></div>
      <div class="column3 middle black"></div>
      <div class="column1 far black"></div>
      <div class="column2 middle green"></div>
      <div class="column3 near blue"></div>
      <div class="column1 middle blue"></div>
      <div class="column2 near black"></div>
      <div class="column3 far green"></div>
      <div class="column1 near green"></div>
      <div class="column2 far blue"></div>
      <div class="column3 middle black"></div>
      <div class="column1 far black"></div>
      <div class="column2 middle green"></div>
      <div class="column3 near blue"></div>
      <div class="column1 middle blue"></div>
      <div class="column2 near black"></div>
      <div class="column3 far green"></div>
      <h1 class="heading">↑<br />Scroll up</h1>
    </div>
  </body>
```

```css:style.css
* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

#container {
  display: grid;
  gap: 1rem;
  grid-template-columns: 1fr repeat(3, 300px) 1fr;
  padding-block: 20vh;
  place-items: center;
  width: 100%;
}

.heading {
  font-family: "SF Pro", sans-serif;
  font-size: 96px;
  font-weight: bold;
  grid-column: 2 / 5;
  padding-block: 5vh;
  text-align: center;
}

.column1 {
  transform-origin: center right;
  grid-column: 2 / 3;
}

.column2 {
  grid-column: 3 / 4;
}

.column3 {
  transform-origin: center left;
  grid-column: 4 / 5;
}

.near {
  animation-direction: alternate;
  animation-duration: 1000ms;
  animation-name: nearAnimation;
  animation-timeline: moveTimeline;
  border-radius: 48px;
  height: 240px;
  width: 240px;
  z-index: 1;
}

.middle {
  animation-direction: alternate;
  animation-duration: 1000ms;
  animation-name: middleAnimation;
  animation-timeline: moveTimeline;
  border-radius: 32px;
  height: 160px;
  width: 160px;
  z-index: 0;
}

.far {
  animation-direction: alternate;
  animation-duration: 1000ms;
  animation-name: farAnimation;
  animation-timeline: moveTimeline;
  border-radius: 16px;
  height: 80px;
  width: 80px;
  z-index: -1;
}

.green {
  background-color: #55c500;
}

.blue {
  background-color: #4097db;
}

.black {
  background-color: #1e2121;
}

@scroll-timeline moveTimeline {
  orientation: vertical;
  scroll-offsets: selector(#container) start 0, selector(#container) end 0;
  source: selector("#container");
}

@keyframes nearAnimation {
  0% {
    transform: translateY(100%);
  }
  100% {
    transform: translateY(-100%) rotate(360deg);
  }
}

@keyframes middleAnimation {
  0% {
    transform: translateY(60%);
  }
  100% {
    transform: translateY(-60%) rotate(360deg);
  }
}

@keyframes farAnimation {
  0% {
    transform: translateY(10%);
  }
  100% {
    transform: translateY(-2%) rotate(360deg);
  }
}
```

</details>

---

最後まで読んでくださってありがとうございます！
Twitterでも情報を発信しているので、良かったらフォローお願いします！

https://twitter.com/xrxoxcxox