LoginSignup
10
0

More than 3 years have passed since last update.

待ち時間に雪を降らせたい(React)

Posted at

はじめに

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

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

概要

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

構成

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

実際にやってみた

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

デモ用に枠を作る

// 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に固定します。
Suspensefallback には SnowLoading コンポーネント(後述)を指定します。

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

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

// 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

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

本題の雪を作っていく

雪は position:absolute で降らせていきます。

// 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「❄」 です。

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

// 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>
)

やってはいけなそうな匂いがしますが、今回は目を瞑りましょう( ˘ω˘ )
これである程度ランダムに30個の雪結晶を降らせます。
実行したものがこちらです。(容量圧縮したので荒いです)
snow_animation-compressor.gif

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

雪を積もらせていく

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

// 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

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

Loading っぽさを追加する

// 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

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

成果物

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

snow_loading.gif

まとめ

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

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

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

10
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
0