HTML
CSS
JavaScript
HTML5
CSS3
CSSDay 10

インタラクション実装におけるCSSとJSの棲み分け

More than 3 years have passed since last update.

Advent Calender 初めて参加させていただきます。
どうぞよろしくお願いいたします。

「インタラクション」と言うと大きな話に聞こえますが、
ユーザがクリックすると何かがアニメーションするよ、くらいの小さいインタラクションを例に話したいと思います。

意見

クリックしてアニメーション、みたいなインタラクションはついJSで全部完結させてしまいがちですが、
アニメーションはCSS、ステータス管理や値の更新はJSといったようにすみ分けるとシンプルに実装できてオススメですよ。という話をしたいと思います。

DEMO

sample.png

↑の画像のような、ごく普通のスライダを例に考えたいと思います。
動きのサンプルと全ソースは下記jsfiddle参照ください。
http://jsfiddle.net/volkuwabara/ecy7voxf/4/embedded/result/

コード

一応ソースの解説をしますが読み飛ばしていただいて良いです。
今回の話に関係するところ以外省略しちゃっています。全ソースは↑のjsfiddleを参照してください。

html
<aside id="slider">
    <div class="banners">
        <ul>
            <li><a href="#">1</a></li>
            <li><a href="#">2</a></li>
            <li><a href="#">3</a></li>
            <li><a href="#">4</a></li>
            <li><a href="#">5</a></li>
            <li><a href="#">6</a></li>
            <li><a href="#">7</a></li>
            <li><a href="#">8</a></li>
            <li><a href="#">9</a></li>
            <li><a href="#">10</a></li>
        </ul>
    </div>
    <div class="pager">
        <a class="prev" href="#">&lt;</a>
        <a class="next" href="#">&gt;</a>
    </div>
</aside>

#sliderという大枠のモジュールの中で、
バナー部分.banner、ページャ部分.pagerとゾーニングしました。

scss
#slider {
    position: relative;
    width: 512px;
    height: 85px;
    margin: 30px auto;

    .banners {
        position: absolute;
        width: 512px;
        height: 85px;
        overflow: hidden;

        ul {
            position: absolute;
            width: auto;
            height: 100%;
            left: 0;
            top: 0;
            transition: left .4s ease-in-out; /*アニメーションはCSSで*/
        }

        li {
            float: left;
            margin: 0 4px;
            a {
                /*省略*/
            }
        }
    }

    .pager {
        /*省略*/
    }

}

よくある、バナーをfloatさせて横一列にならべて、overflow:hidden;している枠の中で左右に移動させるという作りになっています。
続いてJS(jQuery使用)です。

JS(jQuery)
(function($){
  'use strict';

  var slider, nav, container, banner, btnPrev, btnNext, max, count, pitch;

  function sliderInit() {
       slider = $('#slider');
    container = slider.find('.banners > ul');
          nav = slider.find('.pager');
      btnPrev = nav.find('.prev');
      btnNext = nav.find('.next');
          max = container.find('li').length;   //バナーの数
        count = 1;                             //その時一番左に表示されているバナーが何番目か
        pitch = 128;                           //バナー同士の距離px

    if(max <= 4){
      btnNext.hide();
      btnPrev.hide();
    }
    else {
      container.width((max)*pitch); //バナーの個数分親の横幅を伸ばす
      sliderUpdate();

      btnPrev.on('click', function(){
        count -= 1;
        sliderUpdate();
        return false;
      });
      btnNext.on('click', function(){
        count += 1;
        sliderUpdate();
        return false;
      });
    }
  }

  function sliderUpdate() {

    //ulのleftを更新(モーションはCSS側で勝手に動く,なので連打対応とか不要!)
    container.css('left', -1*(count-1)*pitch);

    //条件に応じて< >ボタンの表示/非表示の更新
    if(count <= 1) {
      btnPrev.addClass('disable');
      btnNext.removeClass('disable');
    }
    else if(count > 1 && count < (max-3)){
      btnPrev.removeClass('disable');
      btnNext.removeClass('disable');
    }
    else if(count >= (max-3)){
      btnPrev.removeClass('disable');
      btnNext.addClass('disable');
    }

  }

  $(function(){
    sliderInit();
  });

})(jQuery);

解説

JS側

container.css('left', -1*(count-1)*pitch);

JS側でやっていることとしては
「現在一番左表示されているのが何番目のバナーか」 を変数管理しておいて、< >ボタンが押される度に、その変数を増減させ、その数に「バナー同士の距離」をかけた距離をバナーを囲んでいる親タグのCSS left プロパティに代入しつづける。というだけです。JS側でjQuery.animationなどを使用した「動き」に関する処理はこちらにはありません。

CSS側

transition: left .4s ease-in-out;

一方で、CSSはJSから代入されつづけるleftの値を追いかけてtransitionしつづけるだけです。
アニメーション中だろうがなんだろうが、更新されるleftの値に向かってアニメーションし続けるだけなので、連打などをしても問題なく動きます。

まとめ

下記の点でオススメかなと考えています。

  • 実測できていないですが、jQueryアニメーションよりも動きがスムーズな気がします
  • JSでアニメーションをしようとすると、「現在の場所からどのくらい動かす」という実装になりやすいので、アニメーション中とか、連打とか考えることが多い一方、CSSは「現在がどうあれ、最終的にこの数値になっとけ」というプロパティ指定なので実装がラクです。
  • 住み分けるとコードがシンプルで綺麗になり、メンテナンスしやすくなります。    

タイトルのわりに地味で小さい話で失礼いたしました..!
読んでいただきありがとうございました。