Pablo and the
art of SVG


<pablojs.com>

Premasagar Rose <http://premasagar.com>

Dharmafly <http://dharmafly.com>

Pablo is a small, open-source JavaScript library for SVG, the web standard for vector graphics.

Pablo is used for vector-based art, games, visualisations, responsive graphics and interfaces.

Why SVG?

  • Declarative shapes
  • Stylable with CSS
  • Attach user events
  • Transformation
  • Animation
  • DOM-based
  • Browser dev tools

Building blocks

a line, in SVG

<line x1="5" y1="195" x2="295" y2="5" stroke="green" stroke-width="10"/>

a line, in Pablo

Pablo.line({
    x1: 5,
    y1: 195,
    x2: 295,
    y2: 5,
    stroke: 'green',
    'stroke-width': 10
});

Browser support

IE9+, Chrome, Firefox, Safari, Opera, mobile browsers

if (Pablo.isSupported){
    /* Pablo code here */
    alert('Yes!');
}
else {
    /* Fallback content */
    alert("Noo");
}

.svg()

Append an <svg> element to an HTML element

var svg = Pablo('#mycontainer').svg({
    width:300,
    height:200
});
<div id="mycontainer">
    <svg version="1.1" width="300" height="200"><svg>
</div>

Element methods

Creates the specified element and attributes.

// Pablo.ELEMENT_NAME([attributes])
Pablo.circle({r:50})

// Pablo(elementName, attributes)
Pablo('circle', {r:50})

Add children to the SVG element

svg.circle({r:50})

More children

Pablo.symbol()
     .text()
     .textPath().content('Olá!');

Creates this nested structure

<symbol>
    <text>
        <textPath>Olá!<textPath>
    <text>
<symbol>

SVG shapes

<line>

Pablo.line({
    x1: 5,
    y1: 195,
    x2: 295,
    y2: 5,
    stroke: 'green',
    'stroke-width': 10
});

<rect>

Pablo.rect({
    x:10, y:10,
    width:190, height:190,
    fill:  '#f6f',
    stroke:'#006',
    'stroke-width': 20,
    'stroke-linejoin': 'round'
});

<circle>

Pablo.circle({
    cx: 100,
    cy: 100,
    r: 90,
    fill:  '#ff3',
    stroke:'#060',
    'stroke-width': 20
});

<ellipse>

Pablo.ellipse({
    cx: 160,
    cy: 100,
    rx: 150,
    ry: 90,
    fill:  '#f93',
    stroke:'#606',
    'stroke-width': 20
});

<polygon>

Pablo.polygon({
    points: '100,0 200,50 200,150 100,200 0,150 0,50',
    fill: 'green'
});

<polyline>

Pablo.polyline({
   points: '10,190 90,90 200,160 340,10',
   stroke: '#d21034',
   fill:   'none',
   'stroke-width': 20
});

<path>

Pablo.path({
   d: 'M 10 10 C 0 200, 200 200, 190 10',
   fill: '#d21034'
});


<path>

Pablo.path({
   d: 'M 10 10 C 0 200, 200 200, 190 10',
   fill:  'none',
   stroke:'#d21034',
   'stroke-width': 20,
   'stroke-linecap': 'round'
});

Bézier curves

[1]

<path>

Pablo.path({
   d: 'm3,46l96,0m-48,41l47,46m-47,-46l-44,46m43,-101l0,55m14.5,-69c0,8 -6.5,14.5 -14.5,14.5c-8,0 -14.5,-6.5 -14.5,-14.5c0,-8 6.5,-14.5 14.5,-14.5c8,0 14.5,6.5 14.5,14.5z',
   fill:  'none',
   stroke:'#d21034',
   'stroke-width': 6
});

<path>

<path>

<path>

Fills

<pattern>


var bricks = Pablo.defs().pattern({
    id: 'bricks',
    width:44, height:48,
    patternUnits: 'userSpaceOnUse',
    fill: 'darkred'
});
bricks.rect({width:20, height:20});
bricks.rect({width:20, height:20, x:24});
bricks.rect({width:40, height:20, y:24});
svg.rect({
    width:370,
    height: 280,
    fill:'url(#bricks)'
});
svg.circle({
    r:  125,
    cx: 125,
    cy: 125,
    fill: 'url(#bricks)'
});

<linearGradient>


<linearGradient>

var gr = Pablo.defs().linearGradient({
    id: 'mygradient',
    x1: '0%',
    y1: '100%',
    x2: '100%',
    y2: '0%'
});
gr.stop({offset:'5%', 'stop-color':'#006'});
gr.stop({offset:'95%','stop-color':'#0c9'});

<linearGradient>

Pablo.circle({
    fill:'url(#mygradient)',
    cx:80, cy:80, r:80
});

<linearGradient>

Pablo.rect({
    fill:'url(#mygradient)',
    width:80,
    height:80,
    x: 240,
    y: 10
});

<linearGradient>

Pablo.polygon({
    fill:'url(#mygradient)',
    points: '100,300 200,0 300,300'
});

<clipPath>


<clipPath>

// Append hexagon to a <clipPath>
svg.clipPath({id:'hex-clip'});
   .polygon({points:'108,0 216,63 216,188 108,250 0,188 0,63'});

// Apply clip path to an element
svg.image({
  width:631, height:283,
  'clip-path':'url(#hex-clip)',
  'xlink:href':'guernica.jpg'
});

<filter>

demo

SVG resources

MDN and pablojs.com/resources/

Nested structures

villain = Pablo.g({id:'villain'});
  .polygon({id:'hat', points:'...'})
  .append([eyes, moustache]);

Selection

with querySelectorAll

.foo .foo


Pablo('circle.foo')
    .attr('fill', 'red');

Collections

Inspired by jQuery & Underscore


circles = Pablo('circle.foo');
Pablo([
    Pablo.path(),
    Pablo.rect(),
    Pablo.g()
]);
Pablo.circle()
    .add(Pablo.rect());

Collections

Slice and dice

// Get the 4th wrapped element
circles.eq(3);
// Get the 4th DOM element
circles[0];
// More
circles.length;
circles.pop();
circles.last();
circles.slice(4,9);
circles.each(fn);

Grabbing the elements

var array = collection.toArray();

ES5 Array methods

each, map, every, some, reduce, reduceRight, filterElements, indexOf, lastIndexOf

whities = purplies
  .map(function(el){
    return Pablo.circle({
      cx: Pablo(el).attr('cx'),
      r: 8,
      fill: 'white'
  });
});
whities.after(purplies);

Map the elements

circles.toArray().map(function(el){
    return el.getAttribute('cx');
});
// [55, 140, 195, 260]

Traversal

  • .parent()
  • .children()
  • .prev()
  • .next()
  • .siblings()
  • .find(selector)

Wrap HTML elements

var div = Pablo('div.thingy')
    .append(something)
    .addClass('righteous')
    .on('click', function(){
        alert('I ❤ DOM');
    });

Event listeners

Interaction: drag & drop, clicks, etc

circles.on('click', function(){
    alert('Stop it');
});

Clone & duplicate

.clone()

rect = svg.rect({
    width:80, height:80,
    fill:'red'
});
rect.clone()
    .attr('x', 90)
    .appendTo(svg);

.duplicate()

rect = svg.rect({
    width:80, height:80,
    fill:'red'
});
rect.duplicate(3)
    .attr('x', [0, 90, 180, 270]);

Value arrays & functions

to iterate changes through a collection

Value arrays

Pablo.circle({cy:30, r:30})
    .duplicate(4)
    .attr({
        cx:[30, 90, 150, 210, 270],
        fill: ['red', 'green', 'blue', 'orange', 'purple']
    });

Value functions

Pablo.circle({
    cy: '50%',
    fill: 'rgba(127, 159, 95, 0.2)',
    stroke: '#777'
})
.duplicate(18)
.attr({ // i = element index
    cx: function(el,i){return i*4+1+'%'},
    r:  function(el,i){return i+1+'%'}
});

CSS

  • .css()
  • .style()
  • external stylesheets
shape.css({
    opacity: 0.5,
    fill: 'red',
    cursor: 'pointer'
});

.transform()
.transformCss()

  • translate
  • scale
  • rotate
  • skewX
  • skewY
  • matrix

Animation

  • Native SVG elements
  • CSS3 transitions / keyframes
  • requestAnimationFrame / setTimeout

Native SVG elements

<animate>, <animateTransform>, <animateMotion>

<animateTransform>

<animateTransform>

train.animateTransform({
    attributeName: 'transform',
    type: 'translate',
    from: 0,
    to: 500,
    dur: '2s',
    fill: 'freeze'
});

<animateTransform>

(source)

<animateTransform>

shape.animateTransform({
    attributeName: 'transform',
    type: 'rotate',
    from: 0,
    to: 360,
    dur: '48s',
    repeatCount: 'indefinite'
});

Delayed gratification

var anim = Pablo.animateTransform({
    // [...]
    begin: 'indeterminate'
});
button.on('click', function(){
    anim[0].beginElement();
});

Animation events

var anim = Pablo.animateTransform({
    // [...]
    begin: 'other-anim.end'
});

Shape tweening

CSS

Pablo.rect({
    width: 100,
    height: 100,
    fill: 'darkblue'
})
.transition('opacity', '1s');

// Transitions whenever the opacity changes

.requestAnimationFrame()

SVG paths & animation with HTML


Demo (context)

Plugins

Extend the collections API

Pablo.fn.height = function(){
    return this.bbox().height;
};

Collections can use the new method

Pablo('#myelement').height();

<symbol> & <use>

fishbowl = Pablo.symbol({
    id:'fishbowl'
});
fishbowl.append(fish);
fishbowl.append(bowl);

instance = Pablo.use({x:7, y:20, 'xlink:href':'#fishbowl');

Templates

Give a namespace to a complex structure

Pablo.template('house', function(/* args */){
    /* Code to draw a house */
});
Pablo.house({someOption:'foo'}, [...]);

.requestAnimationFrame()

elem.load('cat.svg', function(cat){
    // ...
});

Import from:

  • Inkscape
  • Illustrator
  • SVG Edit
  • FigurePool
  • Wikipedia

Much more

On pablojs.com