これはアドベントカレンダー7日目の記事です。
23MのNapoliNです。グラフィック班に所属してます。
さて、今回の記事は「CSSでドット絵アニメーションを描く」です。
他の部員のかっこいいポートフォリオサイトに憧れて、自分もかっこいいのを作りたい!と思ったので頑張って書きました。
完成したドット絵アニメーションは以下で確認できます。
書いたコードはこの辺りに転がってます。整理してなくてごめなさい。
動き
今回作成するアニメーションは以下のようなものです。
- タイルが回転してテックちゃんが登場する(RotateTech)
- テックちゃんが歩いて、歩いた跡にロゴが出てくる(WalkingTech)
- ロゴが出きったらテックちゃんが立ち止まる(IdleTech)
それぞれ括弧内で名前をつけました(githubに挙げてるファイル名と対応してます)。3つのアニメーションを組み合わせることで良い感じにします。
描き方 RotateTech
まず登場シーンから行きます。こんな感じでじわじわと表示される!みたいなアニメーションを描きます。
書いたコードは以下の通り。可読性、保守性を少しでも上げるためにSCSSを使って書いてます。
.rotate-tech-position {
position:absolute;
top: calc(50vh - (var(--scale))* 48);
left: calc(50vw - (var(--scale) * 32) - (var(--scale) * 64));
}
.rotate-tech{
width: var(--scale);
height: var(--scale);
position: relative;
}
$bmps : 32 18 #1f4ea2,33 18 #1f4ea2,34 18 #1f4ea2,31 19 #1f4ea2,32 19 #639bff,33 19 #3a6cc7,34 19 #1f4ea2,28 20 #1f4ea2,29 20 #1f4ea2,30 20 #1f4ea2,31 20 #1f4ea2,32 20 #639bff,33 20 #1f4ea2,34 20 #1f4ea2,35 20 #1f4ea2,27 21 #1f4ea2,28 21 #639bff,29 21 #639bff,....(省略);
@keyframes delay-visible {
0% {
visibility: hidden;
}
100% {
visibility: visible;
}}
@keyframes rotate-one {
0% {
transform: rotateY(270deg);
}
100% {
transform: rotateY(360deg);
}}
@keyframes hide {
0% {
visibility: visible;
}
100% {
visibility: hidden;
}}
$i: 0;
@each $x, $y, $color in $bmps {
.rotate-tech-#{$i} {
top: calc($y * var(--scale));
left: calc($x * var(--scale));
background-color: $color;
animation:
delay-visible calc(#{$i}* 0.005s) step-end 0s 1,
rotate-one 0.5s ease-in calc(#{$i}* 0.005s) 1,
hide 0s step-end var(--logo-gen-start) 1 forwards;
}
$i : $i + 1}
var変数は上位コンポーネントで定義されているグローバルな変数です。
RotateTechは3つのアニメーションの組み合わせで表現されています。
delay-visible
: タイルの出現時間の調整rotate-one
: タイルの出現時の回転hide
: 非表示にする時間
ピクセルごとに違うアニメーションをしなければならないので、四角い<div>
要素をいっぱい並べて実装します。
html側のコードは大体こんな感じです。(React使ってるので実際にはfor文で生成したりしてます。数はハードコーディング)
<div className="rotate-tech-position">
<div className="rotate-tech">
<div className="rotate-tech-0 tech-gen"></div>
<div className="rotate-tech-1 tech-gen"></div>
<div className="rotate-tech-2 tech-gen"></div>
...
<div className="rotate-tech-655 tech-gen"></div>
</div>
</div>
rotate-tech-{i}
がi
番目のピクセルを表します。
ピクセルの配置情報は$bmps
に並べてます(かなり長いので省略)。描いたドット絵をopenCVで読み込んで座標とカラーコードを順番に出力させれば作れます。
「x座標 y座標 colorcode」の列で構成されており、scssのfor構文を使ってそれぞれの配置・色を弄っていきます。
$i: 0;
@each $x, $y, $color in $bmps {
.rotate-tech-#{$i} {
top: calc($y * var(--scale));
left: calc($x * var(--scale));
background-color: $color;
animation:
delay-visible calc(#{$i}* 0.005s) step-end 0s 1,
rotate-one 0.5s ease-in calc(#{$i}* 0.005s) 1,
hide 0s step-end var(--logo-gen-start) 1 forwards;
}
$i : $i + 1
}
scssにはeach構文があり、C++でのfor eachみたいなことができます。indexが必要なので、変数$i
を外側で宣言しました。
top
とleft
でピクセルの位置を調整して、background-color
で色を指定します。
一枚ずつめくれるようになってほしいので、各アニメーションに$i
に応じたdelayをかけます。
回転アニメーションでは、少しずつ見えるようになってほしいので、270deg~360degで回転させます。
描き方 WalkingTech
以下の歩行アニメーションをCSSで描きます。
このアニメーションは次の4枚の画像から構成されています。
結論から言うと4つのdiv要素を動かしながら順番に表示させてるだけです。
最終的なscssは以下の通り。
$tech1_bmps : 32 18 #1f4ea2,33 18 #1f4ea2,34 18 #1f4ea2,31 19 #1f4ea2,32 19 #639bff,33 19 #3a6cc7,34 19 #1f4ea2,28 20 #1f4ea2,29 20 #1f4ea2,30 20 #1f4ea2,31 20 #1f4ea2,32 20 #639bff,33 20 #1f4ea2,34 20 #1f4ea2,35 20 #1f4ea2,27 21 #1f4ea2,28 21 #639bff,29 21 #639bff,30 21 #1f4ea2,...;
$tech2_bmps : 32 19 #1f4ea2,33 19 #1f4ea2,34 19 #1f4ea2,31 20 #1f4ea2,32 20 #639bff,33 20 #3a6cc7,34 20 #1f4ea2,28 21 #1f4ea2,29 21 #1f4ea2,30 21 #1f4ea2,31 21 #1f4ea2,32 21 #639bff,33 21 #1f4ea2,34 21 #1f4ea2,35 21 #1f4ea2,27 22 #1f4ea2,28 22 #639bff,29 22 #639bff,30 22 #1f4ea2,...;
$tech3_bmps : 32 18 #1f4ea2,33 18 #1f4ea2,34 18 #1f4ea2,31 19 #1f4ea2,32 19 #639bff,33 19 #3a6cc7,34 19 #1f4ea2,28 20 #1f4ea2,29 20 #1f4ea2,30 20 #1f4ea2,31 20 #1f4ea2,32 20 #639bff,33 20 #1f4ea2,34 20 #1f4ea2,35 20 #1f4ea2,27 21 #1f4ea2,28 21 #639bff,29 21 #639bff,30 21 #1f4ea2,31 21 #1f4ea2,32 21 #639bff,33 21 #639bff,34 21 #639bff,35 21 #1f4ea2,...;
$tech4_bmps : 32 19 #1f4ea2,33 19 #1f4ea2,34 19 #1f4ea2,31 20 #1f4ea2,32 20 #639bff,33 20 #3a6cc7,34 20 #1f4ea2,28 21 #1f4ea2,29 21 #1f4ea2,30 21 #1f4ea2,31 21 #1f4ea2,32 21 #639bff,33 21 #1f4ea2,34 21 #1f4ea2,35 21 #1f4ea2,27 22 #1f4ea2,28 22 #639bff,29 22 #639bff,30 22 #1f4ea2,...;
$tech_bmps : $tech1_bmps, $tech2_bmps, $tech3-bmps, $tech4_bmps;
.walking-tech-position {
position: absolute;
top: calc(50vh - (var(--scale))* 48);
left: calc(50vw - (var(--scale) * 32) - (var(--scale) * 64));
}
.walking-tech {
position: relative;
}
.walking-tech > * {
visibility: hidden;
}
@function scale_bmps($s,$bmp){
$list : ();
@each $x, $y, $bmp in $bmp {
$list : append($list, calc($x * $s) calc($y * $s) $bmp, 'comma')
}
@return $list;
}
$move-step : 70;
@for $i from 1 through 4 {
@keyframes moveAnimation#{$i} {
$n : calc(100% / $move-step);
$j : 0;
@while $n * $j <= 100% {
#{$n * ($j + $i - 1)} {
visibility: visible;
transform: translateX(calc(var(--scale) * ($i + $j) * 2));
}
#{$n * ($j + $i)} {
visibility: hidden;
}
$j : $j + 4;
}
100% {
visibility: hidden;
}
}
.walking-tech-#{$i} {
box-shadow: scale_bmps(var(--scale), nth($tech_bmps, $i));
animation : moveAnimation#{$i} calc(var(--logo-gen-length) + 1s) step-end var(--logo-gen-start) 1 forwards;
}
}
例のごとくbmp情報は省略しました。
歩行アニメーションは次のように構成できます。
- 全部消す
- 1を0pxずらして表示
- 2を1pxずらして表示、1を消す
- 3を3pxずらして表示、2を消す
- 4を4pxずらして表示、3を消す
- 1を5pxずらして表示、4を消す
- ...繰り返し
頑張ってハードコードして書けばいいのですが、それでは芸がないので歩行step数をパラメータにできるようにします。
for構文を使って4枚をそれぞれ描画+アニメーションさせます。
@keyframe
構文ではパーセントを使ってアニメーションを記述します。動きをmovestep
数で分割したいので、1フレーム辺りに進むパーセンテージは$n : calc(100% / $move-step);
で表記できます。
使うべきパーセンテージが分かったので、各フレームでの状態を記述していきます。
@while $n * $j <= 100% {
#{$n * ($j + $i - 1)} {
visibility: visible;
transform: translateX(calc(var(--scale) * ($i + $j) * 2));
}
#{$n * ($j + $i)} {
visibility: hidden;
}
$j : $j + 4;
}
4フレームごとに動かして表示、次のフレームで消すという処理です。$i
がイメージ番号なことに留意すると、常に別のイメージが1つだけ表示されている状態になることが分かります。
さて、これで歩行アニメーションは完成です。
最後に、イメージをbox-shadowを用いて描画します。ここはRotateTech同様div要素をいっぱい並べても構わないのですが、美しさを重視してbox-shadowを使います。
box-shadowを用いたドット絵描画は様々な参考サイトがあるので、そちらをご覧ください。
私は以下のページを参考にしました。
https://kuroeveryday.blogspot.com/2018/10/draw-and-animate-pixel-art-using-only-css.html
まとめ
CSSでドット絵アニメーションを作る方法を紹介しました。ドット絵はやっぱりかわいいですね。ちなみに、レスポンシブ対応するために、グリッドの大きさなど様々な変数でパラメータ化できるようにしました。
明日は@ikura-hamuさんと@ramdosさんの記事です。:tanosimi-: