雪を降らせる。その2

ちょっとずつ改良(?)する

オブジェクトの定義のやりかたがスマートなのか?!

<!DOCTYPE html>
<meta charset="utf-8">
<title>Snow</title>

<style>
body {
  background-color:black;
  overflow: hidden;
}
</style>

<body>

<script>
var Snow = new function () {
  
  if (
    'undefined' === typeof addEventListener ||
    'undefined' === typeof setInterval ||
    'undefined' === typeof Function.prototype ||
    'undefined' === typeof Function.prototype.call ||
    'undefined' === typeof Function.prototype.apply ||
    'undefined' === typeof Function.prototype.bind ||
    'undefined' === typeof Object.create
  ) {
    return null; 
  }

  //__

  // 整数化
  function int (n) {
    return Math.floor (n);
  }


  // 乱数を整数(bからe未満)を返す
  function randomInt (b, e) {
    switch (arguments.length) {
    case 2 : return int (Math.random () * (e - b)) + b;
    case 1 : return int (Math.random () * b);
    default : throw new Error;
    }
  }


  // Sin 関数は計算に時間が必要なので前もって計算しておく
  var sinDeg = new function () {
    var sintab = [ ];
    var sin    = Math.sin;
    var rad    = Math.PI / 180;
    var val    = 0;
    
    for (var i = 0; i < 360; i++) {
      sintab[i] = sin (val);
      val += rad;
    }

    // sin 関数本体
    return function sinDeg (deg) {
      return sintab[deg % 360];
    };
  };

  //__


  function Timer (interval) {
    this.interval = interval;
  }


  function Starter (cbFunc) {
    this.timerID = setInterval (cbFunc.bind (this), this.interval);
  }


  function Stopper () {
    clearInterval (this.timerID);
    this.timerID = null;
  }

  //__

  Timer.prototype = Object.create (
    Timer.prototype, {
      
      timerID: {
        value       : null,
        writable    : true,
        enumerable  : false,
        configurable: false
      },

      interval: {
        value       : null,
        writable    : true,
        enumerable  : true,
        configurable: false
      },
      
      start: {
        value       : Starter,
        writable    : false,
        enumerable  : true,
        configurable: false
      },
      
      stop: {
        value       : Stopper,
        writable    : false,
        enumerable  : true,
        configurable: false
      }
    });

  //__


  function Decorator (alpha, size) {
    this.opacity  = alpha / 100 + '';
    this.fontSize = size + 'px';
  }


  function Locator (x, y) {
    this.left = int (x) + 'px';
    this.top  = int (y) + 'px';
  }


  function Mover (element) {
    Timer.call (this);
    this.element = element;
  }


  function move (x, y) {
    return Locator.apply (this.element.style, arguments);
  }


  function view () {
    var C = this.element.ownerDocument.defaultView;
    return {
      innerWidth : C.innerWidth,
      innerHeight: C.innerHeight
    };
  }

  //__

  Mover.prototype = Object.create (
    Timer.prototype, {
      
      constructor: {
        value: Mover
      },
      
      view: {
        value       : view,
        writable    : false,
        enumerable  : false,
        configurable: false
      },
      
      move: {
        value       : move,
        writable    : false,
        enumerable  : true,
        configurable: false
      }
    });

  //__

  function Snow (element) {
    Mover.apply (this, arguments);
    this.p = 0;
    this.x = 0;
    this.y = 0;
    this.z = 0;
  }


  // Snow の初期化 
  function snow_init (n) {
    var v = this.view ();
    
    this.x = randomInt (v.innerWidth);
    this.y = n ? randomInt (v.innerHeight): 0;
    this.z = randomInt (5, 36);
    this.p = randomInt (360);
    this.interval = randomInt (50, 100);
    
    Decorator.call (this.element.style, randomInt (30, 100), this.z);
  }


  // Snow を作り出す
  function snow_create (target) {
    var doc = target.ownerDocument;
    var e = doc.createElement ('p');
    var style = e.style;
    var obj;
    
    e.textContent = '*';
    style.color = '#fff';
    style.position = 'absolute';

    obj = new this (target.appendChild (e));
    obj.init (true);
    obj.start (snow_fall);

    Snow['#instances'].push (obj);
    
    return obj;
  }


  // target要素に n個の雪を作る
  function snow_demo (target, n) {
    if (0 < n)
      while (n--)
        this.create (target);

    //実験的な例としてクリックしたら落下を止める
    target.ownerDocument.addEventListener ('click', snow_clickStopper, false);
    target = null; //メモリーリーク・パターンを断ち切る
  }


  // クリックされたことで落下を一時停止にする
  function snow_clickStopper (event) {
    var x = Snow['#instances'];
    var e = event.target;
    var snow, i;

    for (i = 0; snow = x[i++]; ) {
    	if (snow.element === e) {
        snow.timerID
        ? snow.stop ()
        : snow.start (snow_fall);
    		break;
    	}
    }
  }

  //__

  function snow_fall () {
    var h = this.view ()['innerHeight'];
    
    this.y += this.z / this.rakka;
    this.p += this.yuragi;

    if (this.y > h)
      this.init ();
    
    this.move (
      this.x + sinDeg (this.p) * this.z,
      this.y
    );
  }

  //__

  Snow['#instances'] = [ ];
  Snow.create = snow_create;
  Snow.demo   = snow_demo;

  //__

  Snow.prototype = Object.create (
    Mover.prototype, {
      
      constructor: {
        value: Snow
      },
      
      init: {
        value       : snow_init,
        writable    : false,
        enumerable  : true,
        configurable: false
      },
      
      yuragi: {
        value       : 6
      },
      
      rakka: {
        value       : 3
      }
    });

  //__

  return Snow;

};

Snow.demo (document.body, 100);

</script>