An Introduction to Snap.svg
Although SVG has been around for more than a decade, it became popular in the last few years as a way to draw charts in web applications, thanks to some great libraries that have made beautiful charts and drawings effortlessly available to developers: in particular D3.js for charts and Raphaël for cool SVG drawings and animations.
New outstanding libraries have been recently emerging; they provide front-end developers and designers with new approaches and amazing new features:
- Snap.svg, as we are going to see, offers the newest SVG features like masking, clipping, patterns, gradients, etc…
- PathsJs is a minimal library for SVG-based charts creation. It’s designed to support reactive programming by generating SVG paths that can be used with template engines. It works best with a mustache-based template engine, such as Ractive.
- Although it is not SVG-based, P5 deserves a mention. It is an attempt, and apparently a good one, to overcome traditional issues affecting the HTML5 canvas element – interaction in particular.
In the rest of this article, we are going to take a good look at Snap.svg, starting from the basics.
Raphaël
If you haven’t had a chance to take a look at Raphaël, you probably should. It’s a nice piece of JavaScript created as a solo project by Dmitry Baranovskiy. Although it started as a personal project, the result is remarkable for interface (very clear and consistent), performance, and appearance (especially for animations). The library is more oriented toward “freehand” drawing and animations rather than charts. The gRaphaël extension was later released to address this, but it hasn’t become as popular and widespread as D3.
Despite being ahead of other libraries, in time Raphaël started showing its limits. For example, in order to be compatible with older browsers Raphaël doesn’t support all those cool new SVG features that would make your animations stand out.
That’s why its author decided to start fresh with a new project, Snap.svg, which of course benefits from the experience gathered designing Raphaël. Snap.svg also breaks with the past, allowing the introduction of a whole new kind of special effects.
Oh, Snap!
Before delving into Snap’s syntax and getting started with a few examples, let’s quickly review the pros and cons of this new library:
Pros:
- It supports all the cool features we mentioned above.
- Snap can wrap around and animate existing SVG. You could generate your SVG with tools like Adobe Illustrator, Inkscape, or Sketch, or load strings of SVG asynchronously and query out the pieces you need to turn an SVG file into a sprite.
- It is free and open source.
Cons:
- It’s a low-level library, so if you need to visualize data, unfortunately there is no support for charts yet.
- There is no support for data-binding.
- Snap is a young project that has yet to reach full maturity. It is already great to use for your personal projects, but you have to weight this aspect before using it in a complex one.
As we mentioned, Snap uses features not supported by older browsers. Although a complete, updated compatibility table is not given yet, this library should work fine at least with the following browser versions (and newer):
- Firefox ESR 18
- IE 9.0.8
- Chrome 29
- Opera 24
Getting Started with Snap
After downloading the source files from the GitHub repository, you can unzip them and look for the dist
folder, which contains the built distribution files. For detailed instructions about building snap with Grunt, or for checking for the latest version, take a look here.
Once you’ve copied the minified version of the file inside the js
folder of your new project, just include the script in your HTML page. Assuming it’s located in the root directory of your project, you can just add this line right before the page’s closing body
tag:
<script src="/js/snap.svg-min.js"></script>
Now we are ready to create a drawing area for our vector graphic. We have two ways to do this:
- Create a brand new drawing surface, that will be appended to the page’s DOM (inside
body
). - Re-use an existing DOM element, and wrap it in a Snap structure. You can wrap any element, but for drawing methods you’ll need an SVG element.
The first way allows you to explicitly set width and height of the surface area at creation in the JavaScript code. If you’d like to achieve a greater level of separation between presentation and content, you can use the second way, specifying the values in a CSS rule. At a high level, the first method allows you to adjust the drawing surface’s appearance dynamically, but if you don’t need to, the second way is more MVC-compliant. Moreover, wrapping is what allows you to import and modify SVG drawings created with external tools, as mentioned in the introduction section.
So, for example, to create a new 800-by-600 pixels drawing area, you just need the following line of JavaScript:
var s = Snap(800, 600);
If, instead, you want to wrap an existing one, say #complexSVGfromIllustrator
:
<svg id='complexSVGfromIllustrator' version="1.1" xmlns="https://www.w3.org/2000/svg">
...
</svg>
You can still get away with a single line of JavaScript, to import the drawing surface:
var s = Snap('#complexSVGfromIllustrator');
Side note: for the curious reader: if you inspect the Snap objects after creation, you’ll notice they have a paper
field, testifying to Raphaël’s legacy.
Shapes
Once we have created our drawing surface, our Snap
wrapper, it is time to draw some shapes on it. Let’s say you’d like to draw a circle:
var paper = Snap('#complexSVGfromIllustrator'),
circle = paper.circle(100, 50, 10);
As you can see from the docs, the first two parameters in the circle()
method are the coordinates of its center, while the third one is the circle’s radius. All these parameters are mandatory, and failing to provide them will result in an error being thrown. The circle()
method, as with all the other drawing methods, will return a reference to an object.
You can draw ellipses too, as shown in the following code sample. Vertical and horizontal radii are needed this time. Again, all parameters are mandatory.
var ellipse = paper.ellipse(100, 50, 10, 20);
If you’d like to draw a rectangle, use the following code. This will create a rectangle with its top-left corner at (100px, 100px), a width of 200px, and a height of 200px.
var r = paper.rect(100, 100, 200, 300);
The cool thing about the rect()
method, is that it also accepts two optional parameters that control the radius of rounded corners, independently for vertical and horizontal axes. These parameters default to 0 when not passed, but be careful that if you only pass one (the horizontal radius), the second one will not be set to zero, but instead both will assume the same value.
var rect = paper.rect(100, 100, 200, 300, 10); //equivalent to paper.rect(100, 100, 200, 300, 10, 10);
Now, if you wanted to start from scratch, you could create another drawing surface, or you could just use the paper.clear()
method to erase all drawings from paper
.
Lines and Polygons
To cover more complicated drawings, we need to take a step back, and talk about drawing lines. As you would expect the method takes the four coordinates of a line’s endpoints, as shown below.
var line = paper.line(10, 100, 110, 200);
What’s far more interesting is the possibility to draw complex polylines: var line = paper.polyline(10, 100, 110, 200);
is in principle equivalent to the line()
method above, but you’d probably be surprised by its visual outcome. To see why, let’s try this
var p1 = paper.polyline(10, 10, 10, 100, 210, 20, 101, 120);
paper.polyline()
and paper.polygon()
are aliases for the same method, and by default the resulting (closed) polygon is drawn with black fill and no stroke. That’s why you could not see the line drawn with polyline()
above (although you can check, by inspecting the page, that the SVG code for it has been indeed appended to its container).
To change this behavior, as well as the appearance of other elements, we must introduce attributes.
Attributes
The notion of attributes for Snap elements is somewhat broader than usual, meaning that it includes both HTML attributes and CSS attributes under the same interface (while most other libraries makes a distinction between .attr()
method for HTML attributes and ‘.style()’ for CSS ones). By using the element.attr()
method on a Snap wrapper object, you can set its class
or id
, as well as its color or width.
As mentioned above, using Snap you have two ways to assign CSS properties to an element. One is to include these properties in a separate CSS file, and then just assign the proper class to your element:
.big-circle {
stroke: red;
stroke-width: 2;
fill: yellow;
}
circle.attr({class: 'big-circle'});
The same result can be obtained by assigning these properties using JavaScript:
circle.attr({
stroke: 'red';
stroke-width: 2;
fill: 'yellow';
});
Once again, the first way allows a better separation between content and presentation, while the second one provides the possibility to dynamically change attributes. If you are thinking about mixing the two strategies, keep in mind that the rules defined in a CSS file will trump the one you assign with element.attr()
, despite the temporal order in which they are assigned to elements.
If you haven’t maintained a reference to the element you want to style, don’t worry, you can easily grab it using CSS selectors:
circle = paper.select('circle'); //First circle in paper's DOM tree
circle = paper.select('circle.big-circle'); //First circle in paper's DOM tree which has class 'big-circle'
circle = paper.select('circle:nth-child(3)'); //Third circle in paper's DOM tree
circle = paper.selectAll('circle.big-circle'); //All circles in paper's DOM tree with class 'big-circle'
Groups
SVG elements can be grouped so that common transformations and event handling can be more easily applied to all the elements in a group. Creating a group is easy:
var group = paper.g(circle, rect);
var g2 = paper.group(rect, circle, ellipse); //an alias for paper.g
Be careful: Order or the arguments matters! Second, if you assign an element to a group, it will be removed from any group it might already belong to.
Elements can, of course, also added to existing groups after they are created:
group.add(circle);
Images
Snap supports nesting raster images inside SVG elements, loading it asynchronously and displaying it only on load completion.
var img = paper.image('bigImage.jpg', x, y, width, height);
The resulting object can be treated as an SVG element. Note, if you use select()
on images to retrieve them later, the wrapper created will be the one for HTML elements, so most of the methods available for SVG elements won’t be supported.
Transformations
We have seen how to draw asymmetric polygons like ellipses and rectangles. However, basic methods constrain us to draw these figures aligned to the Cartesian axes. What if we wanted to draw an ellipse whose axes are 45° rotated with respect to the x-y axes? We can’t specify this in the creation methods, but we can use transformations to obtain the same result.
Likewise, we might need to rotate an image, or to move an element (or a group) at some point after its creation. The transform()
method allows us to do so, by passing an SVG transformation string:
var ellipse = paper.ellipse(100, 50, 10, 20);
ellipse.transform('r45');
This method can take either a string or an object as input. We can also use the transformation matrix associated with an element to apply the same transformation to another element:
var e1 = paper.ellipse(100, 50, 10, 20),
e2 = paper.ellipse(200, 50, 12, 24);
e1.transform('r45');
e2.transform(e1.matrix);
Be careful: the center of transformation for the second element will still be the one used for the first one, so the final effect might surprise you.
The transform()
method can also be used to retrieve the transformation descriptor object for the element it is called on – just call it without arguments. This descriptor can be used to retrieve the local transformation matrix and difference matrix in the case of nested elements:
var g1 = paper.group(),
e1 = paper.ellipse(200, 50, 12, 24);
g1.add(e1);
g1.transform('r30');
e1.transform('t64.6447,-56.066r45,0,0');
console.log(e1.transform());
Conclusion
This article provided an introduction to the basics of Snap.svg. If you are interested in seeing the coolest stuff, please stay tuned, as an advanced follow-up will be published soon.
If you want to learn more about data visualization and Snap, here are a few useful resources: