Qiitaアドベントカレンダー「CSS」 23日目の記事です。イブイブ!
さて、CSSドット絵マリオシリーズ、前回のあらすじ。グラフィックソフトを使わずCSSオンリー打つべし打つべし!でドット絵を描いた(書いた?)マリオ。ホバーアクションで立ちポーズとジャンプポーズを切り替えることに成功したとさ。今回はマリオをCSSアニメで滑らかにジャンプさせたい。発火は擬似クラスをいくつか試したがどれも一長一短あり。最終的にはJSの力を借りてやりたい動きを実現できた。それでは、いきまShow!
【目次】
※参考:前回記事
【CSSドット絵】マリオ立ちポーズとジャンプポーズの切り替え - クモのようにコツコツと
ジャンプするための空間を作る
まずは前回の状態。
カーソルホバーでマリオが立ちポーズ、ジャンプポーズに切り替わります。(スマホではもう一回マリオ以外のスペースを叩かないと戻らない)
このままジャンプすると頭を強打するため、何は無くとも空間が必要です。まずHTMLを変えます。
<div class="space"> <div class="chara"> <ul class="mario mario_j"> <li><img><img><img><img><img><img><img><img><img><img><img><img><img><img><img><img></li> <!-- 中略(果てしないドットたち)--> </ul> <ul class="mario mario_s"> <li><img><img><img><img><img><img><img><img><img><img><img><img><img><img><img><img></li> <!-- 中略(果てしないドットたち)--> </ul> </div> </div>
- 前回はclass
.space
の直下に.mario_j
(ジャンプ)と.mario_ s
(立ち)のul
要素がありました。 - この2つを
.chara
のdiv
要素で囲います。
この.space
の空間の中でキャラ.chara
を地面に配置したい。次、CSSです。
.space { width: 100%; height: 400px; position: relative; background: #a0adfe; border-bottom: 30px solid #DF7D00; } .chara { position: absolute; bottom:0; left: 25%; }
.space
:幅を100%、高さは400pxに設定.space
:position: relative
にして.chara
の基準位置とする.space
:背景色(水色)と地面(border-bottom
)の色(茶色)を設定.chara
:position: absolute;
にして.space
との相対位置を設定.chara
:地面(bottom
)からの位置0
、左left
からの位置25%
下記のようになりました。
空間の中でマリオを地面の少し左側に立っている。ホバーアクションは保持されていますね。(なお、今回は地面はブロックを打つ余力がなくただの茶色ですw)
CSSループアニメ
マリオをアニメーションさせたい。以前にも触れたCSSアニメを使います。transitionとanimationの2つの方法があり、それぞれ下記の記事で解説しています。
※参考:CSS transition
スクロールするとフワッと現れたり動いたりするアニメーション【jquery.inview.js & transition】 - クモのようにコツコツと
※参考:CSS animation
【CSS】background-clip: textで背景を文字形に切り抜いてさらにグラデのアニメにする - クモのようにコツコツと
シンプルな設定はtransition、複雑な設定はanimationといった使い分けになります。
今回はマリオがワンアクションの中でいったん上に行った後に重力の法則に従ってまた地面に着地したい。こうした設定はanimationを使います。
.chara { position: absolute; bottom:0; left: 25%; animation-duration: 0.8s; animation-name: jump; animation-iteration-count: infinite; animation-timing-function: ease-in; }
.chara
にanimation設定を追記animation-duration
はアニメのスピード(いくつか試して0.8秒0.8s
にした)animation-name
でキーフレーム設定を読み込み(キーフレーム名はjump
)animation-iteration-count
は繰り返す回数(infinite
は回数無制限のループアニメ)animation-timing-function
はアニメの加減速(ease-in
は入りがゆっくり)
キーフレームの値は別途設定します。ここでは名前だけ設定。jump
としました。
animation-timing-function
の初期値はlinear
で加減速なし。
※参考:animation-timing-function-CSS3リファレンス
先ほどのキーフレームjump
を設定します。
@keyframes jump { from { bottom:0; } 45% { bottom: 150px; } 55% { bottom: 150px; } to { bottom: 0; } }
@keyframes
でキーフレーム設定(キーフレーム名jump
)from
は開始時の設定(position
の下bottom
から0pxで地面に立つ)- 45%〜55%の10%間を
bottom
から150px
に(ジャンプの頂点に静止時間を作る) to
は終了時の設定(bottom
から0pxで地面に着地する)
from
は0%
、to
は100%
でも同義です。調節した結果、ジャンプの頂点で静止時間を作りました。先ほどanimation-timing-function: ease-in
と合わせるとゆっくりジャンプして、頂点で一瞬止まった後に加速して着地する、というアニメになります。
できました!マリオがジャンプ!
うーむ。ジャンプしたのはいいですが、PC、SP(スマホ)ともに下記の課題が残ります。
- PC、SP:前回設定したホバーとは設定が分離されているため、立ちポーズのままジャンプし続ける
- PC:ホバーするとジャンプポーズになるが、今後はジャンプポーズのままジャンプし続ける
- SP:タッチするとジャンプポーズになる。他の場所をタッチすると立ちポーズになる。
ジャンプ設定とホバーのポーズ変更が分離されているわけです。
擬似クラス:hoverでホバーアクション
ポーズ変更と同様にジャンプの方もホバーで発火させたい。CSS animationをこのように変えてみた。
.chara { position: absolute; bottom:0; left: 25%; } .chara:hover { animation-duration: 0.8s; animation-name: jump; animation-timing-function: ease-in; }
.chara
の擬似クラス.chara:hover
を作る.chara
はポジション設定のみに。CSS animation設定は.chara:hover
に(ホバーで発火する)animation-iteration-count
は削除(ループ設定なし、1回のみ実行)
こうなりました。
マリオにホバーするとジャンプします!ただし、PC、SP(スマホ)でそれぞれ下記の課題が残ります
- PC:ジャンプ中にホバーアウトするとパッと地面に戻ってしまう。
- PC:ホバーしっ放しだと、着地したあともジャンプ姿勢のまま
- SP:タッチするとジャンプしてくれるが、着地後に他の場所をタッチしないと立ちポーズに戻らない
うーむ、惜しい。。
擬似クラス:activeでクリックアクション
ホバーではなくクリックで発火させたい。CSSの擬似クラスはいくつかありますが、クリックで使えそうなのは:active
です。
※参考::active擬似クラス-スタイルシートリファレンス
ユーザーの操作で要素がアクティブになった際のスタイルを指定します。 a:activeでは“クリックされてから離されるまでの状態”です。
ふむ。「クリックされてから離されるまで離されるまで」というのが少し気になるが、とりあえずやってみる。
.chara:active { animation-duration: 0.8s; animation-name: jump; animation-timing-function: ease-in; }
.chara:hover
を.chara:active
に変更
ここで動作検証をしたところスマホ(SP)の方に根源的な問題が。タッチしてもジャンプが全く発火しない!active:
ってPC限定の機能なのだろうか?調べたらこんな記事が!
※参考: 【CSS】スマホでhover・activeが効かない?わずか2分の対処法! | 静岡発のホームページ制作・集客・映像制作のIHOLDINGS
『ontouchstart属性を使うだけ』!これだけです!
さっそく入れてみる。
<div class="space"> <div class="chara" ontouchstart=""> <ul class="mario mario_j"> <li><img><img><img><img><img><img><img><img><img><img><img><img><img><img><img><img></li> <!-- 中略(果てしないドットたち)--> </ul> <ul class="mario mario_s"> <li><img><img><img><img><img><img><img><img><img><img><img><img><img><img><img><img></li> <!-- 中略(果てしないドットたち)--> </ul> </div> </div>
. chara
要素にontouchstart
属性を追加。値は空でOK
こうなった!
マリオがクリックでジャンプします!が、、ここでもまだちょっと課題が残ります。
- PC:ホバーしてもポーズが変わるだけ。クリックするとジャンプし始めるがすぐ着地する。
- PC:マリオを押し続ける間のみジャンプを開始して着地する。ホバーのポーズ変更とは動きが分離している。
- SP:マリオを押し続ける間のみジャンプを開始して着地する。着地後も他の場所をタッチしないとジャンプポーズのまま。
うーむ、やはり「クリックされてから離されるまで」という設定がネックになりました。あとポーズ変更のホバーとクリックのジャンプが分離しているのも違和感。
JSのイベントリスナでclass追加、削除
ジャンプもポーズ変更も両方クリックで発火させたい。また、押し続けなくてもいいよう、押すだけで発火させたい。
擬似クラスでは結局のところ難しそうだったのでJSで実現しました。イベントリスナで発火させてclass名を追加・削除します。
初期設定
まず、CSS擬似クラスはやめて.chara_jump
というクラスにします。
.chara_jump { animation-duration: 0.8s; animation-name: jump; animation-timing-function: ease-in; } /* 削除 .chara:hover .mario_j { display: block; } .chara:hover .mario_s { display: none; } */
.chara:active
を.chara_jump
に変更(初期のHTMLの要素には存在しないclass名).mario_j
と.mario_s
に設定していたホバー:hover
の表示設定display
を丸ごと削除
ホバーでのポーズ変更設定がなくなるのでマリオの立ちポーズ、ジャンプポーズともに画面に表示されます。ジャンプポーズの非表示はCSSではなくJSで行います。
まず、JSの初期設定を行います。
chara = document.querySelector(".chara"); mario_j = document.querySelector(".mario_j"); mario_s = document.querySelector(".mario_s"); //ページ読み込み時 mario_j.style.display="none";
- DOM要素を変数に代入(
chara
に.chara
、mario_j
に.mario_j
、mario_s
に.mario_s
) - ジャンプポーズ
mario_j
のCSSを非表示(display="none"
)に
処理の中でDOMを繰り返し指定するので変数に入れます。そしてページ読み込み時に立ちポーズを非表示にします。
クリック時のイベント(マリオをジャンプさせる)
次にイベントリスナでクリックイベントを設定します。マリオをジャンプさせます。
//イベント(ジャンプ) chara.addEventListener('click', function() { chara.classList.add('chara_jump'); mario_j.style.display="block"; mario_s.style.display="none"; }, false);
chara
をクリックclick
した時にイベントが発生(addEventListener
)chara
に.chara_jump
クラスを追加(classList.add()
関数)- ジャンプポーズ
mario_j
を表示(display="block"
)に - 立ちポーズ
mario_s
を非表示(display="none"
)に
classList.add()
関数でクラスを追加できます。先ほどの.chara_jump
を追加します。
CSSアニメ終了後のイベント(マリオ着地後立ちポーズに戻す)
次にCSSアニメ終了後のイベント設定します。CSSアニメの終了を完治してイベントを実行します。
※参考:CSSアニメーションの終了をJavaScriptで検知する - みかづきブログ その3
トランジションの際はtransitionend、キーフレームアニメーションの際はanimationendをつかえばOKです。
今回はCSS animationなのでanimationend
の方ですね。
//イベント(着地後) chara.addEventListener('animationend', function() { chara.classList.remove('chara_jump'); mario_j.style.display="none"; mario_s.style.display="block"; }, false);
chara
のCSSアニメ終了時(animationend
)にイベントが発生(addEventListener
)chara
に.chara_jump
クラスを削除(classList.remove()
関数)- ジャンプポーズ
mario_j
を非表示(display="none"
)に - 立ちポーズ
mario_s
を表示(display="block"
)に
classList.remove()
関数でクラスを削除できます。先ほど追加した.chara_jump
を削除します。そしてジャンプポーズを非表示、立ちポーズを表示にして最初の状態に戻します。
さあ、どうなるか?
おお、PCではクリック、スマホ(SP)ではタッチによってマリオがジャンプ!そして着地後、マリオが立ちポーズに戻ります。これでようやくどちらも希望通りの動きが実現できました!!
最後に
擬似クラスを試してみたもののPC、スマホともに満足できるような動きは作れませんでした。JSのイベントリスナが一番望み通りの設定ができます。
- アニメの動きはCSS animationでOK
- 擬似クラス
:hover
はPCではホバー外すまで、スマホでは他の場所をタッチするまでジャンプポーズのまま - 擬似クラス
:active
ではPC、スマホともに押し続けている間だけジャンプする(着地後もジャンプポーズのまま) - JSのイベントリスナならばPCのクリック、スマホのタッチで発火させることができる
動きはCSS、イベントはJS、という使い分けが今のところのベターな方法のようです。(:click
みたいな擬似クラスがあれば解決なんだけどなー…)
さて、次はマリオをクリックした時に「トゥ〜ン」という音を追加したい。Web Audio APIを使います。実現にあたり、音階を滑らかに上げるポルタメント設定の方法を調べる必要があります。それではまた!