Advanced Snap.svg

Share this article

We have seen, in an earlier post, how to get started with Snap.svg. In this post, we are going to take a closer look at the new features mentioned in the first article.

Masking

Let’s start by recalling how to create a drawing surface, a simple shape, and then load an image:

var paper = Snap(800, 600),
    img = paper.image('bigImage.jpg', 10, 10, 300, 300),
    bigCircle = s.circle(150, 150, 100);

The circle covers the center of the image, for now.

It is kind of a pity, though, that you can only have rectangular images. Maybe your designer created nice circular buttons or images. Of course, there are several solutions, but they all leave you with another problem: best case scenario, the designer can give you an image with the outside matching the page’s background, so that it will look circular. However, assuming you have a solid background, if you have to change its color, you’ll have to edit the image. You could use transparency, but you’d either need heavier formats like PNG, or loose quality with GIFs. In a few years, maybe, WebP will be fully supported by all browsers, and that would end the conundrum. Either way, if you need interactivity for your image, you’ll be stuck with a rectangular shape responding to events like mouseenter, mouseout, click, etc.

Having dealt with Flash for a long time in the past, one of the most frustrating things in SVG was not being able to use masks, introduced in SVG 1.1). In Snap, applying a mask to any element, including images, is quite easy:

bigCircle.attr('fill', '#fff'); //This is IMPORTANT

img.attr({
    mask: bigCircle
});

Basically, we just have to assign the mask property to our element. We have to be careful with the element we use as the actual mask. Since the opacity of the final element will be proportional to the level of white in the mask element, we must fill the circle in white if we want a full opacity for our image. While at first this might seem annoying, it opens a lot of possibilities for amazing effects, as we’ll see in the next sections.

You can obviously compose together different shapes to create complex masks. Snap offers some syntactic sugar to help you:

var smallRect = paper.rect(180, 30, 50, 40),
    bigCircle = paper.circle(150, 150, 100),
    mask = paper.mask(bigCircle, smallRect);

mask.attr('fill', 'white');

img.attr({
    mask: mask
});

The Paper.mask() method is equivalent to Paper.g(), and in fact it can be seamlessly replaced by it.

Clipping

Clipping paths restrict the region to which paint can be applied, so any parts of the drawing outside the region bounded by the currently active clipping path are not drawn. A clipping path can be thought of as a mask with visible areas (within the clipping path) have an alpha value of 1 and hidden areas have an alpha value of 0. The one difference is that while areas hidden by masks will nonetheless respond to events, clipped-out areas won’t.

Snap doesn’t have shortcuts for clipping, but you can set the clip, clip-path, and clip-route properties of any element using the attr() method.

Gradients

SVG 1.1 allows the use of gradients to fill shapes. Of course, if we use those shapes to fill a mask, we can leverage the possibility to specify the alpha level of the final drawing by changing the mask’s filling, and create astonishing effects. Snap provides shortcuts to create gradients, that can later be assigned to the fill property of other elements. If we modify the previous code just a little bit, for example:

var gradient = paper.gradient('r()#fff-#000');
mask.attr('fill', gradient);

If you test this code, the final effect won’t be exactly what you expected. That’s because we used the relative radiant gradient type, expressed by the lowercase ‘r’ above. Relative gradients are created separately for each element of a group (as a composite mask). If you prefer having a single gradient for the whole group, you can use the absolute version of the command. 'R()#fff-#000' is an absolute radiant gradient starting with white fill at the center and degrading to black at the borders.

We can get the same result by specifying the SVG gradient for the fill property of any element:

mask.attr('fill', 'L(0, 0, 300, 300)#000-#f00:25-#fff');

In this last example, we have shown a more complex gradient. Besides the different type (absolute linear), this gradient goes from (0, 0) to (300, 300), from black through red at 25% to white.

The gradient() method accepts a string. Further details are explained in Snap’s documentation.

It is also possible to use existing gradients from any svg element in the page:

<svg id="svg-test">
    <defs>
      <linearGradient id="MyGradient">
        <stop offset="5%" stop-color="#F60" />
        <stop offset="95%" stop-color="#FF6" />
      </linearGradient>
    </defs>
  </svg>
paper.circle(50, 50, 50, 50).attr('fill', Snap('#svg-test').select('#MyGradient'));

Patterns

Patterns allow to fill shapes by repeating occurrences of another svg shape, gradient, or image. Snap offers the Element.toPattern() method (formely, pattern(), now deprecated) that creates a pattern out of any Snap element.

Creating a pattern and filling an element with it is pretty straightforward:

var p = paper.path("M10-5-10,15M15,0,0,15M0-5-20,15").attr({
                      fill: "none",
                      stroke: "#bada55",
                      strokeWidth: 5
                  }).toPattern(0, 0, 10, 10),
    c = paper.circle(200, 200, 100).attr({
                                            fill: p
                                        });

If, instead, we would like to combine gradients and patterns, that’s a different story, and a slightly more complicated one! As an example, let’s see how to create a mask that combines a radiant gradient and a pattern similar to the one above:

//assuming the shapes bigCircle and smallRect have already been defined, as well as 'paper'
var mask = paper.g(bigCircle, smallRect),
    gradient = paper.gradient("R()#fff-#000"),
    pattern = paper.path("M10-5-10,15M15,0,0,15M0-5-20,15").attr({
        fill: "none",
        stroke: "#bada55",
        strokeWidth: 5
    }).toPattern(0, 0, 10, 10);

mask.attr('fill', pattern); //we need to set this before calling clone!
mask.attr({
    mask: mask.clone()      //makes a deep copy of current mask
});

img.attr({
    mask: mask
});

We basically have to create a two level map. The final map used on our image, which we fill with a gradient, has a map itself filled with a gradient. The result is quite impressive! Turns out this was also a good opportunity to introduce you the clone() method, which does what you would imagine – creates a deep copy of the element it is called on.

Animations

Animations are one of Snap.svg’s best crafted features. There are several ways to handle animations, with slightly different behaviors.

Element.animate()

We will start with the simplest animation method, Element.animate(). This method allows users to animate any number of an element’s properties, all in sync. The initial value for the property is, of course, its current value, while the final one is specified in the first argument to animate(). Besides the properties to be changed, it is possible to pass the duration of the animation, its ease, and a callback that will be called once the animation is completed.

An example will make everything clearer:

bigCircle.animate({r: 10}, 2000);

This will simply shrink the big circle in our mask to a smaller radius over the course of two seconds.

Set.animate()

You can animate elements in a group (set) independently. But, what if you want to animate the whole set synchronously? Easy! You can use Set.animate(). This will apply the same transformation to all the elements in the set, ensuring synchronicity among the various animations, and enhancing performance by gathering all the changes together.

mask.animate({'opacity': 0.1}, 1000);

You can also animate each element in a set independently, but synchronously. Set.animate() accepts a variable number of arguments, so that you can pass an array with the arguments for each sub-element you need to animate:

var set = mask.selectAll('circle');  //Create a set containing all the circle elements in mask's subtree (1 element)
paper.selectAll('rect')                //Select all the rect in the drawing surface (2 elements)
        .forEach(function(e) {set.push(e);}); //Add each of those rectangles to the set previously defined
set.animate([{r: 10}, 500], [{x: 20}, 1500, mina.easein], [{x: 20}, 1500, mina.easein]); //Animate the three elements in the set

Assuming you have correctly followed our example code so far (try it on CodePen), running the code above in your browser’s console you’ll see how the three elements gets animated in sync, but independently. The code above was also a chance to introduce sets (as the results of the select() and selectAll() methods), and a few useful methods defined on them.

Another way to create a set is by passing an array of elements to the Snap constructor method:

var set2 = Snap([bigCircle, smallRect]);

Snap.animate()

You can animate any numeric property, but animate() won’t work on other types, for example it will mess up your text elements if you try to animate their text attribute. Yet there is another way to gain such an effect, i.e. the third way to call animate() in Snap.

By calling the animate method of the Snap object it is possible to specify in further details the actions that will be executed at each step of the animation. This helps both grouping together complex animation and running them in sync (although the Set.animate() method would be the right way to deal with this problem), and to animate complex, non numeric properties.

For instance, let’s create and animate a text element:

var labelEl = paper.text(300, 150, "TEST"),
    labels = ["TEST", "TETT","TEUT","TEVT","TEXT","TES-","TE--","T---", "----", "C---", "CH--", "CHE-", "CHEC-", "CHECK"];
Snap.animate(0, 13, function (val) {
    labelEl.attr({
        text: labels[Math.floor(val)]
    });
}, 1000);

Event Handling

Going back to the initial comparison between masks and images, you could obtain the same effect we have shown in the previous section with an animated gif (sort of). If, however, you want to reproduce this behavior in response to user interaction, the improvement using SVG is even more relevant. You can still find a way to make it work using multiple gifs, but, besides loosing flexibility, you won’t be able to get the same quality with as little effort:

img.click(function(evt) {
    this.minified = !this.minified;
    bigCircle.animate({
        r: !this.minified ? 100 : 10
    }, 1500);
});

Click handlers can be later removed using the Element.unclick() method.

Among other events that can be handled similarly there are dblclick, mousedown and mouseup, mousemove, mouseout and mouseover, and a number of mobile-oriented events, like touchstart and touchend.

For those of our readers used to jQuery or D3 interfaces, there in no on() method in Snap to manually handle other events. If you need a custom behavior that goes beyond the handlers offered by Snap, you can retrieve the node property for any element, which in turn contains a reference to the associated DOM element, and (possibly after wrapping it in jQuery) you can add handlers and properties to it directly:

img.node.onclick = function () {
    img.attr("opacity", 0.1);
};

Drag and Drop

Snap makes particularly easy activating drag and drop for any element, group or set using the Element.drag() method. If you don’t need any custom behavior, you can call it without any arguments:

labelEl.drag();   //handle drag and drop for you

However, if you need some special behavior, you can pass custom callbacks and contexts for the onmove, ondragstart, ondragend events. Be aware that you can’t omit the onmove callback if you want to pass the next ones.

Adding a drag handler will not hide the click event, that will be fired after the ondragend one, unless explicitly prevented.

Load Existing SVG

One of the strongest points of this great library is that it supports reuse of existing SVG code. You can “inject” it as a string, or even better you can read an existing file, and then change it.

You can try it yourself. Download and save into your project’s root this nice svg drawing. Next load it into your page, change its style or structure as we like, even before adding it to our DOM tree, add event handlers, etc.

Snap.load('ringing-phone.svg', function (phone) {
    // Note that we traverse and change attr before SVG is even added to the page (improving performance)
    phone.selectAll("path[fill='#ff0000']").attr({fill: "#00ff00"});
    var g = phone.select("g");
    paper.append(g);    //Now we add the SVG element to the page
});

Note: Because of the same-origin policy in browsers, you’ll need to run the example in a local server to test the load method.

Performance Improvements

One way to improve performance when manipulating the DOM is using DocumentFragments. Fragments are minimal containers for DOM nodes. Introduced a few years ago, they allow you to inexpensively manipulate entire subtrees, and then clone and add a whole subtree with n nodes to our page with 2 method calls instead of n. The actual difference is explained in details on John Resig’s blog.

Snap allows for native use of fragments as well, with two methods:

  1. Snap.parse(svg) takes a single argument, a string with SVG code, parses it and returns a fragment that can be later appended to any drawing surface.

  2. Snap.fragment(varargs) takes a variable number of elements or strings, and creates a single fragment containing all the elements provided.

Especially for large svg drawings, fragments can lead to a huge performance saving, when used appropriately.

Conclusion

This concludes our article on advanced Snap.svg. Now readers should have a clear idea of what they can do with this library, and how to do it. If you are interested in learning a little more, Snap documentation is a good place to start.

A couple of useful links:

Frequently Asked Questions about Advanced Snap SVG

What is Snap SVG and why is it important in web development?

Snap SVG is a powerful and flexible JavaScript library designed specifically for working with Scalable Vector Graphics (SVG). SVGs are an integral part of web development as they allow for the creation of high-quality, scalable graphics that maintain their clarity regardless of the size or resolution of the screen they’re displayed on. Snap SVG enhances this functionality by providing a robust set of tools for manipulating and animating SVGs, making it easier for developers to create interactive, dynamic graphics and animations for their websites.

How does Snap SVG differ from other SVG libraries?

Snap SVG stands out from other SVG libraries due to its comprehensive feature set and ease of use. It provides a simple, intuitive API that allows developers to quickly and easily create, manipulate, and animate SVGs. Additionally, Snap SVG supports a wide range of SVG features, including gradients, patterns, clipping, masking, and more, making it a versatile tool for any web developer’s toolkit.

Can I use Snap SVG with other JavaScript libraries or frameworks?

Yes, Snap SVG is designed to be compatible with other JavaScript libraries and frameworks. This means you can use it alongside tools like jQuery, React, Angular, and more, allowing you to leverage the strengths of each tool to create more powerful, interactive web applications.

How do I get started with Snap SVG?

To get started with Snap SVG, you’ll first need to include the Snap SVG library in your project. This can be done by downloading the library from the Snap SVG website and including it in your HTML file, or by using a package manager like npm to install it. Once the library is included in your project, you can start using it to create and manipulate SVGs.

What are some common use cases for Snap SVG?

Snap SVG is commonly used for creating interactive graphics and animations for websites. This can include things like interactive charts and graphs, animated icons, interactive maps, and more. Additionally, because Snap SVG supports a wide range of SVG features, it can also be used for more complex tasks like creating SVG filters, masks, and gradients.

Does Snap SVG support browser compatibility?

Yes, Snap SVG is designed to be compatible with all modern browsers, including Chrome, Firefox, Safari, and Internet Explorer 9 and above. This means you can use it to create SVGs that will display correctly across a wide range of devices and browsers.

How can I animate SVGs with Snap SVG?

Snap SVG provides a number of methods for animating SVGs. This includes methods for changing the attributes of an SVG element over time, animating along a path, and more. Additionally, Snap SVG supports the use of easing functions, which allow you to control the speed and flow of your animations.

Can I use Snap SVG for responsive design?

Yes, Snap SVG is an excellent tool for creating responsive designs. Because SVGs are vector-based, they can be scaled up or down without losing quality, making them ideal for responsive design. Additionally, Snap SVG provides methods for manipulating and animating SVGs, allowing you to create dynamic, interactive designs that adapt to different screen sizes and resolutions.

Is Snap SVG free to use?

Yes, Snap SVG is an open-source library, which means it is free to use and modify. You can download the library from the Snap SVG website or install it using a package manager like npm.

Where can I find more resources to learn about Snap SVG?

There are many resources available online to help you learn more about Snap SVG. The Snap SVG website provides a comprehensive guide to the library’s features and functionality, as well as a number of demos and examples. Additionally, there are numerous tutorials and articles available on sites like SitePoint, Dabbles, and GitHub that provide in-depth information and practical examples of how to use Snap SVG.

Marcello La RoccaMarcello La Rocca
View Author

I'm a full stack engineer with a passion for Algorithms and Machine Learning, and a soft spot for Python and JavaScript. I love coding as much as learning, and I enjoy trying new languages and patterns.

animationColinISVG
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week