はじめに
こんにちは @hey3 です。
この記事はACCESS Advent Calendar 2019 23日目の記事です。
Qiita に記事を投稿するのが初めてなので緊張しています。。
今回の記事はクリスマス間近と言う事で、ただただやってみたかった事をやっているだけです。(実用性皆無です)(ネタです)
※ネタで1枠使ってしまって申し訳無い気持ちはあります。
概要
クリスマス近いし、非同期の更新処理中にコンポーネントに雪を降らせたい
(本来待ち時間は無い方が良いんですけどね。。)
構成
サクッと非同期処理を用意するために React v16.6
以降に追加された lazy
と Suspense
を用いました。
lazy
と Suspense
に関しては深く触れないため、知りたい場合は 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に固定します。
Suspense
の fallback
には 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 />
)
寒さが伝わってきて如何にも雪が降ってそうな見た目ですね( 'ω')
本題の雪を作っていく
雪は 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
を使うとやりやすいですね。
ちなみに、今回使用した雪結晶はこちら ❄「❄」 です。
実際に雪を生成してみます。
先ほどの 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個の雪結晶を降らせます。
実行したものがこちらです。(容量圧縮したので荒いです)
おー、雪が振りましたね。( 'ω')
満足です。
ただ、これだと非同期処理中だと言う事がわかりずらいですね。
雪を積もらせていきます。
雪を積もらせていく
こんなもんで良いでしょう
// 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 />
)
才能が無く向いてなさそうな感じがしますね。。
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 on loading みたいでダサさが際立ちました。( 'ω')
成果物
画質も出来も荒いですが、このようなものを作成しました。(もはや雪があまり見えないですね( 'ω'))
まとめ
クリスマス間近と言う事で、 loading 中に雪を降らせてみました。
loading すると重くなります。
本記事であまり触れていないですが、 React Suspense に関しても、「なるほど」と言う感想を得る事ができました。
スキルに関しても、これはいかんなと思えたのでもっと勉強しないといけないですね。
そもそも、雪が積もるほどのパフォーマンスを出してはいけない。( 'ω')
明日は @KensukeTakahara さんの投稿です!
React, TypeScript の投稿らしいです。楽しみですね!(直前に変な投稿ですみません。。)