ベジェ曲線を描く、その2。

<!DOCTYPE html>
<title>遅延時間を考えて移動する</title>
<meta charset="UTF-8">
<style>

</style>

<canvas id="hoge" width="800" height="600"></canvas>


<script>


//_______________
// CANVAS を利用して、点と線を簡単に描くもの
(function () {
  
  function Graffiti (canvas) {
    this.ctx = canvas.getContext ('2d');
  }
  
  
  function line (/*[x0, y0], [x1, y1], ...*/) {
    var index = 0;
    var x = arguments[index][0];
    var y = arguments[index][1];
    var arg;
    var ctx = this.ctx;
    
    ctx.beginPath ();
    ctx.moveTo (x, y);
    
    while (arg = arguments[++index]) {
      if ('b' === arg) {
        ctx.closePath ();
        break;
      }
      if ('bx' === arg) {
        ctx.closePath ();
        ctx.fillRect ();
        break;
      }
      ctx.lineTo (arg[0], arg[1]);
    }
    ctx.stroke ();
    
    return this;
  }
  
  function pset (x, y, c) {
    var ctx = this.ctx;
    if (c instanceof Array) {
      ctx.fillStyle = 'rgb(' + c[0] + ',' + c[1] +',' + c[2] + ')';
    }
    
    ctx.fillRect (x, y, 1, 1);
    return this;  
  }  


  function create (canvas) {
    if (! canvas.getContext)
      return null;
    return new Graffiti (canvas);
  }
  
  
  //__________
  
  Graffiti.prototype.line = line;
  Graffiti.prototype.pset = pset;
  
  Graffiti.create = create;
  
  this.Graffiti = Graffiti;
})();


//遅延を感知してそれを解消するよう努力する
(function () {

  function IntervalCompletion (cbFunc, interval, maxCompletion) {
    this.cbFunc    = cbFunc;
    this.interval  = interval;
    this.maxCompletion = maxCompletion;

    this.startTime = null;
    this.count     = null;
  }
  
  
  var loop =
    (function () {

      var now = (new Date).getTime ();
      var delay = (now - this.startTime) - this.interval * this.count;
      var completion = 0;
      var result;
      
      if (0 < delay) {
        completion = Math.floor (delay / this.interval);
        if (this.maxCompletion < completion) {
          completion = this.maxCompletion;
        }
      }

      do {
        result = this.cbFunc ((new Date).getTime ());
        this.count++;
        if (! result) // コールバックが false を返したら終了
          break;

      } while (completion--);
      
      if (result)
        setTimeout (loop.bind (this), this.interval);
    });
  
  
  IntervalCompletion.prototype.start =
    (function () {
      this.startTime = (new Date).getTime ();
      this.count = 0;
      loop.call (this);
    });


  IntervalCompletion.create =
    (function (cbFunc, interval) {
      if (1 > arguments.length)
        throw new Error;
      return new IntervalCompletion (cbFunc, interval);
    });
  
  this.IntervalCompletion = IntervalCompletion;
}) ();

//_______________

// ベジェ曲線を高速化したもの (4点限定)戻り値は[x,y]
function QBezierCurve_create (x0, y0, x1, y1, x2, y2, x3, y3) {
  return function (t) {
    if (t < 0 || 1 < t)
      throw new Error;
    
    var a = 1 - t, b = a * a, c = 3 * a * t, d = t * t * t;
    var e = a * b, f = c * a, g = c * t;

    return [
      e * x0 + f * x1 + g * x2 + d * x3,
      e * y0 + f * y1 + g * y2 + d * y3
    ];
  };
}

// cbFunc に、0〜1 の範囲で step 刻みの値を与える
function 	TransitionTimingFunction_create (cbFunc, step) {
  var t = 0;
  return function () {
    var result = cbFunc (t);
    t += step;
    if (t == 1)
      return false;
    if (1 < t)
      t = 1;
    return true;
  }
}


var points = [40, 300, 150, 0, 400, 600, 550, 300];
var canvas = Graffiti.create (document.querySelector ('#hoge'));
var fx = QBezierCurve_create.apply (null, points);


function draw (t) {
  var p = fx (t);
  canvas.pset (p[0], p[1], [255,0,0]);
  return (t != 1);
}


var time = 5000;
var span = 500;

var draw_ = TransitionTimingFunction_create (draw, 1 / span);



var obj = new IntervalCompletion (draw_, time / span, 4)
obj.start ();


</script>