クモのようにコツコツと

フロントエンドエンジニア イイダリョウのブログ。略称「クモコツ」

CSSドット絵マリオをCSSアニメでジャンプさせてみる

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つを.charadiv要素で囲います。

この.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に設定
  • .spaceposition: relativeにして.charaの基準位置とする
  • .space:背景色(水色)と地面(border-bottom)の色(茶色)を設定
  • .charaposition: 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で地面に着地する)

from0%to100%でも同義です。調節した結果、ジャンプの頂点で静止時間を作りました。先ほど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.charamario_j.mario_jmario_s.mario_s
  • ジャンプポーズmario_jCSSを非表示(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);
  • charaCSSアニメ終了時(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を使います。実現にあたり、音階を滑らかに上げるポルタメント設定の方法を調べる必要があります。それではまた!