- Requires: Paper.js (http://paperjs.org/)
- Project time: 2-3 hours
Before we jump into a bottom-top approach for the code of this tutorial, we are going to briefly look at the history of Paper.js and its predecessor, Scriptographer, as well as how the objects are organised within the framework from a top-bottom view.
A scripting plug-in for Illustrator? If you haven’t used Scriptographer, you’re missing out on an enormous amount of fun, but more importantly, it’s a concept that is arguably a fundamental asset to the modern day designer – and that is the ability to combine code and design in order to automate the design process and extend what is creatively possible through Generative Design.
“Generative design is a design method where the output – image, sound, architectural models, animation – is generated by a set of rules or an Algorithm, normally by using a computer program.” – Wikipedia
The document object model
To understand the hierarchy of Paper.js’ DOM, it may be easier to use Illustrator as a metaphor.
In the image above, we have: 1) A Document open in Illustrator that has several Layers 2) On the selected (Active) Layer ‘HAUS Logo’ 3) there is a Compound Path.
The structure is the same in the DOM for Paper.js, except a Document is referred to as a Project. Therefore, the code would step through each object like so: Project.Layer.CompoundPath.
I typically begin by creating variables that are references to the current items (layers, symbols, views, etc) of the project. In this case, we are only interested in the current activeLayer of the project, which we will later use when it comes time to centre the content that is contained on the layer.
- var layer = project.activeLayer;
The currentStyle defines the current style. All items that are either selected or created after defining the style will use this style.
- project.currentStyle.fillColor = new RGBColor(1.0, 1.0, 1.0);
Creating rectangle and circle paths
If you recall in Image 1, there is a rectangle and circle that together create the Compound Path. New shaped Path Items (Line, Rectangle, RoundRectangle, Oval, Circle, Arc, RegularPolygon and Star) can be created using a variety of techniques, including defining the point and size:
- // Path.Rectangle(point, size)
- var rect = new Path.Rectangle([200, 200], [300, 300]);
Note: The point of origin of a Rectangle is in the top-left corner.
To match the HAUS logo, we need to rotate the rectangle 45º:
Next, we create the centre circle in the logo, defining the centre point and the radius:
- // Path.Circle(center, radius)
- var circ = new Path.Circle(rect.position, 29);
Note that even though we created a new circle, it is not visible. It is actually there, however, it is the same color as the rectangle. Looking closer at Image 1, notice that the circle creates a hole in the centre of the rectangle. This is a feature of the CompoundPath.
Combining them into a CompoundPath
To create the CompoundPath, we create an instance of it and include the PathItems we previously created.
- var shape = new CompoundPath(rect, circ);
- shape.fillColor = new RGBColor(0.95, 0.95, 0.95);
The circ now creates a hole in the middle of the rectangle. Now it is time to add some of Claus’ magic highlights & shadows. Woohoo!
Shadows and highlights
There are two approaches to adding shadows and highlights to the CompoundPath: using the HTML5 Canvas Shadows API, or faking it. Claus’s first attempt was to use the Shadows API. Unfortunately, both Safari and Chrome round the shadowOffsetX and shadowOffsetY parameter values to whole integers, which lead to bumpy animations in those browsers further down the road.
To fake the shadow and highlight, we will use the clone() function, position and fillColor properties. After creating the CompoundPath, we pass this instance to the makeShadow() method, which will take the shape, clone it twice for shadows, twice for highlights, and sets position and fillColor for each of the cloned shapes. It will then take the four cloned shapes along with the original shape and return them as a group item.
- function makeShadow(path)
- var shadow1 = path.clone();
- shadow1.fillColor = new RGBColor(0.92, 0.92, 0.92);
- var shadow2 = path.clone();
- shadow2.fillColor = new RGBColor(0.84, 0.84, 0.84);
- var highlight1 = path.clone();
- highlight1.fillColor = new RGBColor(1, 1, 1);
- var highlight2 = path.clone();
- highlight2.fillColor = new RGBColor(0.97, 0.97, 0.97);
- return new Group([highlight2, highlight1, shadow1, shadow2, path]);
If you viewed it now, the new shadows and highlight CompoundPaths would be there; however, they would be hiding under the original CompoundPath.
To bring them out of hiding, there is another trick we need to do: offset the shadows and highlights from the original CompoundPath.
Offsetting shadows and highlights
We need to call the method adjustShadow() in order to offset the Shadow and Highlight CompoundPaths that are currently hiding underneath the original CompoundPath.
- var group = makeShadow(shape);
- adjustShadow(group, new Point(1, 1));
So after the Shadow and Highlight CompoundPaths are created, their positions are offset, so they are now visible thanks to the following method:
- function adjustShadow(group, vector)
- var position = group.lastChild.position;
- group.children.position = position + vector * 3;
- group.children.position = position + vector * 1.5;
- group.children.position = position + vector * -3;
- group.children.position = position + vector * -1.5;
Tool: mouse event handlers
The last bit of magic we want to add is the ability to adjust the direction of the light source based on the location of the mouse.
By adding a method for onMouseMove, we want to figure out the distance between the centre of the group CompoundPaths and the location of the mouse. Afterward, we clamp the length of the vector with a minimum and maximum. This in turn creates the illusion that we are controlling the direction of the light.
Special thanks to Jonathan for taking the time to make excellent suggestions for tightening up this article.
Some examples for Paper.js were derived from here: processing.org/learning/topics/follow3.html