キンチョウの夏、その2(忘れたころに)JavaScriptで花火

計算との分離を目標に書きました。

爆発直前の処理は、打ち上げ前にやるべきだよなぁ〜
ちゅっとだけ落下しながらフェードアウト。
コードはへなちょこだが、風情は出てきたか!?

<!DOCTYPE html>
<title>Hanabi</title>

<style type="text/css">
body  { background : #000; }
h1    { color      : #440; float: left}
</style>

<body>
<h1>Hanabi</h1>

<script type="text/javascript">

(function () { //@cc_on
  var MAX = 3, stock = [ ],

    CHAR_LIST  = [ '●', '○', '◎', '*', '※', '・', '☆', '∴' ],

    COLOR_LIST = [
      [ 255, 0,   0 ], [ 0, 255,   0 ], [ 0, 0, 255 ], [ 255, 255, 0 ],
      [ 255, 0, 255 ], [ 0, 255, 255 ], [ 255, 255, 255],
      [ 255, 128, 0 ], [ 128, 255, 0 ], [ 0, 128, 255 ],
      [ 255, 0, 128 ], [ 0, 255, 128 ], [ 128, 0, 255 ]
    ],

    // ランダム整数
    randomInt = function ( n, b ) {
      return (b || 0) + Math.random () * n |0
    },
    
    // ランダム配色
    randomColor = function ( ) {
      return COLOR_LIST[ randomInt( COLOR_LIST.length ) ];
    },
    
    // ランダム火玉文字
    randomStar  = function ( ) {
      return CHAR_LIST[ randomInt( CHAR_LIST.length ) ];
    }, 
  //____________________________

    // パーツ追加
    append = function ( parts ) {
      for( var i = 0, p; p = parts[i++]; this.appendChild( p.materialt ) );
    }, 
    
    // 動く
    move = function ( ) {
      for( var i = 0, p, f; p = this.parts[ i++ ]; )
        f = f | this.motion.call(p)
      return f
    },
    
    // パーツ削除
    remove = function ( parts ) {
      for( var i = 0, p; p = parts[ i++ ]; )
        p.materialt.parentNode.removeChild( p.materialt );
    },
    
    // パーツを描画
    draw = function ( parts ) { 
      for( var i = 0, p, s; p = parts[ i++ ]; ) {
        s = p.materialt.style;
        s.top   = (p.position.y |0) + 'px';
        s.left  = (p.position.x |0) + 'px';
        s.color = 'rgb(' + (p.color[0]|0) + ',' + (p.color[1]|0) + ',' + (p.color[2]|0) + ')';
      }
    },
  //____________________________

    // func のループ開始
    start = function ( func ) {
      var that = this;
      this.timerId = setInterval( function () { func.call( that ) }, 30);
    },
    
    // ループ処理終了
    stop  = function ( flag ) {
      this.timerId && clearInterval( this.timerId );
      return this.timerId = null
    }, 
  //____________________________

    // 打ち上げの高さ計算
    motion1 = function ( ) {
      var y = this.option[3],
          h = this.position.y -= Math.floor( ( this.position.y - y ) / 20 ) + 2;
      return y > h;
    },
    
    // 爆発の後の花火の位置を計算
    motion2 = function ( ) {
      var c = this.color, a = this.option[2];
      this.position.x += (this.option[0] *= 0.8);
      this.position.y += (this.option[1] *= 0.8) + 0.4;
      this.color = [ c[0] += a[0], c[1] += a[1], c[2] += a[2] ];
    },

    // ゆっくりと消えてゆく
    motion3 = function ( ) {
      this.position.y += 0.2;
      this.color[0] -= 10 < this.color[0] ? 10: 0;
      this.color[1] -= 10 < this.color[1] ? 10: 0;
      this.color[2] -= 10 < this.color[2] ? 10: 0;
    },
  //____________________________

    // 発射
    fire = function ( ) {
      move.call( this ) ? stage2.call( this ): draw( this.parts );
    },
    
    // 消滅
    extinction = function ( ) {
      if (0 == --this.life) {
        stop.call( this );
        remove( [ { materialt: this.screen } ] );
      }
      else {
        move.call ( this );
        draw ( this.parts );
      }
    },

    // 爆発
    explosion = function ( ) {
      if (0 == --this.life) {
        stop.call( this );
        this.motion = motion3;
        this.life = 25;
        start.call( this, extinction );
      }
      else {
        move.call ( this );
        draw ( this.parts );
      }
    }
  


  // 爆発前処理
  function stage2 ( ) {
    stop.call( this );

    var ball = this.parts[0];
    var wa = 7,                         // 花火の輪の数
        v  = 1,
        u  = 2 / ball.option[2] * Math.PI, // 高速化のために計算
        w  = ball.option[1] / this.life,
        c  = ball.materialt.cloneNode( true ),
        cr = ( ball.option[0][0] - ball.color[0] ) / this.life, // red:色の変化の差分を計算
        cg = ( ball.option[0][1] - ball.color[1] ) / this.life, // green:
        cb = ( ball.option[0][2] - ball.color[2] ) / this.life, // blue:
        j, d, r, t, q;

    remove( this.parts );

    this.parts = [ ];
    this.motion = motion2;

    while( --wa ) {                       // 外側から内側へと計算
      v *= 1.4;                           // 花火の輪の間隔
      d = 0;                              // 角度(単位はちがうけど)
      r = w / v;                          // 花火の半径
      q = ball.option[2] / v;             // 内側になるほど、点の数を減らす
      t = u * v;                          // 次の角度を高速化のために計算
      c.firstChild.nodeValue = randomStar(); // 火の点の文字

      for( j = 0; j < q; j++ ) {
        this.parts.push( {
          materialt : c.cloneNode( true ),
          position  : { x: ball.position.x, y: ball.position.y },
          color     : [ ball.color[0], ball.color[1], ball.color[2] ],
          option    : [ Math.sin( d ) * r, Math.cos( d ) * r, [ cr, cg, cb ] ]
        } );
        d += t; // 次の角度
      }
    }
    append.call( this.screen, this.parts );
    start.call( this, explosion );
  }

  //____________________________

  // 表示領域を取得
  var getViewSize = function (doc) {
      /*@if(1)
        var v = doc.compatMode == 'CSS1Compat' ? doc.documentElement: doc.body;
        return [ v.clientWidth, v.clientHeight ];
      @else@*/
        var v = doc.defaultView;
        return [ v.innerWidth, v.innerHeight ];
      /*@end@*/
    };
   
   // 花火の打ち上げ環境整備
   var create = function ( x, y, size, volume, sColor, eColor, life ) {
    var i = MAX, obj, screen, ball, vsize = getViewSize( this );
    
    if( 'undefined' === typeof x ) x = randomInt( vsize[0] );
    if( 'undefined' === typeof y ) y = randomInt( vsize[1] );
    if( 'undefined' === typeof size ) size = randomInt( 1800, 800 );
    if( 'undefined' === typeof volume ) volume = randomInt( 24, 24 );
    if( 'undefined' === typeof sColor ) sColor = randomColor();
    if( 'undefined' === typeof eColor ) eColor = randomColor();
    if( 'undefined' === typeof life ) life = randomInt( 20, 20 );

    while( i-- ) {
      if( obj = stock[i] )
        if( obj.life ) continue;

      screen = this.createElement( 'div' );
      screen.style.position = 'absolute';
      this.body.appendChild( screen );
      
      ball = this.createElement ( 'span' ),
      ball.appendChild( this.createTextNode( '・' ) );
      ball.style.position = 'absolute';
      
      stock[i] = obj = { // オブジェクト(領域・部品・動き)を作って call する
        screen : screen, // DIV
        parts  : [ {
          materialt : ball,
          position  : { x: x, y: vsize[1] -10 },
          color     : sColor,
          option    : [ eColor, size, volume, y ]
        } ],
        life   : life,
        motion : motion1 // 
      };

      append.call( screen, obj.parts );
      start.call( obj, fire );
      break;
    }
  };

  //____________________________

  var init = function ( view ) {
    var win = view || window, doc = view.document,

        onClick = function (e) {
          /*@if(1)
            var d = doc.compatMode == 'CSS1Compat' ? doc.documentElement: doc.body;
            create.call( doc, e.clientX + d.scrollLeft, e.clientY + d.scrollTop );
          @else@*/
            create.call( doc, e.pageX, e.pageY );
          /*@end@*/;
        },

        onUnload = function (e) {
          doc./*@if(1) detachEvent('on'+ @else@*/ removeEventListener(/*@end@*/ 'click', onClick, false );
          win./*@if(1) detachEvent('on'+ @else@*/ removeEventListener(/*@end@*/ 'unload', onUnload, false );
          MAX = randomStar = getViewSize = set = start = explosion = create = init = null;
        };

    doc./*@if(1) attachEvent('on'+@else@*/ addEventListener(/*@end@*/ 'click', onClick, false );
    win./*@if(1) attachEvent('on'+@else@*/ addEventListener(/*@end@*/ 'unload', onUnload, false );
  };
  
  this.Hanabi = { init: init };

})();

Hanabi.init( this );

</script>

覚えてるもんね〜絶対答えないよ〜〜だ!
http://oshiete.goo.ne.jp/qa/5983961.html

いつもながら、考えがハチャメチャに見える
スタイルシートのルールをかえるだけだろう!?