2020年2月25日

CSS&JavaScriptでクリック時のアニメーション


クリックした要素をアニメーションさせる方法をメモメモ…


マウスの動作に応じてページ内の要素をアニメーションさせたい!というときの実装方法をメモしておきます。要素がボタンとかであれば CSS の疑似クラスだけで実装できるかもしれませんが、今回は普通のdiv要素を対象にするため JavaScript を使います(jQueryその他のライブラリは使いません)。ブラウザ環境によっては動作しないこともあるので注意

まずはシンプルな例です。クリックすると「ぴょこん」と飛び跳ねます。

directions_walk
HTML
<div id="button">飛び跳ねさせたい要素</div>
CSS
#button
{
  display: inline-block;
  cursor: pointer;
}
#button.jump
{
  animation: 0.4s cubic-bezier(.2,1,.2,1) 0s 1 jump;
}
@keyframes jump
{
  0%, 100% { transform: translateY(0px); }
  50% { transform: translateY(-40px); }
}
JavaScript
const button = document.getElementById("button");
button.addEventListener("mousedown", () => {button.classList.add("jump");});
button.addEventListener("animationend", () => {button.classList.remove("jump");});
button.addEventListener("animationcancel", () => {button.classList.remove("jump");});


要素がクリックされたらクラスを追加。1回だけアニメーションが再生されるようにしておきます。再生が完了(または中止)したらクラスを外して、またクリックされたら追加…を繰り返します。CSS の animation の指定が割とややこしいので、書き方をよく確認しましょう(自分も間違ってるかも…)。

さらにもう少し手を加えるとこんな感じになります。

directions_bike

CSS
#button
{
  display: inline-block;
  cursor: pointer;
}
#button.wobble
{
  animation: 0.1s ease-in 0s infinite wobble;
}
@keyframes wobble
{
  0%, 100% { transform: translateY(0px); }
  50% { transform: translateY(2px); }
}
#button.jump
{
  animation: 0.7s ease-in 0s 1 jump;
}
@keyframes jump
{
  0% { transform: translateY(0px) rotateZ(0deg); }
  10% { transform: translateY(-10px) rotateZ(-20deg); }
  35% { transform: translateY(-40px) rotateZ(-20deg); }
  70% { transform: translateY(0px) rotateZ(-16deg); }
  85% { transform: translateY(-14px) rotateZ(-8deg); }
  100% { transform: translateY(0px) rotateZ(0deg); }
}
JavaScript
function startJump(){
 button.classList.remove("wobble");
 button.classList.add("jump");
}
function stopJump(event){
 if(event.animationName === "jump"){
  button.classList.remove("jump");
  button.classList.add("wobble");
 }
}
const button = document.getElementById("button");
button.classList.add("wobble");
button.addEventListener("mousedown", startJump);
button.addEventListener("animationend", stopJump);
button.addEventListener("animationcancel", stopJump);


なんだかややこしそうですが、要は「"wobble" と "jump" を切り替える」という単純なことをやっているだけです。ただしそのままだと animationend イベントが "wobble" アニメーションの再生終了時にも実行されてしまうので、stopJump() 関数内でif(event.animationName === "jump")と書いてはじいています。

上記の2つのパターンではアニメーションの再生が終了するまでクリックに反応しませんでしたが、次の例ではクリックするたびにアニメーションが最初から再生されます。

new_releases
CSS
#button
{
  display: inline-block;
  cursor: pointer;
}
#button.jump
{
  animation: 0.4s ease-in 0s 1 jump;
}
@keyframes jump
{
  0% { transform: translateY(0px) rotateZ(0deg); }
  30% { transform: translateY(-30px) rotateZ(10deg); }
  35% { transform: translateY(-30px) rotateZ(-30deg); }
  45% { transform: translateY(-30px) rotateZ(30deg); }
  55% { transform: translateY(-30px) rotateZ(-30deg); }
  65% { transform: translateY(-30px) rotateZ(30deg); }
  70% { transform: translateY(-30px) rotateZ(10deg); }
  100% { transform: translateY(0px) rotateZ(0deg); }
}
JavaScript
const button = document.getElementById("button");
button.addEventListener("mousedown", () => {
 button.classList.remove("jump");
 void button.offsetWidth;
 button.classList.add("jump");
});


連打するとその都度アニメーションがリセットされます。ポイントはvoid button.offsetWidth;という部分。通常 classList.remove() と classList.add() でクラスを付け外しするだけではアニメーションは再生されません。そこで offsetWidth にアクセスすることで強制的にリフロー(レイアウトの再計算)を発生させ、アニメーションをリセットさせている、というわけです。詳しい内容は次のページが参考になります。

How To Restart a CSS Animation With JavaScript - Better Programming - Medium
https://medium.com/better-programming/how-to-restart-a-css-animation-with-javascript-and-what-is-the-dom-reflow-a86e8b6df00f

もしvoid button.offsetWidth;でうまく動作しない場合はbutton.offsetWidth = button.offsetWidth;という書き方にしてみると機能するかもしれません。それでもダメなら「まったく同じ要素を新しく複製して古いほうを削除する」という方法もあります。

Restart CSS Animation | CSS-Tricks
https://css-tricks.com/restart-css-animation/

ただ、正直どちらの方法にしてもちょっと裏技的というか…あまり綺麗な方法じゃない気もします。特にリフローは場合によってはパフォーマンスに影響が出る可能性もありますし…。もっとシンプルな方法があれば知りたいです。
filter_vintage
filter_vintage
filter_vintage
filter_vintage
filter_vintage
filter_vintage
filter_vintage

0 件のコメント:

コメントを投稿