The Best Way to Create Fantastic ‘Invisible Pen’ Effects in SVG
From the BLINK tag of the early 90’s to today’s modern CSS3 transitions and animations, the ability to move elements on web pages/applications has been always a tempting goal.
Today, technologies like SVG make it relatively easy to create an effect that I’m going to call the ‘Invisible Pen’ effect. This is where the viewer watches an illustration appear line-by-line, as if drawn by an invisible pen.
The idea isn’t new. Back in 1953 in ‘Duck Amuck‘, an unseen hand (later revealed to be Bugs Bunny) famously torments poor Daffy Duck by constantly erasing and redrawing the world around him. It’s a surreal masterpiece.
Fast forward to 2013, and Jake Archibald cleverly demonstrated how you could manipulate stroke-dashoffset
, stroke-dasharray
and some CSS to create this ‘magic pen effect’ on any SVG path.
Jake’s approach is easy to implement on relatively simple SVG linework, but it’s not always the ideal solution when we want to create something more complex. For more complicated linework animations there are purpose-built tools to help you such as Vivus.js. Vivus is a lightweight JavaScript library which allows us to animate the SVG elements without messing around too much with CSS.
I suppose the main question popping into your head now is: What makes Vivus better solution than the pure CSS approach?
In my opinion, Vivus has three main advantages which should be mentioned:
-
Ease-of-use: In Vivus, we control most of our animation properties in a Vivus ‘constructor object’ and in context along with our SVG document. This makes changes more convenient and intuitive to work with. With only a couple of tweaks we can get a completely different animation result.
-
Automation: As you may already know, SVG’s
stroke-dashoffset
property – the key to this effect – is only available onpath
elements. This can cause problems if your SVG drawing containscircle
,rectangle
,line
,polyline
or other common SVG ‘non-path’ objects.Fortunately, Vivus is smart enough to automatically convert all SVG objects into path elements making it possible for us to animate them too. This is a huge advantage.
However, do note that we need to transform all
text
element manually into paths, using a vector editor such as Illustrator, InkScape, and alike. -
Flexibility: Vivus offers five preset animation types, along with a lot of options for manipulation, which gives us great flexibility about our animation’s behavior.
Now, let’s start exploring Vivus in more detail and see these advantages in action.
Attention designers: Yes, there is some code ahead, but Vivus is designed to be used by designers. Even if you’re not a hardcore codemonkey, a little cut, paste and edit will get you some pretty cool results.
Exploring Vivus
To start using Vivus we need to include it in our HTML like this:
<script src="https://cdnjs.cloudflare.com/ajax/libs/vivus/0.3.2/vivus.js"></script>
Then, we can use an inline SVG in the following manner:
<svg id="canvas">
<path...>
<path...>
<path...>
</svg>
And the script:
<script>
new Vivus('canvas', {duration: 300}, callback);
</script>
Here, we have an inline SVG and a Vivus constructor function which takes three parameters:
-
ID of the DOM element we want to interact with (our ID is ‘canvas’).
-
An option object where we put all the animation options.
-
And an optional callback function called at the end of the animation.
Before we move on, we need to know the following:
-
By default Vivus draws elements in the same order as they are defined in the SVG document.
-
All SVG elements must have a stroke property and cannot be filled.
-
We must not put any hidden path elements in our SVG. Otherwise the animation might not be displayed properly.
-
Text elements cannot be converted into path elements. If you want to use text in our animation we need first to transform it into paths (in editor like Illustrator, InkScape).
The Five Drawing Options of Vivus
In the next sections we’ll explore all five animation types offered by Vivus. We’ll use a very simple drawing (four lines) in order to see clearer the difference between the available animation options.
Delayed
See the Pen Vivus demo: Simple line animation with delay by SitePoint (@SitePoint) on CodePen.
Here our SVG drawing consists of four lines with equal length. In delayed
mode, which is the default animation, all paths start almost simultaneously with a small delay between each one.
One-By-One
See the Pen Vivus demo: One-by-one by SitePoint (@SitePoint) on CodePen.
In oneByOne
mode each path is drawn one after the other. In the example above the same four lines are drawn one by one in a continuous manner.
Async
See the Pen Vivus demo: Asynchronous example by SitePoint (@SitePoint) on CodePen.
In async
mode each path is drawn asynchronously. In the example above lines with different lengths are used in order to see more clearly that lines start and finish at the same time regardless their different lengths.
Scenario
This and the next animation type give us ability to set animation properties directly in the DOM of the SVG. This allows us to gain more control over our animation and refine it much more precisely.
In scenario
mode we just have to define the start time and duration of each path element with data-start
and data-duration
attributes. If it is missing, it will use the default value given to the constructor.
See the Pen Vivus Demo: ‘Scenario’: Custom line drawing speed by SitePoint (@SitePoint) on CodePen.
In the example above the second line starts first, then the third line is drawn quickly, next the first line starts, and finaly, the last line starts when the first one is in the middle of its way and they both finish at the same time. Here is the simplified scheme of our SVG:
<line data-start="200" data-duration="200" .../>
<line data-start="0" data-duration="100" .../>
<line data-start="100" data-duration="10" .../>
<line data-start="300" data-duration="100" .../>
As we can see to direct our animation we just need to perform simple math calculations. And here is where the Vivus’ flexibility shines. We don’t have to respect the original order of the SVG. We can start with whichever element we wish and continue with all the others in whatever order we need.
Scenario-Sync
In scenario-sync
mode, the default behavior is the same as in oneByOne
mode. However, we can further refine the animation by targeting some attributes to specific path items such as:
-
data-duration
– to define the duration of the path animation -
data-delay
– to define the delay between the end of the animation of the previous path and the start of the current path -
data-async
(no value required) – to make the drawing of the path asynchronous i.e. the next path will start at the same time. If a path does not have an attribute for duration or delay then the default values, set in the option object, will be used.
See the Pen Vivus Demo: Scenario sync by SitePoint (@SitePoint) on CodePen.
In the example above the first line is drawn with the default duration of 200ms
. The second line starts with a delay of 100ms
and the same duration. The third line starts immediately after the second one and continues 300ms
. Because the third line has data-async
property, the last line starts at the same time as the third but finishes before it because it has a duration of 200ms
.
Here is the simplified scheme of our SVG:
<line .../>
<line data-delay="100" .../>
<line data-duration="300" data-async .../>
<line data-duration="200" .../>
Complete Examples
Now, when we already understand the different types of animation offered by Vivus, it’s time to test them in a more practical and realistic way. In this section, we’re going to create two complete animations using the `delayed` and `scenario-sync` modes.
Car Blueprint Drawing
In the first example, we’ll create this blueprint from a Seat Leon:
The file (Free vector art via Vecteezy!) was opened in Illustrator and then exported as SVG. For the full SVG code, refer to the CodePen example at the end of this section.
First, in our JavaScript file or <script>
tag, we create a new Vivus constructor pointing to our SVG by using canvas
ID. In the option object, we define the type of the animation to be delayed
and the timing function for the entire animation.
We use for that the built-in timing method Vivus.EASE
. As a result, the animation will start and ends more gently. We also add a callback function which will reset the drawing three seconds after the animation end and then will draw it again.
new Vivus('canvas', {
start: 'autostart',
type: 'delayed',
animTimingFunction: Vivus.EASE
},
function(car){
setTimeout(function(){ car.reset().play(); }, 3000);
}
);
You can see the final result here:
See the Pen Vivus Example: Car blueprint by SitePoint (@SitePoint) on CodePen.
As you can see, we get an amazing blueprint animation effect with minimum effort from our side.
Rocket Drawing
In the second example, we’ll create a rocket illustration animation and integrate color.
Our goal is to draw the rocket, fill it, and then remove the stroke. For the SVG code refer to the CodePen example at the end of this section.
We start again with a new Vivus constructor. This time we set the type of animation to scenario-sync
and set pathTimingFunction
to Vivus.EASE_OUT
. We use a callback function which will add some CSS classes at the end of the animation. They will fill the rocket and will remove its strokes.
new Vivus('canvas', {
start: 'autostart',
type: 'scenario-sync',
pathTimingFunction: Vivus.EASE_OUT
},
function(obj){
obj.el.classList.add('fill-1', 'fill-2', 'fill-3', 'fill-4', 'fill-5', 'fill-6', 'fill-7', 'clear-stroke');
}
);
In our CSS file or <style>
tag we initially set the path’s fill-opacity
to zero and a transition for it with duration of 1s
. Then we use CSS selectors to assign fill
property for each element in the SVG and make it visible by setting the fill-opacity
to 1
. You can read more about this trick here.
And finally, we set the stroke
for each path to none
.
path {
fill-opacity: 0;
transition: fill-opacity 1s;
}
.fill-1 g:first-of-type > path{
fill: #1c8ece;
fill-opacity: 1;
}
.fill-2 g:last-of-type > path{
fill: #ffffff;
fill-opacity: 1;
}
.fill-3 path:first-of-type{
fill: #f4a260;
fill-opacity: 1;
}
.fill-4 path:nth-of-type(2){
fill: #ea3e2f;
fill-opacity: 1;
}
.fill-5 path:nth-of-type(3){
fill: #d1d2d4;
fill-opacity: 1;
}
.fill-6 path:nth-of-type(4),
.fill-6 path:nth-of-type(5) {
fill: #000000;
fill-opacity: 1;
}
.fill-7 path:nth-of-type(6){
fill: #ffffff;
fill-opacity: 1;
}
.clear-stroke path{
stroke: none;
}
In our SVG, we use data-duration
and data-delay
properties to build the animation according to our needs. data-async
property is used to make the two wings of the rocket are drawn simultaneously.
Here is the simplified scheme of the SVG:
<path data-duration="50" .../>
<path data-duration="150" data-delay="100" .../>
<path data-duration="50" .../>
<path data-duration="50" .../>
<g> <polygon data-duration="300" data-async .../>
<polygon data-duration="300" .../>
</g> <path data-delay="25" .../>
<path .../>
<g> <polygon data-async .../> <polygon .../> </g>
You can see the final result here:
See the Pen Vivus Example: Rocket by SitePoint (@SitePoint) on CodePen.
Summary
As you can see, with Vivus we can concentrate primarily on our drawing idea and its practical realization, instead of juggling with CSS properties. Once we have our SVG document ready, it’s a relatively simple process to turn it into a working animation.
With a little bit of imagination and the animation capabilities of Vivus, we can create complex and interesting animations in no time.