In my last post, looking at “typed” function overloading, I mentioned that I was working on a new project aiming to simplify animations in paper.js, and I’m pleased to say that today I was able to get the project to what I would consider a useable form. I’m also pleased to mention that I completely changed the approach that I was taking with the api, with the effect that it is simpler to use, and has also allowed me to do away with all the function overloading stuff! (Don’t worry, I’m not a hypocrite; I said at the beginning of the post that it was something I disagreed with in Javascript!).

Anyway, check it out over on GitHub, I’d love to hear any opinions or feedback.

 

Usage

paper.animate.js has been designed to integrate easily with existing paper.js code. Simply prefix a transform command withanimate(duration, updater, chain) / animate(duration, chain). This returns a proxy object, PaperAnimate.AnimationProxy, with overloads for all the transform methods found on paper.Item. To run the animations, call the update() method of the proxy from the paper.js view onFrame event handler. It’s easiest to use the PaperAnimate.Updater to make this easier and to automatically clear up any finished animations (more info below).

var shape = ...
var updater = new PaperAnimate.Updater();

function onFrame(e) { // paper.js onFrame event
    updater.update(e);
}

// Translate
shape.translate(new Point(100, 50); // paper.js
shape.animate(1, updater).translate(new Point(100, 50)); // paper.animate.js

// Rotate
shape.rotate(90); // paper.js
shape.animate(4, updater).rotate(90); // paper.animate.js

// Chaining
shape.animate(2, updater, true) // 2 secs, chained = true
     .scale(2)
     .shear(0.1, 0.3);

// Animate Shape (paper.animate.js only)
shape.animate(2, updater).replaceShape(differentShape);

// Not using PaperAnimate.Updater
var anim = shape.animate(1).rotate(45);
function onFrame(e) {
    anim.update(e);
}

Params

animate(duration[, updater, chain]) or animate(duration[, chain])

  • duration (Number): Specifies, in seconds, the duration of the animation. If unspecified then defaults to 1 second
  • updater (PaperAnimate.Updater): Makes it simple to work with and update many animations
  • chain (Boolean): Allows chaining commands by returning PaperAnimate.AnimationProxy object rather than originalpaper.Item. Defaults false, in keeping with paper.js api (returning item)

Supported Transformations

paper.animate.js supports all the transformations currently implemented by paper.js:

  • scale(scale[, center])
  • scale(hor, ver[, center])
  • translate(delta)
  • rotate(angle[, center])
  • shear(point[, center])
  • shear(hor, ver[, center])
  • transform(matrix, flags)
  • fitBounds(rectangle[, fill])

PaperAnimate.Updater

The updater is simply an easy way to manage animations. Rather than having to maintain a list of all the animation proxies returned every time animate() is called, simply pass in the reference to the updater and then call update() only on that. Additionally, the updater will remove animations when they have completed

Before I start, I want to go on the record to say that I do not agree with using function overloads in javascript. Javascript doesn’t really support function overloading, and overloaded functions (at least in my experience) seem to lead to unclear code; the better solution is to pass in an object and determine behaviour from that.

That said, the other day I did have to implement function overloading, for a new project I started which aims to simplify basic animations when using paper.js. I wanted my solution to integrate nicely with existing code written for paper, so I decided to use a proxy object which intercepts the tranform function calls on an item and animates it, and for this reason my proxy object needed to implement matching method signatures.

Typically when people use function overloading in javascript, they are dealing with different numbers of arguments, but paper.js complicates things slightly as there can be ambiguities in function calls which can be resolved not by counting the number of arguments but by looking at the “types” of the arguments.

  • scale(Number:scale [, Point:center])
  • scale(Number:hor, Number:ver[, Point:center])

When calling something.scale(a,b), we cannot know which function to use without further inspecting b. I briefly googled function overloading in javascript and came up with a few solutions that dealt with overloads which had different numbers of overloads, but nothing that dealt with ambiguities such as the one above, and so I came up with this method:

function selectOverload(that, args, overloads) {
	var types = {
		Number: function(val) { return !isNaN(parseFloat(val)) && isFinite(val); },
		Point: function(val) { return val.x !== undefined && val.y !== undefined; }
	};
	for (var o = 0; o < overloads.length; o++) {
		var overload = overloads[o],
			matches = true;
		if (args.length > overloads[o].params.length) continue;
		for (var a = 0; a < args.length; a++) {
			if (!types[overload.params[a]](args[a])) {
				matches = false;
				break;
			};
		}
		if (matches) { return overload.fn.apply(that, args); }
	}
}

And it gets used like this:

AnimationProxy.prototype.scale = function() {
	selectOverload(
		this, arguments,
		[{
			params: ["Number","Point"],
			fn: function(scale, center) {
				// code for this overload here
			}
		},
		{
			params: ["Number","Number","Point"],
			fn: function(hor, ver, center) {
				// code for this overload here
			}
		}]
	);
	return this;
}

Although not perfect, I’m pretty pleased with this code. I think the intention is pretty clear, and like that the arguments are correctly named. It doesn’t actually check the type of the argument; the “type definitions” are actually just boolean functions which return true or false if an argument meets their “type criteria”, and so this can easily be used on a whole variety of scenarios.

Also, I made the decision to pass a string value in for the type and have the type defs in the selectOverload function, but that’s just because it suited the situation I was working with, but there’s no reason you can’t adapt the code to use the criteria function itself. In my situation I didn’t feel it would be as readable, but if you have a lot of different types then it may work out more efficient.