The intersection of JavaScript and SVG is a good example of how web technologies can work together to create something greater than the sum of their individual specifications.
SVG works easily with JavaScript through the SVG DOM interface to enhance the interactivity of the web. But, the vanilla JavaScript workflow can be complex and cluttered. Fortunately, several libraries, like Pablo and gySVG, have been developed to help simplify the construction and manipulation of SVGs with JavaScript without compromising performance.
In this article, we’ll introduce Pablo and discuss how it can be used to create both simple and complex SVG shapes.
Pablo is a lightweight, open source library used for simplifying the construction and manipulation of SVGs in JavaScript. It is relatively full-featured and has a friendly, easy-to-explore API.
Pablo’s primary focus is simplicity and performance. This library enables developers to more easily work with dynamically generated vector graphics while avoiding the verbose workflow of vanilla JavaScript.
There are several libraries and frameworks available for drawing and manipulating SVGs. But Pablo offers a unique, simplified approach and a plugin system that allows for new functionalities to be added on the fly.
The vanilla code for drawing even the simplest SVG shape tends to be several lines long. This lengthy code can quickly become hard to understand and maintain.
Pablo provides name methods, like .line()
and .circle()
for creating standard types of SVG elements. Pablo also provides methods for manipulating SVG and HTML elements to change their appearance, size, position, and more. These methods make the code comprehensive but very concise.
Here’s a comparison of vanilla JavaScript code and Pablo code. Both examples render a simple SVG circle:
// vanilla js const ns = 'http://www.w3.org/2000/svg' const div = document.getElementById('vector') const svg = document.createElementNS(ns, 'svg') svg.setAttributeNS(null, 'width', '100%') svg.setAttributeNS(null, 'height', '100%') div.appendChild(svg) const circle = document.createElementNS(ns, 'circle') circle.setAttributeNS(null, 'width', 100) circle.setAttributeNS(null, 'height', 100) circle.setAttributeNS(null, 'fill', '#f06') svg.appendChild(circle) // Pablo const svg = Pablo(HTMLElement).svg({height:100}), circles = svg.circle({cy:50, r:50});
As you can see, Pablo’s code is simpler than the vanilla JS.
Now that we have some insight into how concise Pablo can be, let’s take a look at how we can set it up in a project.
There are two methods for getting started with Pablo: downloading and adding the script to the HTML document or installing it with the Bower package manager.
When downloading Pablo’s script, you can choose to either download the full script or the minified script. The full script is for development — it is large and is not optimized. The minified script is for production. It is a compressed, optimized version of the full script.
Both the full and minified scripts are available for download directly from their respective script pages: pablo.js
and pablo.min.js
.
To add either of the scripts to your project, create a new file in your project folder:
pablo.js
for full scriptpablo.min.js
for minified scriptThen, copy and paste the code from the script page and save.
Now, add the script file to the project’s HTML with pablo.min.js
:
<script src="pablo.min.js""></script>
Or, add the script using a path to the downloaded folder passed in as a src
attribute:
<script src="source/pablo.min.js"></script>
Bower is a package manager, like Yarn and npm, which manages frameworks, libraries, assets, and utilities and makes sure they are up to date.
Bower is a command-line utility. You will need to have the latest version of Node.js and Git installed on your machine. First, we use this command to install Bower:
$ npm install -g bower
Next, we install Pablo with this command:
$ bower install pablo
We previously examined the basic structure of a Pablo code block. Now, let’s take an in-depth look at the building blocks of the library and how they work.
The Pablo()
object is the most significant method in Pablo. It contains several properties that can be used to create and append an SVG element to a pre-existing element in the DOM. It is also used to create an array-like structure (called a collection) of new and pre-existing HTML or SVG elements. We will discuss these in more detail in the following sections.
The Pablo()
method returns an empty Pablo collection when logged to the console:
const collection = Pablo(); alert(collection.length); // 0
To load Pablo into the document, we have to append it to a pre-existing HTML element in the DOM. Suppose we have a div element with a class attribute of elem
in the document:
<div class="elem"></div>
We can append our Pablo SVG to the div container in the document by passing the class or id into the Pablo()
method as a parameter and then chaining an .svg()
method to specify the width and height of the vector as a parameter:
const svg = Pablo(.mycontainer).svg({ width: 200, height: 100 });
The code above creates an <svg></svg>
HTML element in the DOM and then appends it to the div container we created earlier.
The output will look like this in the DOM:
<div class="mycontainer"> <svg version="1.1" width="200" height="100"></svg> </div>
A collection is an array-like object that encloses SVG and HTML elements when Pablo creates or selects any element in the DOM. Elements can be worked on directly, but the methods on the collection object are usually used to manipulate and filter elements in Pablo.
However, there are a few methods that are equivalent to those used in standard JS arrays, such as .push()
, .pop()
, .forEach()
, .map()
, and .filter()
. These methods work just like they would in a standard array object. For example, elements can be added to a collection with the .push()
method or removed with the .pop()
method.
Appending elements to a collection is as easy as creating a new element, setting its attribute object, and then chaining it to the collection with either the .push()
, .concat()
, or .unshift()
methods:
const collection = Pablo(['circle', 'path']); collection.push(Pablo.rect({width: 200, height: 100})); alert(collection.lenght) //3
In this example, we created a collection, passed in an array of element methods, and then added a new rectangle shape to the array with the .push()
method. The .push()
method appends new elements to the end of a collection. It is the equivalent of .add()
in jQuery.
Check the Pablo documentation for a comprehensive list of methods you can use to manipulate a collection.
Now, let’s walk through how we can create basic SVG shapes with Pablo, and how we can append them to the created SVG element.
Element methods are used to create new SVG elements with the same name as the method. For example, the circle, rectangle, and line elements will be created with the .circle()
, .rect()
, and .line()
methods, respectively. These elements are nested under the <svg></svg>
element in the DOM, creating a nested structure similar to this example:
<svg> <line x1="5" y1="195" x2="295" y2="5" stroke="green" stroke-width="10"/> </svg>
We can create these elements independently as a variable by calling them directly on a collection, — Pablo.ELEMENT_NAME()
— and appending them to an element on the DOM.
Alternatively, we can simply chain them to the element:
/* Append an <svg> element to an HTML element */ const svg = Pablo(demoElement).svg({ width: 220, height: 220 }); /* Create a <circle> element, wrapped in a collection */ const circle = Pablo.circle(); /* Create a <rectangle> element, wrapped in a collection */ const rect = Pablo.rect(); /* Append to svg element */ svg.append(circle, rect)
Pablo is largely inspired by jQuery. It uses a jQuery-like pattern of chaining method calls to manipulate SVG and HTML elements. This technique makes it possible to run multiple, successive methods on the same element within a single statement.
To create a chain, simply append a method to the previous method:
/* Append an <svg> element to an HTML element */ const svg = Pablo(demoElement).svg({ width: 220, height: 220 }); /* Append a <rect> element to the <svg> */ svg.rect({width:200, height:100}).transform('translate', 70).attr('fill', 'turquoise')
In this example, we chain the .rect()
, .transform()
, and .attr()
methods to the SVG element. Pablo appends a rectangular shape with a width of 200px and a height of 100px, rotates the element with the CSS transform
property, and then sets an attribute property to the shape element to change the color of the rectangle.
We can format the block of code by adding line breaks and indentations to avoid the rabbit hole of cluttered syntaxes:
/* Append an <svg> element to an HTML element */ const svg = Pablo(demoElement).svg({ width: 220, height: 220 }); /* Append a <rect> element to the <svg> */ svg.rect({width:200, height:100}) .transform('translate', 70) .attr('fill', 'turquoise')
In the above example, Pablo will ignore the whitespace and execute the block as one long line of code.
Pablo rect
No Description
This technique of chaining specific named methods to the element enables us to quickly create and append multiple SVG shapes to the DOM.
External SVG files can be imported into a collection using the .load()
method. This method accepts a string of the path to the SVG:
const rocket = Pablo(demoElement).load('/rocket.svg'); /* Find some elements */ rocket.find('path, rect') /* Change their attributes */ .attr('opacity', 0.2)
A callback function may be inserted into the method as a second parameter. Methods can be chained to the external SVG file directly from the callback function:
Pablo(demoElement).load('/rocket.svg', function(rocket){ /* Find some elements */ rocket.find('path, rect') /* Change their attributes */ .attr('opacity', 0.2) });
Now, let’s take a look at several element manipulation methods for creating complex collections.
.attr()
The .attr()
method is used to set a named method’s attribute to a specified value:
const svg = Pablo(demoElement).svg({height:100}), rect = svg.rect({ width: 200, height:100, }); rect.attr('fill', 'blue');
In this example, we created a new collection and appended a named .rect()
method to the collection. Next, we called the .attr()
method and added a fill
attribute of blue
to the element.
When calling the .attr()
method on a collection that contains multiple elements, you can set a different value for each element by passing in an array as the value.
const svg = Pablo(demoElement).svg({height:100}), circles = svg.circle({cy:50, r:50}).duplicate(4); .attr({ fill: ['red', 'green', 'blue', 'orange', 'purple'], cx: [50, 150, 250, 350, 450] });
In this example, the first item in the array will be used to set the first element’s attribute, the second item will be used to set the second element’s attribute, etc.
We can also set multiple attributes for all elements in the collection with just one .attr()
method and a specified object:
const svg = Pablo(demoElement).svg({height:100}), circles = svg.circle({cy:50, r:50}).duplicate(4); .attr({ x: 50, y: -50, width: 200, height:100, fill: 'orange', transform: 'rotate(45)' });
.duplicate([amount])
The .duplicate([amount])
method performs a deep clone of all elements in a collection. This method inserts the duplicated elements after the original elements in the DOM and returns the new collection.
const svg = Pablo(demoElement).svg({height:40}) square = svg.rect({width:40, height:40}); square.duplicate(5) // Set x position for each element .attr('x', function(el, i){ return i * 50; });
In this example, a square is duplicated five times.
Pablo duplicate
No Description
.find(selector)
The .find(selector)
method is used to search for elements that match an inserted CSS selector or list of selectors and then returns these descendants in a new collection.
Pablo(demoElement).load('/rocket.svg', function(rocket){ /* Find some elements */ rocket.find('path, rect') /* Change their attributes */ .attr('opacity', 0.2) });
In this example, the .find()
method returns all .path()
and .rect()
elements from the imported SVG and then appends an opacity property attribute to every element in the returned collection.
With Pablo, you don’t have to worry about manually adding event listeners to your vector graphics with Element.addEventListener
. The library offers several methods for managing native and custom events that can be chained to elements.
.on()
The .on()
method adds event listeners to each element in a collection. An event type, like click
or mouseout
, can be passed into the method as a string alongside a callback function that houses the event logic:
const svg = Pablo(elem).svg({ width: 200, Height: 100 }) const circles = svg.circle(); circles.on('click', function(circle){ circle.attr({fill: 'blue'}) });
In this example, we created a circle and chained a click
event to it. When clicked, the circle’s fill
attribute will change to blue
.
Pablo events
No Description
Pablo offers several methods for creating animation effects. We can either use the Pablo CSS transform()
, transition()
, and transformCss()
methods or the SVG-native <animate>
and <animateMotion>
elements to create effects.
transition(property, duration)
The transition(property, duration)
method creates CSS transitions on each element in a collection. When a transition is set and the named CSS property is modified, the change will take place over the specified duration. In this syntax, property
represents the name of a CSS property, and duration
represents the length of the transition in milliseconds.
const container = Pablo(demoElement), svg = container.svg({width:'100%', height:160}), circles = Pablo.circle().duplicate(3).attr({ r: 50, cx: function(el,i){return i * 140 + 80}, cy: 80, stroke: 'lightblue', fill: 'darkblue', cursor: 'pointer' }).appendTo(svg); // Transition any changes to `stroke-width` over 1000ms circles.transition('stroke-width', 1000); container.on('click', function(){ // Change the `stroke-width` circles.css('stroke-width', 60); // Change it back after a delay window.setTimeout(function(){ circles.css('stroke-width', 0); }, 750); });
We first create a circle and then duplicate it three times. We chain a transition effect to the circle with a delay duration of 1ms in order to set the transition effect for the circle’s stroke-width
. Lastly, we chain a click
event that increases and decreases the stroke-width
of the circle.
Pablo animation
No Description
Pablo(elements, [attributes])
The Pablo(elements, [attributes])
function returns a new collection when an element and an attribute are passed into it as parameters.
The elements
parameter could represent a DOM element, another collection, or an array of elements. The attributes
parameter, if specified, is set on each of the elements in the collection:
const element = document.getElementById('foo'), collection = Pablo(element, {fill:'red'});
Pablo(selector, [context])
The Pablo(selector, [context])
function uses the browser’s native selector engine, Element.querySelectorAll
, to select a specified CSS class, ID, or a comma-seperated list of selectors and then return them in a new collection.
Since the function uses the browser’s native selector engine, both SVG and HTML elements can be targeted.
// A single element, specified by id Pablo('#foo'); // Multiple elements, specified by class Pablo('.bar'); // Multiple selectors Pablo('circle, line, #foo, .bar');
The context parameter, if specified, will make the function return only elements that are descendants of the context provided. The context can be an element or an array of collections.
Pablo('.hexagon', myShapes);
The code above will return only a collection of elements or individual elements that have the .hexagon
class in the myShapes
collection of shapes.
In this article, we covered how to get started with Pablo and how to use Pablo to create both simple and complex SVG elements concisely in JavaScript. We also looked at a few use cases that illustrated some useful Pablo utilities available for working with SVG. We’ve barely scratched the surface of what’s possible with Pablo. Check out the Pablo documentation to build on this introduction.
There’s no doubt that frontends are getting more complex. As you add new JavaScript libraries and other dependencies to your app, you’ll need more visibility to ensure your users don’t run into unknown issues.
LogRocket is a frontend application monitoring solution that lets you replay JavaScript errors as if they happened in your own browser so you can react to bugs more effectively.
LogRocket works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app’s performance, reporting metrics like client CPU load, client memory usage, and more.
Build confidently — start monitoring for free.
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.
Handle frontend data discrepancies with eventual consistency using WebSockets, Docker Compose, and practical code examples.
Efficient initializing is crucial to smooth-running websites. One way to optimize that process is through lazy initialization in Rust 1.80.