待ち時間に雪を降らせたい（React）

## はじめに
こんにちは @hey3 です。
この記事はACCESS Advent Calendar 2019 23日目の記事です。
Qiita に記事を投稿するのが初めてなので緊張しています。。

今回の記事はクリスマス間近と言う事で、ただただやってみたかった事をやっているだけです。（実用性皆無です）（ネタです）
※ネタで１枠使ってしまって申し訳無い気持ちはあります。

## 概要
クリスマス近いし、非同期の更新処理中にコンポーネントに雪を降らせたい
（本来待ち時間は無い方が良いんですけどね。。）

## 構成
サクッと非同期処理を用意するために `React v16.6` 以降に追加された `lazy` と `Suspense` を用いました。
`lazy` と `Suspense` に関しては深く触れないため、知りたい場合は Google 先生にお願いしてください。
styling には、 `styled-component` を用いました。
あと、気持ち程度の `TypeScript` です。

## 実際にやってみた

所々怒られそうなコードが出てくるけど、しょうがないよね。。( 'ω')
指摘ありましたら、どんどん教えてください！

#### デモ用に枠を作る

```jsx
// LoadingArea/index.tsx

import React, { lazy, Suspense } from 'react'
import styled from 'styled-components'

const LazyComponent = lazy(() => import('../../components/Lazy'))

const LoadingArea = styled.div`
  width: 800px;
  height: 500px;
`

export default () => (
  <LoadingArea>
    <Suspense fallback={<SnowLoading />}>
      <LazyComponent />
    </Suspense>
　</LoadingArea>
)
```

将来的には汎用的に使えるようにしたいけど、今回は横800px, 縦500pxに固定します。
`Suspense` の `fallback` には `SnowLoading` コンポーネント（後述）を指定します。

#### 雪を降らせるための背景を作る

今回は `radial-gradient` を使って雪が降ってそうな背景を作っていきます。

```jsx
// SnowLoading/index.tsx

import React from 'react'
import styled from 'styled-components'

const Container = styled.div`
  display: grid;
  justify-content: center;
  align-content: center;
  width:100%;
  height:100%;
  background: radial-gradient(farthest-corner at 50% 50%,#7397a1 1%,#3f2c41 100%);
  position: relative;
`

export default () => (
  <Container />
)
```

出来たのがこちら
![snow_background.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/539403/8f30d669-4d45-9c36-c80c-dc888a76aa55.png)

寒さが伝わってきて如何にも雪が降ってそうな見た目ですね( 'ω')


#### 本題の雪を作っていく
雪は `position:absolute` で降らせていきます。

```jsx
// Snow/index.tsx

import React from 'react'
import styled, { css, keyframes } from 'styled-components'

interface Props {
  left: number
  delay: number
}

const fall = keyframes`
  0% {
    top: 0;
    opacity: 0;
  }
  100% {
    top: 95%;
    opacity: 1;
  }
`

const swing = keyframes`
  0% {
    transform: translateX(0px);
  }
  50% {
    transform: translateX(70px);
  }
  100% {
    transform: translateX(0px);
  }
`

const snowAnimation = (delay: number) =>
  css`
    ${swing} 2s infinite ease-in-out, ${fall} 5s infinite linear ${delay}s;
  `

const Snow = styled.div<{left: number, delay: number}>`
  position: absolute;
  color: green;
  opacity: 0;
  left: ${({ left }) => left}%;
  z-index: 1000;
  animation: ${({ delay }) => snowAnimation(delay)};

  &:after {
    content: "\\2744";
  }
`

export default ({ left, delay }: Props) => (
  <Snow left={left} delay={delay} />
)
```

雪が落ちるアニメーションと揺れるアニメーションに分けて `@keyframes` を用意します。
落ちるアニメーションで `opacity` を変えてあげると幻想的ですね( 'ω')

`div` タグ一つに対して一つの雪結晶を表現しています。
降らせる位置(left)と降らせるタイミング(delay)を props で渡す事で、親コンポーネント側で各雪結晶のアニメーションをずらす事ができます。
この辺は `styled-component` を使うとやりやすいですね。

ちなみに、今回使用した雪結晶はこちら &#x2744「&#x2744;」 です。

実際に雪を生成してみます。
先ほどの `SnowLoading` コンポーネントを以下のように修正します。

```jsx
// SnowLoading/index.tsx
...
import Snow from '../Snow'

...

export default () => (
  <Container>
    {[...Array(30)].map(index =>
      <Snow key={index} left={Math.floor(Math.random() * 90)} delay={Math.floor(Math.random() * 20)}/>
    )}
  </Container>
)
```

やってはいけなそうな匂いがしますが、今回は目を瞑りましょう( ˘ω˘ )
これである程度ランダムに３０個の雪結晶を降らせます。
実行したものがこちらです。（容量圧縮したので荒いです）
![snow_animation-compressor.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/539403/ea4b95c6-133b-54e1-60e7-170248a9203a.gif)

おー、雪が振りましたね。( 'ω')
満足です。
ただ、これだと非同期処理中だと言う事がわかりずらいですね。
雪を積もらせていきます。

#### 雪を積もらせていく

こんなもんで良いでしょう

```jsx
// PileUpSnow/index.tsx

import React from 'react'
import styled, { css, keyframes } from 'styled-components'

const pileUp = keyframes`
  0% {
    height: 0;
  }
  100% {
    height: 100%;
  }
`

const pileUpAnimation = () =>
  css`
    ${pileUp} 7s infinite ease-out;
`

const PileUpSnow = styled.div`
  position: absolute;
  bottom: 0;
  width: 100%;
  background: aliceblue;
  z-index: 2000;
  animation: ${pileUpAnimate};
`

export default () => (
  <PileUpSnow />
)
```

![snow_piling_up-compressor.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/539403/2470f2df-3215-ebd3-569c-edcfd1317035.gif)

才能が無く向いてなさそうな感じがしますね。。

#### Loading っぽさを追加する

```jsx
// Loading/index.tsx

import React from 'react'
import styled, { css, keyframes } from 'styled-components'

const loading = keyframes`
  0% { transform: translate(0,0); }
  50% { transform: translate(0,15px); }
  100% { transform: translate(0,0); }
`

const loadingAnimation = delay =>
  css`
    ${loading} .6s infinite linear ${delay};
  `

const Container = styled.div`
  position: absolute;
  z-index: 3000;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  margin: auto;
  width: 5rem;
  height: 1rem;
`

const Point = styled.div`
  display: inline-block;
  width: 1rem;
  height: 1rem;
  margin-right: 0.3rem;
  border-radius: 1.2rem;
  background-color: #4b9cdb;
  &:nth-last-child(1) {
    animation: ${loadingAnimation('.1s')};
  }
  &:nth-last-child(2) {
    animation: ${loadingAnimation('.2s')};
  }
  &:nth-last-child(3) {
    animation: ${loadingAnimation('.3s')};
  }
`

export default () => (
  <Container>
    <Point />
    <Point />
    <Point />
  </Container>
)
```

![loading.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/539403/dcf89b76-48e0-79a9-2a96-a35b97b83cb9.gif)


こんなのを真ん中に配置しています。
loading on loading みたいでダサさが際立ちました。( 'ω')

## 成果物

画質も出来も荒いですが、このようなものを作成しました。（もはや雪があまり見えないですね( 'ω')）

![snow_loading.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/539403/60ce8fc8-1d4e-6452-80e7-c7e106cc99bc.gif)

## まとめ

クリスマス間近と言う事で、 loading 中に雪を降らせてみました。
loading すると重くなります。
本記事であまり触れていないですが、 React Suspense に関しても、「なるほど」と言う感想を得る事ができました。
スキルに関しても、これはいかんなと思えたのでもっと勉強しないといけないですね。

そもそも、雪が積もるほどのパフォーマンスを出してはいけない。( 'ω')

明日は @KensukeTakahara さんの投稿です！
React, TypeScript の投稿らしいです。楽しみですね！（直前に変な投稿ですみません。。）