この例で、私達は XHTMLSVGJavaScriptDOM 2 を「ほこり」の群れを動かすのにつかっています。これらのほこりは 2 つの簡単な法則によって制御されています。1 つめは、それぞれのほこりがマウスカーソルの方向に向かって移動しようとします。2 つ目はそれぞれのほこりはほこりの位置の平均から遠ざかろうとします。組み合わせることで、このとても自然に見える動きができます。

これは完全に Flash やその他のベンダ特有の拡張を用いずに、 W3C 標準- XHTML と SVG と JavaScript で実現されています。これは Firefox 1.5 以上で動作します。

実行例を表示

xml
<?xml version='1.0'?>
<html xmlns="http://www.w3.org/1999/xhtml"
  xmlns:svg="http://www.w3.org/2000/svg">
  <head>
  <title>ほこりの群</title>
  <style type='text/css'>
  <![CDATA[
    label, input
    {
      width: 150px;
      display: block;
      float: left;
      margin-bottom: 10px;
    }
    label
    {
      text-align: right;
      width: 75px;
      padding-right: 20px;
    }
    br
    {
      clear: left;
    }
  ]]>
  </style>
  </head>
  <body onload='update()'>
    <svg:svg id='display' width='400' height='300'>
      <svg:circle id='cursor' cx='200'
cy='150' r='7' fill='#0000ff' fill-opacity='0.5'/>
    </svg:svg>

    <p>ほこりの群れは 2 つの簡単な法則によって制御されています。1 つめは、それぞれのほこりがカーソルの方向に向かって移動しようとします。2 つ目はそれぞれのほこりはほこりの位置の平均から遠ざかろうとします。組みあわせることで、このとても自然に見える動きができます。</p>

    <p>
    これは完全に Flash やその他のベンダ特有の拡張を用いずに、 W3C 標準 – XHTML / SVG / JavaScript - によって実現されています。現在のところ、これは Mozilla Firefox 1.5 以上で動作するでしょう。</p>

    <div>
    (C) 2006 <a id='emailme' href='#'>Nick Johnson</a>

    <script type='text/javascript'>
    <![CDATA[
      // spam ボット撃退
      var email = '@riovia.net';
      email ='nick' + email;
      document.getElementById('emailme').href = 'mailto:'+email;
    ]]>
    </script>
    This software is free for you to use in any way whatsoever,
    and comes with no warranty at all.</div>

    <form action="" onsubmit="return false;">
      <p>
      <label>ほこりの数:</label>
        <input id='num_motes' value='5'/>
        <br/>

      <label>最大速度:</label>
        <input id='max_velocity' value='15'/>
        <br/>

      <label>カーソルへの引力:</label>
        <input id='attract_cursor' value='6'/>
        <br/>

      <label>仲間との反発力:</label>
        <input id='repel_peer' value='5'/>
        <br/>
      </p>
    </form>

  <script type='text/javascript'>
  <![CDATA[

    // ほこりの配列
    var motes;

    // 表示用の要素の取得。
    function Display()
    {
      return document.getElementById('display');
    }

    // 表示用の要素の大きさを定義。
    // これを配列の 2 組タプル(x,y)として返す
    function Dimensions()
    {
      // 描画用の要素
      var display = Display();
      var width = parseInt( display.getAttributeNS(null,'width') );
      var height = parseInt( display.getAttributeNS(null,'height') );

      return [width,height];
    }

    // これはマウスの移動イベントで呼び出される
    var mouse_x = 200, mouse_y = 150;
    function OnMouseMove(evt)
    {
      mouse_x = evt.clientX;
      mouse_y = evt.clientY;

      var widget = document.getElementById('cursor');
      widget.setAttributeNS(null,'cx',mouse_x);
      widget.setAttributeNS(null,'cy',mouse_y);
    }
    document.onmousemove = OnMouseMove;

    // カーソルの (x,y) を定義
    function Cursor()
    {
      return [mouse_x, mouse_y];
    }

    // ほこりの平均 (x,y) を定義
    function AverageMotePosition()
    {
      if( !motes )
        return [0,0];

      if( motes.length == 0 )
        return [0,0];

      var i;
      var sum_x=0, sum_y=0;
      for(i=0; i<motes.length; i++)
      {
        sum_x += motes[i].x;
        sum_y += motes[i].y;
      }

      return [sum_x/motes.length, sum_y/motes.length];
    }

    // よりよい、整数の乱数
    function Rand(modulo)
    {
      return Math.round( Math.random() * (modulo-1));
    }

    // Mote クラス
    function Mote()
    {
      // 描画領域の大きさ
      var dims = Dimensions();
      var width = dims[0], height = dims[1];

      // 始めるときのランダムな座標を選ぶ。
      this.x = Rand( width );
      this.y = Rand( height );

      // 速度の初期値はゼロ。
      this.vx = this.vy = 0;

      // 視覚的要素---初期状態ではなし
      this.elt = null;
    }

    // これをクラスに。
    new Mote();

    // Mote::applyForce() -- 与えられた方向に
    // 速度を調整。
    // 警告: 疑似的な物理 -- なんらかの/実際の/物理法則によって
    // 制御されているわけでありません
    Mote.prototype.applyForce = function(pos, mag)
    {
      if( pos[0] > this.x )
        this.vx += mag;
      else if( pos[0] < this.x )
        this.vx -= mag;

      if( pos[1] > this.y )
        this.vy += mag;
      else if( pos[1] < this.y )
        this.vy -= mag;
    }

    // Mote::capVelocity() -- ほこりの速度の
    // 上限を設定。
    Mote.prototype.capVelocity = function()
    {
      var max = parseInt( document.getElementById('max_velocity').value );

      if( max < this.vx )
        this.vx = max;
      else if( -max > this.vx )
        this.vx = -max;

      if( max < this.vy )
        this.vy = max;
      else if( -max > this.vy )
        this.vy = -max;
    }

    // Mote::capPosition() -- ほこりの位置の
    // 上限下限を設定。
    Mote.prototype.capPosition = function()
    {
      var dims = Dimensions();
      if( this.x < 0 )
        this.x = 0;
      else if( this.x >= dims[0] )
        this.x = dims[0]-1;

      if( this.y < 0 )
        this.y = 0;
      else if( this.y >= dims[1] )
        this.y = dims[1]-1;
    }

    // Mote::move() -- ほこりの移動、スクリーンの更新。
    Mote.prototype.move = function()
    {
      // カーソルの引力を適応。
      var attract = parseInt( document.getElementById('attract_cursor').value );
      var cursor = Cursor();
      this.applyForce(cursor, attract);

      // ほこりの位置の平均からの反発を適用
      var repel = parseInt( document.getElementById('repel_peer').value );
      var average = AverageMotePosition();
      this.applyForce(average, -repel);

      // 速度にでたらめさを追加。
      this.vx += Rand(3)-1;
      this.vy += Rand(3)-1;

      // 速度の上限を適応
      this.capVelocity();

      // 速度を適応。
      var old_x = this.x, old_y = this.y;
      this.x += this.vx;
      this.y += this.vy;
      this.capPosition();

      // 描画。

      if( this.elt === null )
      {
        var svg = 'http://www.w3.org/2000/svg';
        this.elt = document.createElementNS(svg,'line');
        this.elt.setAttributeNS(null,'stroke','green');
        this.elt.setAttributeNS(null,'stroke-width','3');
        this.elt.setAttributeNS(null,'stroke-opacity','0.5');
        Display().appendChild( this.elt );
      }

      this.elt.setAttributeNS(null,'x1',old_x);
      this.elt.setAttributeNS(null,'y1',old_y);

      this.elt.setAttributeNS(null,'x2',this.x);
      this.elt.setAttributeNS(null,'y2',this.y);
    }


    function update()
    {
      // 最初の呼び出し?
      if( !motes )
        motes = [];

      // 幾つのほこりがあるべき ?
      var num = parseInt( document.getElementById('num_motes').value );
      if( num < 0 )
        num = 0;

      // 量を厳密に確認。
      // 少なすぎ ?
      while( motes.length < num )
        motes.push( new Mote() );
      // あるいは多すぎ ?
      if( num == 0 )
        motes = [];
      else if( motes.length > num )
        motes = motes.slice(0,num-1);

      // ほこりをランダムに移動
      if( motes.length > 0 )
        motes[ Rand( motes.length ) ].move();

      // これを 100 分の 1 秒ごとに再実行。
      setTimeout('update()', 10);
    }
  ]]>
  </script>
  </body>
</html>