こんにちは。マークアップエンジニアのやまともです。

実際にソードアート・オンラインがあったら、挑戦してみたいと願っている毎日です。
自分の身体を使って、ときにはモンスターをバシバシ倒して、ときには街でのんびりしてといった生活を一度は送ってみたいと思っております。

以前、「公式サイトに見る話題のアニメーション」という記事でご紹介した、ソードアート・オンラインのWEBサイトで使われているアニメーションに注目してお話ししていきます。

ソードアート・オンライン

今回注目した箇所は、ソードアート・オンラインのWEBサイトのトップページ右側で利用されている、マウスオーバー時に画像の大きさを変化させるアニメーションです。
社内の案件ではあまりこういったマウスオーバーした時に横幅を変化させるアニメーションを実装する機会がない為、jQueryとCSSを使ってサンプルを作成してみました。

STEP1. アニメーションなしで実装してみる。

要素を6個横並びに配置してそれぞれの横幅を10%、そして、1個目の最初の横幅をjQueryを使って50%にしています。
マウスオーバーした要素以外のものは10%、マウスオーバーした要素は50%に変更します。
この場合の$(this)はマウスオーバーしたdivを示しています。
これは、わざわざスクリプトを使わなくてもCSSの疑似セレクタの:hoverを使えば横幅を変えることが出来ますね。

STEP1のソースコード

$function(){
    //マウスオーバーしたとき
    $('#all div').hover(function(){
        //一度divの横幅を10%にする
        $('#all div').width('10%');
        //マウスオーバーした要素の横幅を50%にする
        $(this).width('50%');
    });
});

STEP2. 横幅の変化をアニメーションさせる。

単純に、サンプル1のwidth()の部分をanimate()の書式で横幅を変更するように書き換えてました。
すると、マウスオーバーした要素の横幅が50%になるまでアニメーションし、それ以外の横幅が50%の要素は10%になるまでアニメーションするようになりました。
ですが、この状態だと横幅が50%の要素にマウスオーバーした場合、一度横幅10%になるまでアニメーションしてから50%になってしまい、更には、アニメーションが終わったあと他の要素にマウスオーバーしたときに続けて次のアニメーションが開始されてしまい表示崩れや、マウスカーソルを要素に重ねていた分だけアニメーションを繰り返してしまいます。

STEP3. 横幅50%の要素にマウスオーバーしたときはアニメーションさせない。

STEP2で作成したソースコードにマウスオーバーした要素(サンプルでは$(this)の部分)にclass名(サンプルではon)を追加します。
if分を使って条件分岐し、指定したclass名(サンプルではon)を持っていない要素に対してマウスオーバーしたときだけ処理を実行するようにすることで、横幅が50%の要素にマウスオーバーしたときはアニメーションが実行されなくなりました。
ですが、このままだとclass名(サンプルではon)を持っている要素が次々に増えていき、いずれアニメーションしなくなってしまいました。
マウスオーバーしてアニメーションを実行するタイミングでclass名を一度外すことで、class名(サンプルではon)が増えていくことを防ぐことができました。
ただし、この状態ではまだアニメーション中に他の要素にマウスオーバーすると連続してアニメーションが実行されてしまい、表示崩れも起きてしまいます。

STEP4. アニメーションを連続で実行させない。

このマウスオーバーのアニメーションを作成したときに一番はまったポイントです。
animate()の後ろに.stop()を書いてアニメーションを止めようにも、同時に二つのアニメーションが実行されているため、中途半端な横幅でアニメーションが停止してしまいます。
DEMO社員のプログラマーに相談したところ、「マウスオーバーしたときと、アニメーションが終わった時点でフラグを切り替えたら連続してしまうアニメーションを止められる!」という回答をもらいました。
実際に、$flagという変数を用意して初期値をfalseに設定し、if文の条件に$flagがfalseだった場合に処理を実行するように追記します。
これで、マウスオーバーした要素が指定したclass名を持っていない、且つ、$flagがfalseのときだけアニメーションが開始されるようになります。
あとは、条件が成立したときに$flagをtrueに変更して、横幅が50%になるアニメーションが終わったタイミングで$flagをfalseに戻してあげれば、繰り返し実行されるアニメーションを防ぐことが出来ます。
flagってすごい!ブール値万歳!

なぜ、フラグを入れる前はアニメーションが連続してしまっていたのかというと…
アニメーションしている途中、マウスカーソルが他の要素にぶつかりマウスオーバーをしたという扱いになってしまっていたようです。
そうなっている状態をflagを入れてあげることによって、アニメーションしている間はtrueとなっている為、if文の条件が成立しなくなり処理が実行されなくなります。
マウスオーバーによるアニメーションが完了した時はflagがfalseに戻されるため、他の要素にマウスオーバーしたときは次のアニメーションが実行されるようになります。

STEP2からSTEP4までのソースコード

$(function(){
    //フラグの初期値を設定する
    $flag = false;
    $('#all div').hover(function(){
        //マウスオーバーした要素がonというclassを持っていて、フラグがfalseだったときにアニメーションを実行させる
        if(!$(this).hasClass('on') && !$flag){
            //条件が成立したらフラグをtrueに変更する
            $flag = true;
            //onというclassを外す
            $('.on').removeClass('on');
            $('#all div').animate({width:'10%'},200,'swing');
            $(this).addClass('on').animate({width:'50%'},200,'swing',function(){
               //横幅が50%になるアニメーションが終わったら、フラグをfalseにする
               $flag = false;
            });
        }
    },function(){
         return false;
    });
});

番外編. CSS3と組み合わせてみる

まだまだ、IE7,8への対応も必要ではありますが、sample2を作成しているときにずっと思っていたことがあります。
「CSS3だったらあまりはまることなくサクサクっとできるのになぁ…」ということで、CSS3とjQueryを組み合わせて書いてみました。

まずは、それぞれの横幅を変えたい要素にCSSでwidthとtransitionプロパティを指定して、マウスオーバーしたときにwidthを変えてあげるだけです。
あとは、jQueryを使って要素からマウスカーソルが離れたときに、マウスオーバーしていた要素にwidthを変えるclassを付けてあげます。
ただ、classをつけっぱなしにしてしまうと、次に別の要素へマウスオーバーしたときにそのままclassが残ってしまっているので、もともとの横幅に戻らなくなってしまいます。
なので、マウスオーバーしたときにはwidthを変えるclassを外してあげましょう。

番外編のCSS

#all{
  width: 100%;
  overflow: hidden;
  margin: 0 auto;
}
#all div{
  width: 10%;
  max-width: 50%;
  float: left;
  transition: 0.2s ease-in;
}
/* マウスオーバーしたときに最優先でかかるCSS */
#all div:hover{
  width: 50% !important;
}
/* jQueryでマウスオーバーした要素につけるclass */
.current{
  width: 50% !important;
}

番外編のソースコード

$(function(){
    $('#all div').hover(function(){
        //currentというclassを持っている要素からcurrentを外す
        $('.current').removeClass('current');
        //要素の横幅を10%に変更するが、疑似セレクタが!importantになっているので、マウスオーバーした要素にしか適用されない
        $('#all div').width('10%');
    },function(){
        //マウスカーソルが離れたとき、currentというclass名をつけて、横幅50%を維持する
        $(this).addClass('current');
    });
});

まとめ

まだまだ、CSS3未対応のブラウザも視野に入れていかないといけないので、jQueryを使ったアニメーションやフェードといった処理が必要になりますね。

HTMLやCSSだけでは実装が難しい箇所でも簡単に、jQueryを組み合わせることで機能を実装することが可能なり、CSS3と組み合わせることで色々な表現をより簡潔に表現できるようになりましたね。