Follow me if you want to grow your full-stack JavaScript skills with my screencasts and courses.

Canvas with paper.js

More about paper.js

As an introduction, I would be hard pressed to explain it better than it's website, so I'll paraphrase a few quotes for you:

Paper.js is an open source vector graphics scripting framework that runs on top of the HTML5 Canvas

Paper.js is not simply a wrapper around the Canvas

A Scene Graph / Document Object Model for vector graphics: Work with nested layers, groups, paths, compound paths, rasters, symbols etc.

PaperScript, a simple extension of JavaScript, allowing the scoped execution of scripts without polluting the global scope, the execution of multiple scripts per page in their separate sand-boxed scopes while sharing the library code, and adding support for operator overloading to any object.

To take paperjs for a spin, we're going to build and animate the tagtree logo using paperjs. At the end of this screencast we will end up with exactly what you see here. We will also make sure that it renders correctly according to the size of the window, which is a huge benefit of working with vectors.

Let's get started by creating a simple project that pulls in paperjs. We install paper using bower, if you don't use bower, you should. If you still don't want to, you could download it using git or possibly downloading it manually like a peasant.

terminal
    
    $ mkdir ttlogo && cd ttlogo
    $ bower install paper
    
  

I'm creating an index.html file that will contain our canvas. I've precoded this page. Its very simple, a standard html file, referencing paperjs, it has a canvas. The only interesting part is the reference to tagtree.js, as you can see it has two interesting attributes. The type attribute is paperscript. That's because paper extends javascript to allow you to take some shortcuts, specifically with operators, so for example it will allow you to multiply a size object by 3.

index.html
    
    <html>
      <head>
        <script src="bower_components/paper/dist/paper.js"></script>
      </head>
      <body>
        <canvas style="background-color: #eeeeed" id="tagtree" resize></canvas>

        <script type="text/paperscript" src="tagtree.js" canvas="tagtree"></script>
      </body>
    </html> 
    
  

We also create a tagtree.js file, which the html file expects to be there. We're going to do all the rest of the work in this file.

One last thing before we get down and dirty with paper. We need to host this so that we can hit it up in a browser. I usually use the built in python webserver module for simple projects like this, and yeoman with grunt for more complex stuff.

terminal
    
    $ python -m SimpleHTTPServer
    
  

Drawing the badge rectangle

Let's start with something simple. We're going to code up a rectangle, which is basically a collection of point and size objects, which is used by a specialized path object which knows how to use the config to render a rectangle to the canvas.

We also want to fill the path with this particular color. We want a white border, and a pretty wide stroke around the path.

And we've got a rectangle!

Let's rotate that path by 45 degrees so that it looks like the logo in the final product.

tagtree.js
    
    var rectangle = new Rectangle([200, 200], [585,585]);
    var path = new Path.Rectangle(rectangle);
    path.style = {
      fillColor: '#272727',
      strokeColor: '#fff',
      strokeWidth: 20
    };

    path.rotate(45);

    
  

Dynamic sizing and positioning of badge rectangle

We started with the simplest solution first, which was to hard code all our locations and sizes. But we would want this logo to scale according to the size of the canvas. That's what vector drawings are all about. To do that, we need to introduce some variables at the top here, don't worry about global variables, because paper compiles paperscript to be fully isolated. We pull out the canvas size. From there we work out which is the biggest, the width or the height, that's what we're going to use for the maximum size we can draw the rectangle in. And we pull out the center of the canvas.

tagtree.js
    
    var canvasSize = this.view.size;
    var maxSize = Math.min(canvasSize.width, canvasSize.height);
    var center = this.view.center;
    
  

Now we get to calculate our relative size using the maxSize. What we're basically saying here is that we want the rectangle to have the width and height of 90% the maximum drawable area.

We also want to position the rectangle relative to the canvas dimensions, and we want to position it in the center. To do this, we calculate the top left position of the rectangle from the center of the canvas by subtracting half the height, and half the width. Now it's just a matter of plugging in these values to the rectangle configuration.

The stroke is still hardcoded, so let's make that relative. That looks cool.

tagtree.js
    
    var canvasSize = this.view.size;
    var maxSize = Math.min(canvasSize.width, canvasSize.height);
    var center = this.view.center;
    var strokeWidth = maxSize * 0.02;

    var rectSize = new Size(maxSize * 0.6, maxSize * 0.6);
    var topLeftPositionForRect = new Point(center.x - rectSize.width/2,
      center.y - rectSize.height/2);
    var rectangle = new Rectangle(topLeftPositionForRect, rectSize);
    var path = new Path.Rectangle(rectangle);
    path.style = {
      fillColor: '#272727',
      strokeColor: '#fff',
      strokeWidth: 20
    };

    path.rotate(45);
   
  

OOP'ifying the badge

We need to do a bit of cleanup here. This code needs a bit of structure, because we're going to add a lot more soon.

Let's introduce a bit of OOP here, to make this rectangular badge an object called badge. So we start by defining the Badge function.

Now we flesh out its prototype, setting the function as the it's constructor. We'll move the drawing code to it's draw function, so we copy all this stuff directly into it. Let's return the badge object from the draw function, it lends itself to a more fluent interface, as you'll see a bit later on.

tagtree.js
    
      function Badge(){

      }

      Badge.prototype = {
        constructor: Badge,
        draw: function(){
          var rectSize = new Size(maxSize * 0.6, maxSize * 0.6);
          var topLeftPositionForRect = new Point(center.x - rectSize.width/2,
            center.y - rectSize.height/2);
          var rectangle = new Rectangle(topLeftPositionForRect, rectSize);
          var path = new Path.Rectangle(rectangle);
          path.style = {
            fillColor: '#272727',
            strokeColor: '#fff',
            strokeWidth: strokeWidth
          };

          path.rotate(45);
        }
      }
    
  

Now we can construct this badge object, and call draw on it.

tagtree.js
    
      var badge = new Badge().draw();
    
  

Drawing a branch of the logo

Now we're going to build the tree in the logo.

We're taking the same approach as we did with the badge, we're going to hardcode the crap out of it, then refactor afterwards.

We start by creating a rectangle for the bottom right branch.

I chose to draw the branches from left to right and then rotating from it's left point, that's why I am using a negative angle here.

tagtree.js
    
      var rectangle = new Rectangle(new Point(956, 665), new Size(304, strokeWidth));
      var path = new Path.Rectangle(rectangle);
      path.rotate(-45);
      path.style = {
        fillColor: '#fff'
      }
    
  

As you can see this doesn't look right. The branch is to close to the edge of the badge, that's because it rotating the branch from it's center, as is the default with paper. Luckily we can specify a rotation point for the path, so we calculate the left part of the branch, taking care to specify the vertical center.

tagtree.js
    
      path.rotate(-45, new Point(path.bounds.topLeft.x, path.bounds.topLeft.y + strokeWidth/2));
    
  

That looks promising.

Let's test this code to see if we can use it for the other branches as well. We duplicate it, and change the angle, so that we can render a branch on the left.

tagtree.js
    
    var rectangle = new Rectangle(new Point(956, 665), new Size(304, strokeWidth));
    var path = new Path.Rectangle(rectangle);
    path.rotate(-45, new Point(path.bounds.topLeft.x, path.bounds.topLeft.y + strokeWidth/2));
    path.style = {
      fillColor: '#fff'
    }


    rectangle = new Rectangle(new Point(956, 665), new Size(304, strokeWidth));
    path = new Path.Rectangle(rectangle);
    path.rotate(-135, new Point(path.bounds.topLeft.x, path.bounds.topLeft.y + strokeWidth/2));
    path.style = {
      fillColor: '#fff'
    }
    
  

Its looking like we can reuse this code for all the branches, but we need to make it relative as we did with the badge.

Relative size and position for branches

We want to render the branch in the center of the badge, so it makes sense to make the badge's path available to code on the outside, so let's just set it on the object so that it can be retrieved externally.

tagtree.js
    
    Badge.prototype = {
      constructor: Badge,
      draw: function(){
        var rectSize = new Size(maxSize * 0.6, maxSize * 0.6);
        var topLeftPositionForRect =
        new Point(center.x - rectSize.width/2,
        center.y - rectSize.height/2);
        var rectangle = new Rectangle(topLeftPositionForRect,
        rectSize);
        this.path = new Path.Rectangle(rectangle);
        this.path.style = {
          fillColor: '#272727',
          strokeColor: '#fff',
          strokeWidth: strokeWidth
        };

        this.path.rotate(45);
        return this;
      }
    };
    
  

Now we can use the badge path to calculate the origin for our branch. Horizontally we want it at the center of the canvas, as our rectangle is also positioned in the center of the canvas. Vertically we start from the top of the rectangle and use the maxsize to calculate a relative ratio that positions the branch. We could've opted to use the rectangle size too, but I opted to use the maxSize variable we pulled out already at the top of the script.

Let's use this origin for both of the branches. We do the same for the branch size.

tagtree.js
    
    var badge = new Badge().draw();

    var originPoint = new Point(center.x, badge.path.bounds.topLeft.y + (maxSize * 0.58));
    var rectangle = new Rectangle(
      originPoint,
      new Size(304, strokeWidth)
    );
    var path = new Path.Rectangle(rectangle);
    path.rotate(-45, new Point(path.bounds.topLeft.x, path.bounds.topLeft.y + strokeWidth/2));
    path.style = {
      fillColor: this.color || '#fff'
    };
    
  

OOP'ify the branch logic

As we did with the badge, we want to create a Branch object we can use for all the branches. So we do this using the combination constructor/prototype pattern again. This time I want some arguments available to the constructor. On our object, we want the draw function, exactly like the badge object earlier.

tagtree.js
    
    function Branch(args){
    }

    Branch.prototype = {
      constructor: Branch,
      draw: function(){
        return this;
      }
    }
    
  

OK, let's start pulling out some variables from the args. We want a relative size, a relative origin, a position (right, left or center). Fow now we just hardcode the angle.

tagtree.js
    
    function Branch(args){
      this.size = args.size;
      this.origin = args.origin;
      this.position = args.position;
      this.rectTop = args.rectTop;
      this.angle = -45;
    }

    Branch.prototype = {
      constructor: Branch,
      draw: function(){
        return this;
      }
    }
    
  

Let's take what we've learned earlier and flesh out the draw function. We calculate the origin, targeting the horizontal center, and a relative vertical value based from the top of the badge, by utilizing our relative origin argument against the maxSize value.

tagtree.js
    
    Branch.prototype = {
      constructor: Branch,
      draw: function(){
        var resolvedOrigin = new Point(center.x, this.rectTop + (maxSize * this.origin));
        return this;
      }
    }
    
  

We construct our rectangle using this origin, and a relative width we get by multiplying our size parameter with the maxSize variable.

tagtree.js
    
    Branch.prototype = {
      constructor: Branch,
      draw: function(){
        var resolvedOrigin = new Point(center.x, this.rectTop + (maxSize * this.origin));
        var rectangle = new Rectangle(
          resolvedOrigin,
          [maxSize * this.size, strokeWidth]
        );
        return this;
      }
    }
    
  

We construct a path from the rectangle config. We rotate the path from the middle of the left edge. And set it's color to white.

tagtree.js
    
    Branch.prototype = {
      constructor: Branch,
      draw: function(){
        var resolvedOrigin = new Point(center.x, this.rectTop + (maxSize * this.origin));
        var rectangle = new Rectangle(
          resolvedOrigin,
          [maxSize * this.size, strokeWidth]
        );
        this.path = new Path.Rectangle(rectangle);
        this.path.rotate(this.angle, [resolvedOrigin.x, resolvedOrigin.y + strokeWidth/2]);
        this.path.style = {
          fillColor: '#fff'
        };

        return this;
      }
    }
    
  

We use our newly created object to draw a branch. We plug in the same values as we used earlier, because all the calcs are still the same.

tagtree.js
    
    var rightBottomBranch - new Branch({size: 0.3, origin: 0.58});
    
  

We hardcoded the angle earlier, to make this dynamic, we call a calculateAngle function, which we add onto the object. We want to support three angles, so we create a simple object which contains an attribute for each angle. Then we just return the correct value based on the position parameter.

tagtree.js
    
    function Branch(args){
      ...
      this.angle = this.calculateAngle(args.position);
    }

    Branch.prototype = {
      ...
      calculateAngle: function(position){
        var angles = {
          left: -135,
          right: -45,
          center: -90
        };

        return angles[position];
      }
    };


    
  

Let's take this for a spin on our existing branch. And it's really easy to create the left branch as well.

tagtree.js
    
    var rightBottomBranch = new Branch({size: 0.3, origin: 0.58, position: 'right'}).draw();
    var leftBottomBranch = new Branch({size: 0.3, origin: 0.58, position: 'left'}).draw();
    
  

And now for the trunk and the rest of the branches.

tagtree.js
    
    var rightBottomBranch = new Branch({size: 0.3, origin: 0.58, position: 'right'}).draw();
    var leftBottomBranch = new Branch({size: 0.3, origin: 0.58, position: 'left'}).draw();

    var leftMiddleBranch = new Branch({size: 0.178, origin: 0.42, position: 'left'}).draw();
    var rightMiddleBranch = new Branch({size: 0.178, origin: 0.42, position: 'right'}).draw();

    var leftTopBranch = new Branch({size: 0.07, origin: 0.28, position: 'left', color: '#39b54a'}).draw();
    var rightTopBranch = new Branch({size: 0.07, origin: 0.28, position: 'right', color: '#39b54a'}).draw();


    var trunk = new Branch({size: 0.47, origin: 0.65, position: 'center'}).draw();
    
  

As you can see, the top branches have a color parameter, but we don't cater for that yet. Let's fix that.

tagtree.js
    
    function Branch(args){
      ...
      this.color = args.color;
    }

    Branch.prototype = {
      constructor: Branch,
      draw: function(){
        ...
        this.path.style = {
          fillColor: this.color || '#fff'
        }

        return this;
      },
      ...
    }
    
  

Rotating the badge

We've got a static logo drawn on the canvas, but we want it to rotate. We hook into the onFrame event. In here we call the rotate function on the badge object, which we create. In the rotate function, we just rotate the path by 5 degrees, just to make sure that everything is hooked up correctly.

tagtree.js
    
    Badge.prototype = {
      constructor: Badge,
      draw: function(){
      ...
      },

      rotate: function(){
        this.path.rotate(5);
      }
    }

    ...

    var trunk = new Branch({size: 0.47, origin: 0.65, position: 'center'}).draw();

    function onFrame(event){
      badge.rotate();
    }
    
  

At this stage, it looks OK, but we want it to alternate between rotating and idle states.

To facilitate the different rotation states of the badge, we're going to introduce a really simple statemachine in the constructor function.

We start wit the idle state, we want it to stay in this state for a duration of 15 frames, and go to the twirling state once it's done.

We want it to stay in the twirling state until it's completed rotating 360 degrees and go to the idle state once it's done, and we want to rotate it 5 degrees per frame.

We also set the default state to idle.

tagtree.js
    
    function Badge(){
      this.states = {
        idle: {duration:150, target: 'twirling'},
        twirling: {degrees:360, target: 'idle', increment: 5}
      };

      this.state = this.states.idle;
    }
    
  

We hook the rotate function up to the statemachine. We want to keep track of the number of frames we've spent on a state, so we ensure that the state has a frameCount value, defaulting it to 0 if it hasn't. Then we increment the frameCount for the current state.

tagtree.js
    
    ...
    rotate: function(){
      this.state.frameCount = this.state.frameCount || 0;
      this.state.frameCount++;
    }
    ..
    
  

If it has an increment variable, we know we can rotate the badge by the increment value.

tagtree.js
    
    ...
    rotate: function(){
      this.state.frameCount = this.state.frameCount || 0;
      this.state.frameCount++;

      if(this.state.increment){
        this.path.rotate(this.state.increment);
      }
    }
    ...
    
  

In the same way as we store the frameCount, we also want to keep track of how far we've rotated it. So we make sure it has a rotation value, and we default it to 0 if it doesn't. Then we increment the rotation value.

tagtree.js
    
    ...
    rotate: function(){
      this.state.frameCount = this.state.frameCount || 0;
      this.state.frameCount++;

      if(this.state.increment){
        this.path.rotate(this.state.increment);
        this.state.rotation = this.state.rotation || 0;
        this.state.rotation += this.state.increment;
      }
    }
    ...
    
  

To figure out whether we can exit a certain state, we call a function called isLastFrameForCurrentState, and then create the function.

tagtree.js
    
    ...
    Badge.prototype = {
      ...
      isLastFrameForCurrentState: function(){
        ...
      },

      rotate: function(){
        ...

        if(this.isLastFrameForCurrentState()){
          ...
        }
      }
    }
    ...
    
  

If this function returns true, we transition to the next state.

tagtree.js
    
    ...
    if(this.isLastFrameForCurrentState()){
      this.state.frameCount = 0;
      this.state.rotation = 0;
      this.state = this.states[this.state.target];
    }
    ...
    
  

We've got two values that can cause us to transition to a next state: the number of frames we've been in a state, or the number of degrees we've rotated while in the state.

tagtree.js
    
    ...
    isLastFrameForCurrentState: function(){
      return this.state.duration == this.state.frameCount || this.state.degrees == this.state.rotation;
    },
    ...
    
  

At this moment it alternates between the two states quite nicely, but the transition between the idle and twirling states is a bit jerky. We create additional easing states which rotates a bit slower (less degrees per frame). After this, when it transitions from twirling to idle it seems to lose some speed before stopping and when it transitions from idle to twirling it seems to pick up a bit of speed before it twirls at full speed.

tagtree.js
    
    function Badge(){
      this.states = {
        idle:       {duration: 150,    target: 'easeOut'},
        easeOut:    {degrees: 20,      target:'twirling',    increment:1},
        twirling:   {degrees: 320,     target:'easeIn',      increment:5},
        easeIn:     {degrees: 20,      target:'idle',        increment:1},
      };

      this.state = this.states.idle;
    }
    
  

Let's have a look at the whole file as it should look now.

tagtree.js
    
    var canvasSize = this.view.size;
    var maxSize = Math.min(canvasSize.width, canvasSize.height);
    var center = this.view.center;
    var strokeWidth = maxSize * 0.02;


    function Badge(){
      this.states = {
        idle:       {duration: 150,    target: 'easeOut'},
        easeOut:    {degrees: 20,      target:'twirling',    increment:1},
        twirling:   {degrees: 320,     target:'easeIn',      increment:5},
        easeIn:     {degrees: 20,      target:'idle',        increment:1},
      };

      this.state = this.states.idle;
    }

    Badge.prototype = {
      constructor: Badge,
      draw: function(){
        var rectSize = new Size(maxSize * 0.6, maxSize * 0.6);
        var topLeftPositionForRect = new Point(center.x - rectSize.width/2,
          center.y - rectSize.height/2);
        var rectangle = new Rectangle(topLeftPositionForRect, rectSize);
        this.path = new Path.Rectangle(rectangle);
        this.path.style = {
          fillColor: '#272727',
          strokeColor: '#fff',
          strokeWidth: strokeWidth
        };

        this.path.rotate(45);

        return this;
      },

      isLastFrameForCurrentState: function(){
        return this.state.duration == this.state.frameCount || this.state.degrees == this.state.rotation;
      },

      rotate: function(){
        this.state.frameCount = this.state.frameCount || 0;
        this.state.frameCount++;

        if(this.state.increment){
          this.path.rotate(this.state.increment);
          this.state.rotation = this.state.rotation || 0;
          this.state.rotation += this.state.increment;
        }

        if(this.isLastFrameForCurrentState()){
          this.state.frameCount = 0;
          this.state.rotation = 0;
          this.state = this.states[this.state.target];
        }
      }
    }

    var badge = new Badge().draw();

    function Branch(args){
      this.size = args.size;
      this.origin = args.origin;
      this.position = args.position;
      this.rectTop = badge.path.bounds.topLeft.y;
      this.angle = this.calculateAngle(args.position);
      this.color = args.color;
    }

    Branch.prototype = {
      constructor: Branch,
      draw: function(){
        var resolvedOrigin = new Point(center.x, this.rectTop + (maxSize * this.origin));
        var rectangle = new Rectangle(resolvedOrigin, new Size(maxSize * this.size, strokeWidth));
        this.path = new Path.Rectangle(rectangle);
        this.path.rotate(this.angle, new Point(resolvedOrigin.x, resolvedOrigin.y + strokeWidth/2));
        this.path.style = {
          fillColor: this.color || '#fff'
        }

        return this;
      },

      calculateAngle: function(position){
        var angles = {
          left: -135,
          right: -45,
          center: -90
        };

        return angles[position];
      }
    }

    var rightBottomBranch = new Branch({size: 0.3, origin: 0.58, position: 'right'}).draw();
    var leftBottomBranch = new Branch({size: 0.3, origin: 0.58, position: 'left'}).draw();

    var leftMiddleBranch = new Branch({size: 0.178, origin: 0.42, position: 'left'}).draw();
    var rightMiddleBranch = new Branch({size: 0.178, origin: 0.42, position: 'right'}).draw();

    var leftTopBranch = new Branch({size: 0.07, origin: 0.28, position: 'left', color: '#39b54a'}).draw();
    var rightTopBranch = new Branch({size: 0.07, origin: 0.28, position: 'right', color: '#39b54a'}).draw();


    var trunk = new Branch({size: 0.47, origin: 0.65, position: 'center'}).draw();

    function onFrame(event){
      badge.rotate();
    }
    
  

Wrap up

That's it for this episode on paper.js. After playing around with paper, my key take away was that it's important to utilize OOP concepts to keep the code manageable.

See you next time!