ryoakg
6/9/2016 - 4:30 PM

js-animation.html

<html>
  <head>
    <style>
     .canvas {
       position: relative;
       height: 600px;
       background: #000;
       overflow: hidden;
       margin-bottom: 5px;
     }
     .flying-charactor{
       position: absolute;
       color: #0f0;
     }
     h1{
       font-size: 20px;
     }
    </style>
  </head>
  <body>
    <div style="margin-top:-10px;margin-bottom:20px;">
      <script>
       function tweener_env(){
         // functions & constants
         var FPS = 60;
         function default_easing(elapsed, from, distance, duration){
           return distance * elapsed / duration + from;
         };
         var default_duration = 1000;

         function normalize_property(name, prop){
           return { name     :name,
                    from     :prop.from,
                    to       :prop.to,
                    distance :prop.to - prop.from,
                    prefix   :prop.prefix || '',
                    suffix   :prop.suffix || 'px' };
         }

         function normalize_item(target, properties, options){
           var props = [];
           for (var name in properties)
             props.push(normalize_property(name, properties[name]));
           return { props      :props,
                    target     :target,
                    start_at   :new Date() - 0,
                    duration   :options.duration || default_duration,
                    easing     :options.easing || default_easing,
                    onComplete :options.onComplete };
         }

         // procedures & mutable data
         var items = [];
         var timer_id;
         function render(){
           var now  = new Date() - 0;
           var n = items.length;
           while (n--){
             var item = items[n];
             var elapsed = now - item.start_at;
             if (elapsed < item.duration){
               item.props.forEach(function(prop){
                 item.target[prop.name] = prop.prefix +
                                          item.easing(elapsed, prop.from, prop.distance, item.duration) +
                                          prop.suffix;
               });
             } else {
               item.props.forEach(function(prop){
                 item.target[prop.name] = prop.prefix + prop.to + prop.suffix;
               });
               if (item.onComplete) item.onComplete();
               items.splice(n, 1); // 前からループすると、ココで添字がズレるので後ろからループ
             }
           }
           if (items.length === 0) finish();
           // わざわざ while (n--) とかして逆向きのループにしているけど
           // これは何度も配列を分割して、くっつけているのでそんなに速くないのかも
           // 素直に、以下の様に2回にループを分けた方が速いかもしれない。
           //   1回目: target の書換え と duration を過ぎたやつに null を入れる
           //   2回目: Array.prototype.filter で null のやつをまとめて削除
           // もちろん JS の処理系の実装によるけど
         };
         function start(){
           timer_id = setInterval(render, 1000 / FPS);
         }
         function finish(){
           items = [];
           clearInterval(timer_id);
           timer_id = null;
         }

         return function(target, properties, options){
           if (items.length === 0) start();
           items.push(normalize_item(target, properties, options));
         };
       }
      </script>
      <div id="canvas" class="canvas"></div>
      <button onclick="run_or_stop()">アニメーションを実行</button>
      <h1>参考</h1>
      <ul>
        <li><a href="http://gihyo.jp/dev/serial/01/crossbrowser-javascript/0017">第17回 アニメーションの基礎</a></li>
        <li><a href="http://coderepos.org/share/wiki/JSTweener">JSTweener</a></li>
        <li><a href="http://hosted.zeh.com.br/tweener/docs/en-us/misc/transitions.html">Tweener Documentation</a></li>
      </ul>
      <script>
       var canvas = document.getElementById('canvas');
       var timerId;
       var add_target = tweener_env();
       function random_charactor(){
         return Math.floor(Math.random()*36).toString(36);
       }
       function run_or_stop(){
         if(timerId){
           clearInterval(timerId);
           timerId = null;
         } else {
           timerId = setInterval(function(){
             var i = document.createElement('span');
             i.className = 'flying-charactor';
             i.style.top = i.style.left = '50%';
             i.style.fontSize = '50%';
             i.appendChild(document.createTextNode(random_charactor()));
             canvas.appendChild(i);

             add_target(i.style,
                        { top      : {from:50, to:100*Math.random(), suffix:'%'},
                          left     : {from:50, to:100*Math.random(), suffix:'%'},
                          fontSize : {from:50, to:300*Math.random(), suffix:'%'}},
                        { duration : Math.random()*1000,
                          onComplete:function(){ canvas.removeChild(i); }});
           }, 32);
         }
       }
      </script>
    </div>
  </body>
</html>