Tuesday, 8 January 2013

HTML5 Canvas Bezier Curve


I'm braindead. So I decided no to go into philosophical discussion - rather checked the Canvas API the first time. Yeah, I know it's already too old. Anyways. Canvas is so simple anybody can learn the basics in 5 minutes. To make a little sense I decided to make a silly screensaver like animation - a curve that bouncing on the screen and leaves a frame.


The idea is very simple. A bezier curve has a start context point, 2 control points an an end point.

Fig.: Bezier curve

We will create 4 points. Place them randomly on the canvas an initiate them with random speed vectors. Then we fire a timer and in each tick we will move those points on the canvas and check when we have to adjust their speed vectors (when it touches the wall). Before each iteration we clear some part of the canvas, so you can see the curve moving and also it leaves a fancy frame.

First you need a nice HTML5 template like this:


<!doctype html>
<html>
<head>

<script type="text/javascript">
</script>

</head>
<body onload="onBodyLoad();">

</body>
</html>


For the canvas you need the dom element in the body:

<canvas id="screen" width="640" height="480"></canvas>


The canvas has a drawing context, so let's create a variable for it:

var ctx;


This context can accept our commands later. We need to access our canvas' boundaries somehow:

var boundaries = {
  w: 640,
  h: 480
};


We set the timer interval to 0.01 second:

var interval = 10;


Set the average speed of a point:

var speed = 10;


Then we generate 4 random points with random vectors:

var number = 4;
var items = [];
for (var i = 0; i < number; i++) {
  items[i] = {
    point: {
      x: Math.random() * boundaries.w,
      y: Math.random() * boundaries.h
    },
    speed: getRandomSpeedVectors(speed)
  }
}


To make all speed the same but with a different angle we use some math to generate the X and Y speed values:

function getRandomSpeedVectors(speed) {
  var speed_x = Math.random() * speed;
  return {
    sx: speed_x,
    sy: Math.sqrt(Math.pow(speed, 2) - Math.pow(speed_x, 2))
  };
}

To make point updates easy we add a new method to the JavaScript Object prototype:

Object.prototype.applyWorld = function(speed_vector, boundaries) {
  if (this.x + speed_vector.sx >= boundaries.w || this.x + speed_vector.sx <= 0) {
    speed_vector.sx *= -1;
  }

  if (this.y + speed_vector.sy >= boundaries.h || this.y + speed_vector.sy <= 0) {
    speed_vector.sy *= -1;
  }

  this.x += speed_vector.sx;
  this.y += speed_vector.sy;
};

This function will be available on the object method level and can handle 1 state change on a point. That's enough to make the point moving.

Now we need something that actually handles 1 step for all the points and draw the curve on the canvas:

function loop() {
  ctx.clearRect(100, 100, boundaries.w - 200, boundaries.h - 200);
  ctx.beginPath();
  ctx.moveTo(items[0].point.x, items[0].point.y);
  ctx.bezierCurveTo(
    items[1].point.x, items[1].point.y,
    items[2].point.x, items[2].point.y,
    items[3].point.x, items[3].point.y
  );
  ctx.stroke();

  for (var idx in items) {
    var item = items[idx];
    if (!item.point) {
      continue;
    }
    item.point.applyWorld(item.speed, boundaries);
  }

  setTimeout(loop, interval);
}

It also calls itself so that makes it an animation. And finally we have to initialize the canvas and start the animation, so let's hook into the body onload event:

function onBodyLoad() {
  var canvas = document.getElementById('screen');
  if (!canvas.getContext) {
    return;
  }

  ctx = canvas.getContext('2d');
  ctx.lineWidth = 6;
  ctx.strokeStyle = 'rgba(33, 66, 99, 0.5)';

  loop();
}

That's it. You can find the demo here: https://dl.dropbox.com/u/2629592/canvas_screensaver.html. For the source code check the Github repo: https://github.com/itarato/CanvasScreensaver/blob/master/index.html.

---

If you have a more efficient way to do it (I bet you have) please let me know.

Peter

No comments:

Post a Comment