[別々で動くSkill bar]同一クラスを使って可視領域に入った要素を別々に動作させる方法


はじめに

スキルバーはネットにいっぱい情報が転がってるけど、

 1.クリック動作だけとかスクロール設定までされてないもの
 2.デザインにこだわりすぎてシンプルじゃないもの
 3.ライブラリを使ってるもの
 4.なぜか全部一緒に動くようにしてる。←ココが一番気になる
 5.シンプルでそれぞれ別々で動くものがない

というスキルバーが結構あったので、
以下のようなスキルバーを作成すべくコーディング。

上記を解決するために、
1.シンプルで汎用的なもの
2.全部一緒に動かずに別々に動くもの(同じクラスを付与)
3.別ライブラリを使わずになるべく再現性や応用ができるもの

の3つにこだわって作成。
DEMOはこちら。


See the Pen
pojEJeY
by 坊 拓磨 (@bo_chan6130)
on CodePen.


こんな感じで、
スクロールすれば可視領域でそれぞれのバーが動く、という仕組みを作ってみた。
(下記に解説を載せておきます。)

HTMLとCSSを準備

index.html
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<div class="pro-box">
    <div class="Progress_Status">
      <div class="myprogressBar"></div>
      <div class="prorate" data-value="80">1%</div>
    </div>

    <div class="Progress_Status">
      <div class="myprogressBar"></div>
      <div class="prorate" data-value="60">1%</div>
    </div>

    <div class="Progress_Status">
      <div class="myprogressBar"></div>
      <div class="prorate" data-value="40">1%</div>
    </div>

    <div class="Progress_Status">
      <div class="myprogressBar"></div>
      <div class="prorate" data-value="20">1%</div>
    </div>
</div>

[point]
バーと数字(%)を別で動作させる仕組みの方がかなり簡単だけど正直カッコよくないので
myprogressBarの長さは、直下の要素<div class="prorate" data-value="80">1%</div>data-value=""の値に応じて長さが決まるようにする。

index.css
.pro-box {
  margin-bottom:500px;
  margin-top: 300px;
  /*スクロール用*/

}

.Progress_Status {
  width: 100%;
  background-color: #fff;
}
.myprogressBar {
  width: 1%;
  height: 50px;
  background-color: #6babf1;
  text-align: center;
  display: inline-block;
}
.prorate {
  display: inline-block;
  vertical-align: top;
  height: 50px;
  line-height: 50px;
  margin-left:10px;
}

バーの大きさやスタイル調整はお好みで。

jQueryで動作を制御

作成にあたってのミソ。
Ⅰ)スクロールして要素が可視領域に入れば「一度だけ」動作する
Ⅱ)同クラスから別々のdata-value値をそれぞれ取得→動作させる

index.js
$(function(){
  $(window).scroll(function(){
    $(".myprogressBar:not(.fire)").each(function(){
      //eachで同一クラスmyprogressBarをそれぞれ取得
      var position = $(this).offset().top;
      //それぞれ要素の位置を取得
      var scroll = $(window).scrollTop();
      //スクロール値を取得
      var windowHeight = $(window).height();
      //画面サイズの高さを取得

        var element = $(this);
      //barの長さに反映するために用意
        var prorate = $(this).next();
      //指定要素の2番目の要素<div class="prorate"></div>をnext()で指定。
        var value = prorate.data("value");
      //barの値を取得
        var width = 1;
      //初期値を1に設定

      if(scroll > position - windowHeight){
      //可視領域設定。※高さを変えたい場合はwindowHeightの後ろを調整。+ 100 にすると画面下から100pxの高さで表示設定できる。
        var identity = setInterval(scene, 10);
      //setIntervalで関数を制御。バーの動きを遅くしたい場合は第2引数の数字を増やす。
        function scene() {
          if (width >= value) {
            clearInterval(identity);
          //value値まで数字が到達すればintervalをストップ。
          } else {
            $(element).addClass( 'fire' );
            //それぞれの要素にfireクラスを付与。「一度だけ」行う関数の制御にはaddClassが有効。
            width++;
            element.width(width + '%');
            //barの長さ。value値の分だけ伸びる設定
            prorate.html(width * 1  + '%');
            //value値のカウント※html()でindex.htmlのテキスト「1%」を上書き)
          }
        }
      }
    })
  })
})

[point]
ポイントは、「一度だけ処理」する場合はaddClass()を使って制御するということ。
今回の場合、最初はfireクラスを持っていないmyprogressBarをそれぞれ取得し、function scene(){}を実行するときに、fireクラスを持たせることで、$(window).scroll(function(){ }以下のループ処理を実行不可にさせている。もし、この処理がない場合は、スクロールするたびにeachがループするので、思った挙動は起こらない。今回のように同一クラスを用いて別々に「一度だけ」実行処理する場合はaddClass()で制御する。

また、同一クラスでそれぞれの値を取得するときは、each(){}の中身は必ず$(this)で統一する。仮に$(".myprogressBar")とした場合、ループは実行されず、最初の.myprogressBarのみ値を取得する。そのため、一番上のバーのみ動き、他は動かくなるので注意が必要。

まとめ

scroll()を使用するとスクロール度に動作が実行されるので、addClass()を使って動作制御をすることでうまくいく。また、このコードを参考に指定値までのカウンティングに応用できるはずなので、余力があればそちらも公開したい。