Playing with paper.js and background animations

After starting my blog, I got a few comments on my @gguuss twitter account about the animated background that I have. So, I figured I would write a short post about it.

 

What is paper.js?
I’m sure that its creators will scoff at me for saying this, but in my simple mind, paper.js takes canvas and makes it better. You get new (advanced) path objects, a scene graph, more efficient draw routines, and performant input / interaction. So, how about that background animation…

 

Looks a lot like an example
Rather than jump into the paper.js API set and reference, I started with an example. More specifically, I started with the “Space” example that ships with paper.js and does some nifty pseudo-parallax. The following code shows the unmodified JavaScript example:
// The amount of symbol we want to place;
var count = 150;

// Create a symbol, which we will use to place instances of later:
var path = new Path.Circle(new Point(0, 0), 5);
path.style = {
  fillColor: 'white',
  strokeColor: 'black'
};

var symbol = new Symbol(path);

// Place the instances of the symbol:
for (var i = 0; i < count; i++) {
  // The center position is a random point in the view:
  var center = Point.random() * view.size;
  var placed = symbol.place(center);
  placed.scale(i / count);
  placed.data = {};
  placed.data.vector = new Point({
    angle: Math.random() * 360,
    length : (i / count) * Math.random() / 5
  });
}

var vector = new Point({
  angle: 45,
  length: 0
});

var mouseVector = vector.clone();

function onMouseMove(event) {
  mouseVector = view.center - event.point;
}

// The onFrame function is called up to 60 times a second:
function onFrame(event) {
  vector = vector + (mouseVector - vector) / 30;

  // Run through the active layer's children list and change
  // the position of the placed symbols:
  for (var i = 0; i < count; i++) {
    var item = project.activeLayer.children[i];
    var size = item.bounds.size;
    var length = vector.length / 10 * size.width / 10;
    item.position += vector.normalize(length) + item.data.vector;
    keepInView(item);
  }
}

function keepInView(item) {
  var position = item.position;
  var itemBounds = item.bounds;
  var bounds = view.bounds;
  if (itemBounds.left > bounds.width) {
    position.x = -item.bounds.width;
  }

  if (position.x < -itemBounds.width) {
    position.x = bounds.width + itemBounds.width;
  }

  if (itemBounds.top > view.size.height) {
    position.y = -itemBounds.height;
  }

  if (position.y < -itemBounds.height) {
    position.y = bounds.height  + itemBounds.height / 2;
  }
}
The code does some pretty interesting things considering how concise the code is. For one, look at the allocation of Path objects (circles) which happens in the code beneath the “// Create a symbol” comment. All the code does is iterate and create. Next, look at how the code retrieves the objects and updates the path positions in the code beneath “// Place the instances of the symbol”. All this does is use the project.activeLayer.children[i] accessor to retrieve the path objects. The first example of this uses a random value to place the objects. The second example of this code pattern uses a (mouse position) vector to move and animate the path objects. In summary, the first time I looked at the code, my jaw dropped at how simple it is.

 

Setting my background to canvas
After seeing the demo, I decided I wanted… no had to … set this as a background for my blog. After some thinking, I decided to use a div element to control the canvas, and then shim this item to the back of my site, stretched full page. The following code shows how this was done:
<!-- crazy paper js canvas -->
<div id="backgrounddiv" style="position:absolute; top:0; left:0; z-index:-1">
  <canvas id="canvas" resize style='background:4D9BCC'></canvas>
</div>
All this does is position the div so that the other elements aren’t “moved” by it, moves it to the back (z-index -1) and then fills the canvas to the size of the screen. Of note, the behavior of
Internet Explorer diverges from Mozilla and Chrome in that events will bubble to the -1 z-index which blocks the div (and canvas) from seeing and responding to events. I’m working on a fix for this but as of writing, the code only works in IE (*sigh*).
Hacking the code for fun
What I wanted to do was create an animation that would swoosh rectangles across my background in a “deco” sort of manner and wanted to constrain the animation to the horizontal axis. To modify the code for this enhancement, all I had to do was change the shapes that were created in the allocation code, replace the mouse vector with one that I set up, and then … wait, that’s all I did!
Changing the shape:
	// Create a symbol, which we will use to place instances of later:
	var path = new Path.Rectangle(new Point(0, 0), new Point(500, 45));
	path.style = {
		fillColor: 'black',
		strokeColor: 'black'
	};
Tinkerers, for a good time… try changing the shape for the code here. For example, you could change this to a circle, square, or any other path.
Changing the motion vector:
	var slideVector = new Point({angle: 0, length: 100});

	// The onFrame function is called up to 60 times a second:
	function onFrame(event) {
		vector = vector + (slideVector - vector) / 30;

		// Run through the active layer's children list and change
		// the position of the placed symbols:
		for (var i = 0; i &lt; count; i++) {
			var item = project.activeLayer.children[i];
			var size = item.bounds.size;
			var length = vector.length / 10 * size.width / 10;
			item.position += (vector.normalize(length) / 10) + item.data.vector;
			keepInView(item);
		}
	}
Another great spot for hacking. If you play around with the vector here, you can make the shapes move faster and slower.

 

But when you scroll it’s broken!
An astute reader noticed that when the content of the page scrolls past the bounds of the canvas (which paper.js stretches). I initially didn’t want the behavior of the decoration to extend beyond the fold but for fun, I decided to make the div stay in the reader’s view. To do this, I slid the div while the user scrolled. The following code was added:
window.onscroll = scroll;
var scrollY = 0;
var posY    = 0;

function smoothMove(){
  if (scrollY != posY){
    if (scrollY &gt; posY){
      posY += 1;
    }else{
      posY -= 1;
    }
    var bgDiv   = document.getElementById("backgrounddiv");
    bgDiv.style.top = posY + "px";
    setTimeout("smoothMove()", 5);
  }
}

function scroll()
{
  scrollY = window.pageYOffset;
  setTimeout("smoothMove()", 5);
}
The window.onscroll event handler will add a handler for when the user scrolls. This will invoke the smoothMove function which will then create timer events that call itself to smoothly update the position of the div containing the canvas. Note I added the smoothmove code because moving the div immediately would cause jerkiness in the decoration. Something also of note here, every time that you scroll, a new thread will be instantiated that will call the smoothmove function. More scrolls = more threads = faster scrolling. Kinda neat huh (or kinda unintended?)! If you wanted to prevent the smoothing from accelerating with each successive scroll call, you could use a lock on the smoothing function or could have a timer function that would always be “scrolling” but that would stop itself once the position to move equals zero. JavaScript race conditions are fun 🙂

 

Ahhh! My eyes, make it stop
After looking at the page a couple times, I realized just how ridiculously distracting the animation is (cool, yes, but distracting…). So, I decided to turn it off but still let you enable it by clicking on the background. Since paper.js has its own interaction model, I couldn’t just add an onClick handler to the div. Instead, I did the following in the script above the paper.js code and then added a switch in the animation render code:
var toggleEnable = false;

function toggleAnimation(){
  if (toggleEnable){
    toggleEnable = false;
  }else{
    toggleEnable = true;
  }
}
function onMouseDown(event) {
  toggleAnimation();
}
function onFrame(event) {
        if (toggleEnable){
        [...]
        }
 }
Now, clicking on the background will make it start (or stop). Similar code was added to the background div through the onClick handler.
Thoughts, conclusions
Writing and hacking code like this makes me want to say something along the lines of, “kids, when I was your age… we had to wait for the turtle to draw before we could add another UI element” because it is so darn easy to code with these libraries and the code is so mind-boggling performant. Paper.js is a great example of modern development: it’s a simple, fast, and compatible library that even in this early stage shows promise and gets developers and designers excited.
Also of note, I randomly discovered a bug in the Space example: when objects fall off screen on the left side of the screen, the repositioning code will actually position them to the right (off screen) which results in the objects disappearing until (or unless) they are positioned back on screen with a velocity moving right. Perhaps this is by design (ohh those crazy interaction designers).
The following fix to the Space.html example will keep objects on screen even if they fall off the left side of the screen:
// The amount of symbol we want to place;
var count = 150;

// Create a symbol, which we will use to place instances of later:
var path = new Path.Circle(new Point(0, 0), 5);
path.style = {
  fillColor: 'white',
  strokeColor: 'black'
};

var symbol = new Symbol(path);

// Place the instances of the symbol:
for (var i = 0; i &lt; count; i++) {
  // The center position is a random point in the view:
  var center = Point.random() * view.size;
  var placed = symbol.place(center);
  placed.scale(i / count);
  placed.data = {};
  placed.data.vector = new Point({
    angle: Math.random() * 360,
    length : (i / count) * Math.random() / 5
  });
}

var vector = new Point({
  angle: 45,
  length: 0
});

var mouseVector = vector.clone();

function onMouseMove(event) {
  mouseVector = view.center - event.point;
}

// The onFrame function is called up to 60 times a second:
function onFrame(event) {
  vector = vector + (mouseVector - vector) / 30;

  // Run through the active layer's children list and change
  // the position of the placed symbols:
  for (var i = 0; i &lt; count; i++) {
    var item = project.activeLayer.children[i];
    var size = item.bounds.size;
    var length = vector.length / 10 * size.width / 10;
    item.position += vector.normalize(length) + item.data.vector;
    keepInView(item);
  }
}

function keepInView(item) {
  var position = item.position;
  var itemBounds = item.bounds;
  var bounds = view.bounds;
  if (itemBounds.left &gt; bounds.width) {
    position.x = -item.bounds.width;
  }

  if (position.x &lt; -itemBounds.width) {
    position.x = bounds.width - itemBounds.width;
  }

  if (itemBounds.top &gt; view.size.height) {
    position.y = -itemBounds.height;
  }

  if (position.y &lt; -itemBounds.height) {
    position.y = bounds.height  + itemBounds.height / 2;
  }
}