David Omotayo Frontend developer and indie game enthusiast.

Building SVGs in JavaScript with Pablo

9 min read 2663

Building SVGs Javascript Pablo

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.

What is Pablo?

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.

Comparing vanilla JavaScript to Pablo

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.

We made a custom demo for .
No really. Click here to check it out.

Getting started with Pablo

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.

Loading the Pablo script

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 script
  • pablo.min.js for minified script

Then, copy and paste the code from the script page and save.

Loading a Pablo Script

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>

Installing Pablo with Bower

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

Understanding the Pablo building blocks

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>

Adding elements to a collection

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.

Creating SVG shapes with element methods

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)

Creating SVG shapes with method chaining

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

Add External Stylesheets/Pens Any URL’s added here will be added as s in order, and before the CSS in the editor. You can use the CSS from another Pen by using it’s URL and the proper URL extention. JavaScript Preprocessor Babel includes JSX processing.

This technique of chaining specific named methods to the element enables us to quickly create and append multiple SVG shapes to the DOM.

Adding external SVGs to a collection

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

Add External Stylesheets/Pens Any URL’s added here will be added as s in order, and before the CSS in the editor. You can use the CSS from another Pen by using it’s URL and the proper URL extention. JavaScript Preprocessor Babel includes JSX processing.

.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.

Using Pablo to create SVG events

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

Add External Stylesheets/Pens Any URL’s added here will be added as s in order, and before the CSS in the editor. You can use the CSS from another Pen by using it’s URL and the proper URL extention. JavaScript Preprocessor Babel includes JSX processing.

Using Pablo to create SVG animations

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

Add External Stylesheets/Pens Any URL’s added here will be added as s in order, and before the CSS in the editor. You can use the CSS from another Pen by using it’s URL and the proper URL extention. JavaScript Preprocessor Babel includes JSX processing.

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.

Conclusion

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.

: Full visibility into your web and mobile apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page and mobile apps.

Are you adding new JS libraries to improve performance or build new features? What if they’re doing the opposite?

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.

https://logrocket.com/signup/

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 — .

.
David Omotayo Frontend developer and indie game enthusiast.

Leave a Reply