SVG is a powerful and flexible graphic format, which fits perfectly into the web medium. Unlike raster image formats, such as JPG, PNG, or GIF, SVG is vector-based and consists of “real” objects, which you can select and manipulate in whatever manner you want. So even with some basic scripting, a static image can be animated and made interactive. And that will be the subject of this tutorial.
SVG and Vue: The Perfect Match
To demonstrate how SVG can be scripted, I selected Vue.js. The reason behind my choice is that, in my opinion, SVG and Vue make the perfect match. First, Vue supports SVG out of the box. And second, SVG, like HTML, is XML-based, so we can apply Vue’s reactivity system to SVG and make it interactive in the same easy and convenient way as we do with HTML templates.
Quick Primer on Vue and SVG Integration
Before we get to the two use cases which we’ll explore below, let me give you a clue about the way SVG and Vue integration works.
To get started, we create a basic HTML file and include the Vue framework. Then, we put the SVG we want to manipulate inside.
Here, we have a rectangular object whose attributes are bound to the data object in the Vue instance. We also have a click event listener, which will invoke the toggleStroke()
method. So when we click on the rectangle, the stroke will be toggled.
And here is the Vue code:
new Vue({ el: '#app', data: { color: 'orange', width: 100, height: 100, stroke: 0 }, methods: { toggleStroke(){ this.stroke == 0 ? this.stroke = 5 : this.stroke = 0 } } })
See the Pen <a href=”&lt;a href=&quot;&lt;a href=” https:=”” codepen.io=”” tutsplus=”” pen=”” pvbonr=”” “=””>Vue”&gt;</a><a href=”&lt;a href=” https:=”” codepen.io=”” tutsplus=”” pen=”” pvbonr=”” ‘&gt;vue”=””></a><a href=”&lt;a href=” https:=”” codepen.io=”” tutsplus=”” pen=”” pvbonr=”” ‘&gt;vue”=””><a href=”https://codepen.io/tutsplus/pen/PVBoNR/’&gt;Vue” &gt;https:=”” codepen.io=”” tutsplus=”” pen=”” pvbonr=”” ‘&gt;vue”&gt;https:=”” ‘&gt;vue<=”” a>”=””>https://codepen.io/tutsplus/pen/PVBoNR/’&gt;Vue”&gt;https://codepen.io/tutsplus/pen/PVBoNR/’…</a>” >https:=”” codepen.io=”” tutsplus=”” pen=”” pvbonr=”” “>vue”>https:=”” ‘>vue”=””><a href=”&lt;a href=” https:=”” codepen.io=”” tutsplus=”” pen=”” pvbonr=”” ‘&gt;vue”=””></a><a href=”<a href=”https://codepen.io/tutsplus/pen/PVBoNR/’&gt;Vue” >https:=”” codepen.io=”” tutsplus=”” pen=”” pvbonr=”” ‘&gt;vue<=”” a>”=””>https://codepen.io/tutsplus/pen/PVBoNR/’&gt;Vue”>https://codepen.io/tutsplus/pen/PVBoNR/’&…</a>” >https:=”” codepen.io=”” tutsplus=”” pen=”” pvbonr=”” “>vue”..”=””><a href=”&lt;a href=” https:=”” codepen.io=”” tutsplus=”” pen=”” pvbonr=”” ‘&gt;vue”=””><a href=”https://codepen.io/tutsplus/pen/PVBoNR/’&gt;Vue</a>”>https://codepen.io/tutsplus/pen/PVBoNR/’&gt;Vue</a></a>” >https:=”” codepen.io=”” tutsplus=”” pen=”” pvbonr=”” “>vue”..”=””><a href=”<a href=”https://codepen.io/tutsplus/pen/PVBoNR/’&gt;Vue”>https://codepen.io/tutsplus/pen/PVBoNR/’&gt;Vue</a>” &gt;https:=”” codepen.io=”” tutsplus=”” pen=”” pvbonr=”” “&gt;vue”..”=””><a href=”https://codepen.io/tutsplus/pen/PVBoNR/’&gt;Vue” &gt;https:=”” codepen.io=”” tutsplus=”” pen=”” pvbonr=”” “&gt;vue”…<=”” a>..”=””>https://codepen.io/tutsplus/pen/PVBoNR/’&gt;Vue”&gt;https://codepen.io/tutsplus/pen/PVBoNR/”…</a>. SVG Basic Example by Envato Tuts+
(<a href=”&lt;a href=&quot;&lt;a href=” https:=”” codepen.io=”” tutsplus”=””>@tutsplus”&gt;</a><a href=”&lt;a href=” https:=”” codepen.io=”” tutsplus’&gt;@tutsplus”=””></a><a href=”&lt;a href=” https:=”” codepen.io=”” tutsplus’&gt;@tutsplus”=””><a href=”https://codepen.io/tutsplus’&gt;@tutsplus” &gt;https:=”” codepen.io=”” tutsplus’&gt;@tutsplus”&gt;https:=”” tutsplus’&gt;@tutsplus<=”” a>”=””>https://codepen.io/tutsplus’&gt;@tutsplus”&gt;https://codepen.io/tutsplus’&gt;@tutsplus”…</a>” >https:=”” codepen.io=”” tutsplus”>@tutsplus”>https:=”” tutsplus’>@tutsplus”=””><a href=”&lt;a href=” https:=”” codepen.io=”” tutsplus’&gt;@tutsplus”=””></a><a href=”<a href=”https://codepen.io/tutsplus’&gt;@tutsplus” >https:=”” codepen.io=”” tutsplus’&gt;@tutsplus<=”” a>”=””>https://codepen.io/tutsplus’&gt;@tutsplus”>https://codepen.io/tutsplus’&gt;@tutsplus</…</a>” >https:=”” codepen.io=”” tutsplus”>@tutsplus”>https:=”” ..”=””><a href=”&lt;a href=” https:=”” codepen.io=”” tutsplus’&gt;@tutsplus”=””><a href=”https://codepen.io/tutsplus’&gt;@tutsplus</a>”>https://codepen.io/tutsplus’&gt;@tutsplus</a></a>” >https:=”” codepen.io=”” tutsplus”>@tutsplus”>https:=”” ..”=””><a href=”<a href=”https://codepen.io/tutsplus’&gt;@tutsplus”>https://codepen.io/tutsplus’&gt;@tutsplus</a>” &gt;https:=”” codepen.io=”” tutsplus”&gt;@tutsplus”&gt;https:=”” ..”=””><a href=”https://codepen.io/tutsplus’&gt;@tutsplus” &gt;https:=”” codepen.io=”” tutsplus”&gt;@tutsplus”&gt;https:=”” …<=”” a>…”=””>https://codepen.io/tutsplus’&gt;@tutsplus”&gt;https://codepen.io/tutsplus”&gt;@tutsplus”…</a>) on <a href=”&lt;a href=&quot;&lt;a href=” https:=”” codepen.io”=””>CodePen”&gt;</a><a href=”&lt;a href=” https:=”” codepen.io’&gt;codepen”=””></a><a href=”&lt;a href=” https:=”” codepen.io’&gt;codepen”=””><a href=”https://codepen.io’&gt;CodePen” &gt;https:=”” codepen.io’&gt;codepen”&gt;https:=”” codepen.io’&gt;codepen<=”” a>”=””>https://codepen.io’&gt;CodePen”&gt;https://codepen.io’&gt;CodePen”&gt;https://codepe…</a>” >https:=”” codepen.io”>codepen”>https:=”” codepen.io’>codepen”=””><a href=”&lt;a href=” https:=”” codepen.io’&gt;codepen”=””></a><a href=”<a href=”https://codepen.io’&gt;CodePen” >https:=”” codepen.io’&gt;codepen<=”” a>”=””>https://codepen.io’&gt;CodePen”>https://codepen.io’&gt;CodePen</a></a>” >https:=”” codepen.io”>codepen”>https:=”” codepen.io’>codepen”=””><a href=”&lt;a href=” https:=”” codepen.io’&gt;codepen”=””><a href=”https://codepen.io’&gt;CodePen</a>”>https://codepen.io’&gt;CodePen</a></a>” >https:=”” codepen.io”>codepen”>https:=”” codepen.io’>codepen”=””><a href=”<a href=”https://codepen.io’&gt;CodePen”>https://codepen.io’&gt;CodePen</a>” &gt;https:=”” codepen.io”&gt;codepen”&gt;https:=”” codepen.io’&gt;codepen”=””><a href=”https://codepen.io’&gt;CodePen” &gt;https:=”” codepen.io”&gt;codepen”&gt;https:=”” codepen.io’&gt;codepen<=”” a>”=””>https://codepen.io’&gt;CodePen”&gt;https://codepen.io”&gt;CodePen”&gt;https://codepe…</a>.
As you can see, it’s super easy to combine Vue and SVG. Now, let’s explore some more realistic and useful examples.
Example One: Creating a Countdown Timer Widget
In the first example, we’ll create a countdown timer widget. It will allow users to set minutes and seconds for a given time period and, when started, the timer will show the remaining time in circular progress.
To draw and animate the progress, we’ll use an SVG circle object and its stroke-dasharray
attribute. You can read about the SVG circular progress technique here. Also, to add some structure and nice styling, we’ll use the Card component from Bulma. So make sure you’ve added the framework to your file.
See the Pen <a href=”&lt;a href=&quot;&lt;a href=” https:=”” codepen.io=”” tutsplus=”” pen=”” wpknol=”” “=””>Vue”&gt;</a><a href=”&lt;a href=” https:=”” codepen.io=”” tutsplus=”” pen=”” wpknol=”” ‘&gt;vue”=””></a><a href=”<a href=”https://codepen.io/tutsplus/pen/WPKNoL/’&gt;Vue” >https:=”” codepen.io=”” tutsplus=”” pen=”” wpknol=”” ‘&gt;vue<=”” a>”=””>https://codepen.io/tutsplus/pen/WPKNoL/’&gt;Vue”>https://codepen.io/tutsplus/pen/WPKNoL/’&…</a>” >https:=”” codepen.io=”” tutsplus=”” pen=”” wpknol=”” “>vue”>https:=”” ‘>vue”=””><a href=”&lt;a href=” https:=”” codepen.io=”” tutsplus=”” pen=”” wpknol=”” ‘&gt;vue”=””><a href=”https://codepen.io/tutsplus/pen/WPKNoL/’&gt;Vue</a>”>https://codepen.io/tutsplus/pen/WPKNoL/’&gt;Vue</a></a>” >https:=”” codepen.io=”” tutsplus=”” pen=”” wpknol=”” “>vue”..”=””><a href=”<a href=”https://codepen.io/tutsplus/pen/WPKNoL/’&gt;Vue”>https://codepen.io/tutsplus/pen/WPKNoL/’&gt;Vue</a>” &gt;https:=”” codepen.io=”” tutsplus=”” pen=”” wpknol=”” “&gt;vue”..”=””><a href=”https://codepen.io/tutsplus/pen/WPKNoL/’&gt;Vue” &gt;https:=”” codepen.io=”” tutsplus=”” pen=”” wpknol=”” “&gt;vue”…<=”” a>.”=””>https://codepen.io/tutsplus/pen/WPKNoL/’&gt;Vue”&gt;https://codepen.io/tutsplus/pen/WPKNoL/”…</a>. SVG Basic Example by Envato Tuts+
(<a href=”&lt;a href=&quot;&lt;a href=” https:=”” codepen.io=”” tutsplus”=””>@tutsplus”&gt;</a><a href=”&lt;a href=” https:=”” codepen.io=”” tutsplus’&gt;@tutsplus”=””></a><a href=”<a href=”https://codepen.io/tutsplus’&gt;@tutsplus” >https:=”” codepen.io=”” tutsplus’&gt;@tutsplus<=”” a>”=””>https://codepen.io/tutsplus’&gt;@tutsplus”>https://codepen.io/tutsplus’&gt;@tutsplus</…</a>” >https:=”” codepen.io=”” tutsplus”>@tutsplus”>https:=”” tutsplus’>@tutsplus”=””><a href=”&lt;a href=” https:=”” codepen.io=”” tutsplus’&gt;@tutsplus”=””><a href=”https://codepen.io/tutsplus’&gt;@tutsplus</a>”>https://codepen.io/tutsplus’&gt;@tutsplus</a></a>” >https:=”” codepen.io=”” tutsplus”>@tutsplus”>https:=”” ..”=””><a href=”<a href=”https://codepen.io/tutsplus’&gt;@tutsplus”>https://codepen.io/tutsplus’&gt;@tutsplus</a>” &gt;https:=”” codepen.io=”” tutsplus”&gt;@tutsplus”&gt;https:=”” ..”=””><a href=”https://codepen.io/tutsplus’&gt;@tutsplus” &gt;https:=”” codepen.io=”” tutsplus”&gt;@tutsplus”&gt;https:=”” …<=”” a>..”=””>https://codepen.io/tutsplus’&gt;@tutsplus”&gt;https://codepen.io/tutsplus”&gt;@tutsplus”…</a>) on <a href=”&lt;a href=&quot;&lt;a href=” https:=”” codepen.io”=””>CodePen”&gt;</a><a href=”&lt;a href=” https:=”” codepen.io’&gt;codepen”=””></a><a href=”<a href=”https://codepen.io’&gt;CodePen” >https:=”” codepen.io’&gt;codepen<=”” a>”=””>https://codepen.io’&gt;CodePen”>https://codepen.io’&gt;CodePen</a></a>” >https:=”” codepen.io”>codepen”>https:=”” codepen.io’>codepen”=””><a href=”&lt;a href=” https:=”” codepen.io’&gt;codepen”=””><a href=”https://codepen.io’&gt;CodePen</a>”>https://codepen.io’&gt;CodePen</a></a>” >https:=”” codepen.io”>codepen”>https:=”” codepen.io’>codepen”=””><a href=”<a href=”https://codepen.io’&gt;CodePen”>https://codepen.io’&gt;CodePen</a>” &gt;https:=”” codepen.io”&gt;codepen”&gt;https:=”” codepen.io’&gt;codepen”=””><a href=”https://codepen.io’&gt;CodePen” &gt;https:=”” codepen.io”&gt;codepen”&gt;https:=”” codepen.io’&gt;codepen<=”” a>”=””>https://codepen.io’&gt;CodePen”&gt;https://codepen.io”&gt;CodePen”&gt;https://codepe…</a>.
We start by adding a card component and then put a header inside with the title of our widget.
COUNTDOWN TIMER
Next, we use the image section of the card to put our SVG.
Here, we have a rectangle which serves as a background. We use two circles to create the circular progress. We position them so they perfectly overlap. We set the fill
attribute of the first circle to none
and use only its stroke as an outline for the progress.
To create the illusion of drawing a circle, we bind the stroke-dasharray
attribute of the second circle to the dasharray()
computed property, which we’ll create a bit later. Also, we want the starting point of the drawing to be at 12 o’clock and not at 3 o’clock which is the default. To do this, we rotate the point using the transform
attribute. The last object is the text, which we position in the circle’s center. To display the time correctly, with a leading zero, we apply the formatTime()
filter, which we’ll create later.
Next, we need to add the controls for the minutes and seconds.
The important controls here are the inputs, which we bind with the corresponding Vue properties by using the v-model
directive. We also disable them when the state
is set to started
or paused
. Finally, we add a change event listener, which will call the updateTime()
method.
And finally, we add the buttons to control the timer.
Here, we again add click event listeners and some conditions to the buttons, so they will be disabled when not needed.
So far, we’ll need some CSS to correct the spacing and aligning of some parts of the timer.
#app { width: 260px; margin: 10px; } .card-header-title { justify-content: center; } .card-content { padding: 4px 20px 8px; } .card-footer-item { padding: 4px; }
And now, it’s time to add the Vue code to the equation.
new Vue({ el: '#app', circumference: 2 * Math.PI * 80, data: { state: 'stopped', minute: 0, second: 0, progress: 0, timeInSeconds: 0 }, computed: { dasharray(){ return this.progress + " " + this.$options.circumference }, } })
First, we define the necessary properties in the data object, and we add the circumference of the circles as a custom option of the Vue instance. The latter is because we need circumference
to be static but not reactive. We create dasharray()
computed to calculate the values for the stroke-dasharray
attribute.
Now, let’s add the methods:
methods: { updateTime(){ this.timeInSeconds = Number(this.minute) * 60 + Number(this.second) }, start() { this.state = "started"; if (this.progress == 0){ this.progress = this.$options.circumference; } this._tick(); this.interval = setInterval(this._tick, 1000); }, pause() { this.state = "paused"; clearInterval(this.interval); }, stop() { this.state = "stopped"; clearInterval(this.interval); this.minute = 0; this.second = 0; this.progress = 0; }, _tick: function() { //if second is 0 and minute is 0, clear the interval if (this.minute == 0 && this.second == 0){ this.stop() } //update progress let delta = (this.$options.circumference / this.timeInSeconds) if ((this.progress - delta) < (delta / 2)){ this.progress = 0 } else { this.progress -= delta } //if second is not 0, just decrement second if (this.second !== 0) { this.second--; return; } //if second is 0 and minute is not 0, decrement minute and set second to 59 if (this.minute !== 0) { this.minute--; this.second = 59; } } }
The updateTime()
method updates the value of the timeInSeconds
property each time the values change.
The start()
method changes the state
to started
and invokes the _tick()
method every second.
The _tick()
method handles the proper update of the progress
, minute
, and second
props.
The pause()
method changes the state
to paused
and stops the clock by clearing the interval.
The stop()
method changes the state
to stopped
, stops the clock, and resets the progress
, minute
, and second
props.
And finally, we add the formatTime()
filter to handle the proper display of the time.
filters: { formatTime: function(value) { if (value >= 10) { return value; } return "0" + value; } }
And that's it! We successfully used Vue's reactivity features to transform our static SVG drawing into an interactive countdown timer. Let's move on to the next example.
Example Two: Creating an SVG Infographic
In this example, we'll create a small infographic demonstrating what responsive web design is and how it works. Thanks to Vue, we'll be able to animate our SVG illustration and make it more realistic and engaging.
See the Pen <a href="&lt;a href=&quot;&lt;a href=" https:="" codepen.io="" tutsplus="" pen="" erpxmk="" "="">Vue"&gt;</a><a href="<a href="https://codepen.io/tutsplus/pen/ErpxmK/'&gt;Vue">https://codepen.io/tutsplus/pen/ErpxmK/'&gt;Vue</a>" &gt;https:="" codepen.io="" tutsplus="" pen="" erpxmk="" "&gt;vue"&gt;https:="" '&gt;vue"=""><a href="https://codepen.io/tutsplus/pen/ErpxmK/'&gt;Vue" &gt;https:="" codepen.io="" tutsplus="" pen="" erpxmk="" "&gt;vue"...<="" a>"="">https://codepen.io/tutsplus/pen/ErpxmK/'&gt;Vue"&gt;https://codepen.io/tutsplus/pen/ErpxmK/"...</a>; SVG Basic Example by Envato Tuts+
(<a href="&lt;a href=&quot;&lt;a href=" https:="" codepen.io="" tutsplus"="">@tutsplus"&gt;</a><a href="<a href="https://codepen.io/tutsplus'&gt;@tutsplus">https://codepen.io/tutsplus'&gt;@tutsplus</a>" &gt;https:="" codepen.io="" tutsplus"&gt;@tutsplus"&gt;https:="" tutsplus'&gt;@tutsplus"=""><a href="https://codepen.io/tutsplus'&gt;@tutsplus" &gt;https:="" codepen.io="" tutsplus"&gt;@tutsplus"&gt;https:="" ...<="" a>"="">https://codepen.io/tutsplus'&gt;@tutsplus"&gt;https://codepen.io/tutsplus"&gt;@tutsplus"...</a>) on <a href="&lt;a href=&quot;&lt;a href=" https:="" codepen.io"="">CodePen"&gt;</a><a href="<a href="https://codepen.io'&gt;CodePen">https://codepen.io'&gt;CodePen</a>" &gt;https:="" codepen.io"&gt;codepen"&gt;https:="" codepen.io'&gt;codepen"=""><a href="https://codepen.io'&gt;CodePen" &gt;https:="" codepen.io"&gt;codepen"&gt;https:="" codepen.io'&gt;codepen<="" a>"="">https://codepen.io'&gt;CodePen"&gt;https://codepen.io"&gt;CodePen"&gt;https://codepe...</a>.
I created the static parts of the infographic in Illustrator, and then exported it as SVG. Then I added the dynamic parts manually. The dynamic parts are three wireframes, which simulate how one and the same web design is viewed on different devices. Let's create them now.
First, let's create the data objects necessary for the different wireframes.
const laptop = { r1: {x: '100', y: '335', width: '400', height: '220'}, r2: {x: '115', y: '350', width: '200', height: '30'}, r3: {x: '115', y: '390', width: '370', height: '70'}, r4: {x: '115', y: '470', width: '110', height: '40'}, r5: {x: '245', y: '470', width: '110', height: '40'}, r6: {x: '375', y: '470', width: '110', height: '40'}, } const tablet = { r1: {x: '200', y: '335', width: '200', height: '220'}, r2: {x: '215', y: '350', width: '100', height: '30'}, r3: {x: '215', y: '385', width: '170', height: '70'}, r4: {x: '215', y: '460', width: '80', height: '40'}, r5: {x: '305', y: '460', width: '80', height: '40'}, r6: {x: '215', y: '505', width: '80', height: '40'}, } const phone = { r1: {x: '220', y: '335', width: '160', height: '220'}, r2: {x: '225', y: '340', width: '150', height: '30'}, r3: {x: '225', y: '375', width: '150', height: '70'}, r4: {x: '225', y: '450', width: '150', height: '30'}, r5: {x: '225', y: '485', width: '150', height: '30'}, r6: {x: '225', y: '520', width: '150', height: '30'}, } new Vue({ el: '#app', data: { d: { r1: {x: '100', y: '335', width: '400', height: '220'}, r2: {x: '100', y: '335', width: '400', height: '220'}, r3: {x: '100', y: '335', width: '400', height: '220'}, r4: {x: '100', y: '335', width: '400', height: '220'}, r5: {x: '100', y: '335', width: '400', height: '220'}, r6: {x: '100', y: '335', width: '400', height: '220'}, } }, })
Every wireframe consists of six rectangles, so for each design we create a separate data object with the necessary values. In the Vue instance, we create another one, which will be the base object.
Now, let's create the SVG rectangles necessary for the wireframes and bind their attributes to the data values:
Next, we create the animation method with the help of Tween.js. So make sure you've added that library to your file. We use the Vue created()
lifecycle hook to initially animate the wireframe from the base object to the laptop design wireframe.
created(){ this.anim(laptop) }, methods: { anim(val){ function animate(time) { requestAnimationFrame(animate); TWEEN.update(time); } requestAnimationFrame(animate); new TWEEN.Tween(this.d.r1).to(val.r1, 1000).start(); new TWEEN.Tween(this.d.r2).to(val.r2, 1000).start(); new TWEEN.Tween(this.d.r3).to(val.r3, 1000).start(); new TWEEN.Tween(this.d.r4).to(val.r4, 1000).start(); new TWEEN.Tween(this.d.r5).to(val.r5, 1000).start(); new TWEEN.Tween(this.d.r6).to(val.r6, 1000).start(); } }
And now, we add overlays to the device icons by positioning transparent SVG rectangles above them. This way, the clicking area will contain the whole icons and not only their outlines. And finally, we add the click event listeners, which will call the anim()
method with the selected design.
So now, when we open the infographic, the laptop design will show up gently animated, and when we click on the different device icons, the wireframe design will be updated accordingly with smooth animation. Cool, huh?
Conclusion
As you can see, SVG, in combination with Vue, can be very powerful and flexible. Vue makes it super easy to access and manipulate SVG objects and to make them fully interactive. This way, you can bring life to your static SVG graphics and make them dynamic and more pleasant for users. Such an engagement can dramatically improve the user experience and the overall appearance of your website or application.