The Scalable Vector Graphics SVG format is a vector image standard. A SVG document has an XML structure and can be used as a standalone file but it’s now an integral part of the HTML5 language. We can use the SVG element directly in an HTML document, style it with CSS and manipulate it in JavaScript through the DOM functions. In this article I’m going to play with these possibilities to generate abstract shapes like on the header of this article.

Drawing in SVG

The SVG language offers basic vector primitives to draw shapes like circles, rectangles, polygons, paths. But it also has more advanced possibilities like defining graphical template objects. This is what I’m going to use throughout this article. For a full description of all the possibilities of SVG the documentation on the Mozilla Developer Network has all the details.

Drawing a basic illustration

An SVG root element, a rectangle to display a background and three colored circles to make a kind of very simple logo. The image has a 100x100 size displayed in a 100x100 viewport.

<svg width="100" height="100" viewBox="0 0 100 100"  
     xmlns="http://www.w3.org/2000/svg">
    <!-- Background -->
    <rect width="100" height="100" fill="#f1f3f5"/>

    <!-- A few circles in group element -->
    <g>
        <circle cx="32" cy="32" r="27" fill="#868e96"/>
        <circle cx="81" cy="44" r="15" fill="#343a40"/>
        <circle cx="55" cy="78" r="19" fill="#495057"/>
    </g>
</svg>  

The <g> group element is a generic container similar to a <div> in HTML. And it’s very similar to a layer or group in a drawing app.

Making reusable symbols

Now I’m going to change the logo into a template with the <symbol> tag. The symbol is just a definition and isn’t displayed yet. We must assign it a unique id. Then the symbol can be instantiated with the <use> tag and a specific link to the id of the symbol.

<svg width="300" height="100" viewBox="0 0 300 100"  
     xmlns="http://www.w3.org/2000/svg">
    <!-- Symbol definition, not displayed yet -->
    <symbol id="symbol-circles-1">
        <circle cx="32" cy="32" r="27" fill="#868e96"/>
        <circle cx="81" cy="44" r="15" fill="#343a40"/>
        <circle cx="55" cy="78" r="19" fill="#495057"/>
    </symbol>

    <!-- Background -->
    <rect width="300" height="100" fill="#f1f3f5"/>

    <!-- Display 2 distinct instances of the symbol -->
    <use xlink:href="#symbol-circles-1" x="33"/>
    <use xlink:href="#symbol-circles-1" x="166" opacity="0.5"/>
</svg>  

Changing colors of symbol instances

By removing the color attributes in the symbol definition we can change its color directly by setting color attributes on the <use> tag of the instances.

<svg width="300" height="100" viewBox="0 0 300 100"  
     xmlns="http://www.w3.org/2000/svg">
    <symbol id="symbol-circles-2">
        <circle cx="32" cy="32" r="27"/>
        <circle cx="81" cy="44" r="15"/>
        <circle cx="55" cy="78" r="19"/>
    </symbol>
    <rect width="300" height="100" fill="#f1f3f5"/>

    <!-- Display 2 distinct versions of the symbol -->
    <use xlink:href="#symbol-circles-2" x="33" fill="#1098ad"/>
    <use xlink:href="#symbol-circles-2" x="166" fill="#0ca678" fill-opacity="0.5"/>
</svg>  

Transform definitions

The instances of the symbol can also be modified with the transform attribute. With this attribute it’s possible to translate, rotate, scale ans skew an element. These transformations can also be composed as a transformation matrix.

<svg width="300" height="100" viewBox="0 0 300 100"  
     xmlns="http://www.w3.org/2000/svg">
    <symbol id="symbol-circles-3">
        <circle cx="32" cy="32" r="27"/>
        <circle cx="81" cy="44" r="15"/>
        <circle cx="55" cy="78" r="19"/>
    </symbol>
    <rect width="300" height="100" fill="#f1f3f5"/>

    <!-- Display 2 instances with different transform -->
    <use xlink:href="#symbol-circles-3" fill="#1098ad"
         transform="translate(50 25) rotate(-20) scale(0.75)"/>
    <use xlink:href="#symbol-circles-3" fill="#0ca678" fill-opacity="0.5"
         transform="translate(200 0) rotate(45) scale(0.5)"/>
</svg>  

Manipulating the SVG DOM with JavaScript

Before manipulating SVG elements and attributes with JavaScript we must properly declare the XML namespaces used by the SVG standard on the <svg> tag. Otherwise after some trial and error I noticed that the DOM API wouldn’t work!

Namespaces in SVG

A few things to note about the attributes on the <svg> element. The SVG format was born as an XML document hence the namespace declarations inherited from this legacy:

<svg xmlns="http://www.w3.org/2000/svg"  
     xmlns:xlink="http://www.w3.org/1999/xlink">
</svg>  
  • When used directly in HTML the two xmlns declarations are optional.
  • When the SVG is used as a standalone image file the xmlns="http://www.w3.org/2000/svg" declaration is required. Otherwise I’ve notices that an app like Adobe Illustrator for example wouldn’t open the file.
  • When using DOM manipulation functions in JavaScript and particularly when manipulating the xlink:href attribute to instantiate symbols the xmlns:xlink="http://www.w3.org/1999/xlink" declaration is also required.

Then this is a good idea to add the two xmlns attributes when we want to manipulate SVG elements with the DOM API in JavaScript.

Adding nodes and attributes

With the previous empty SVG let’s create a new <polygon> element to draw a triangle by setting the proper points attribute, then adding it to the <svg> element.

let triangle = document.createElementNS("http://www.w3.org/2000/svg", "polygon");  
triangle.setAttribute("points", "0,100 100,100 50,0");  
document.querySelector("svg").addChild(triangle);  

To create a symbol instance element and adding its link attribute this a slightly more complicated:

let symbolInstance = document.createElementNS("http://www.w3.org/2000/svg", "use");  
symbolInstance.setAttributeNS("http://www.w3.org/1999/xlink",  
                              "xlink:href",
                              "#symbol-circles-3");
document.querySelector("svg").addChild(symbolInstance);  

The trick here is to always use the document.createElementNS() function with the SVG namespace to create a SVG element instead of the standard document.createElement() function we would normally use for HTML elements.

To set standard attributes on these elements the standard element.setAttribute() is fine but to set the specific link attribute we must use instead the element.setAttributeNS() function. This is the tricky part about manipulating SVG elements through the DOM!

The documentation pages about all these functions:

Export the generated SVG

As I’m going to generate random SVG elements this is interesting to save what I’ve done. The aim is to make an editable SVG file that can be opened in a vector design application or displayed directly on the web as a vector image.

Export an SVG element as a file

Here’s an SVG illustration and a basic void link in a HTML file.

<!DOCTYPE html>  
<html>  
<head>  
    <meta charset="utf-8"/>
    <title>Export SVG</title>
</head>  
<body>  
<svg id="illustration"  
     width="100" height="100" viewBox="0 0 100 100"
     xmlns="http://www.w3.org/2000/svg">
    <rect width="100" height="100" fill="#f1f3f5"/>
    <circle cx="32" cy="32" r="27" fill="#868e96"/>
    <circle cx="81" cy="44" r="15" fill="#343a40"/>
    <circle cx="55" cy="78" r="19" fill="#495057"/>
</svg>

<p>  
    <a href="#" class="link-download">Download displayed SVG</a>
</p>  
</body>  
</html>  

And the reusable JavaScript code snippet to export the illustration as a file by setting up Blob and object URL objects in JavaScript.

document.querySelector(".link-download").addEventListener("click", (evt) => {  
    const svgContent = document.getElementById("illustration").outerHTML,
          blob = new Blob([svgContent], {
              type: "image/svg+xml"
          }),
          url = window.URL.createObjectURL(blob),
          link = evt.target;

    link.target = "_blank"; // for browsers that don't support the download attribute
    link.download = "Illustration.svg";
    link.href = url;
});

The demo of this download link. When clicking on the link your browser should open the standard File save as dialog:

See the Pen Export SVG Fragment as File by Frederic Perrin (@blustemy) on CodePen.

Make generative art

The previous sections are the main technical aspects I’ve used to implement a generic random shapes generator. Just before jumping to the demo this is a screenshot of the generated SVG from the following demo as displayed in Affinity Designer:

The generated SVG works also perfectly in the open-source Inkscape app. It’s mostly working in Adobe Illustrator but a few tweaks would be needed.

Demo

This is the full JavaScript code in ES6 that I’ve used to generate the previous illustration. Making your own symbols, tweaking the values in the JavaScript class, the colors, the random numbers and the quantities of generated elements may give appealing designs!

The link to the full CodePen code: http://codepen.io/blustemy/pen/NbyGgY.

See the Pen Making SVG Patterns with JavaScript by Frederic Perrin (@blustemy) on CodePen.

Browser support

I’ve used a lot of novelties here, so the compatibility will be limited to modern browsers only! This is working perfectly in the latest versions of Firefox, Chrome, Edge and Safari. You can generate the SVG file in a modern browser and display it in older browsers though.

Performance

Beware of performance! This generates a whole SVG structure and not a flat bitmap. Every shape has a slot in memory with a lot of properties and the browser is making complex composition calculations to render them in layers. Here the aim is to open and reuse the generated shapes in a vector design app. To generate a simple flattened bitmap image that would be much straightforward to use the Canvas API instead. But that’s another story… for another article!

Full code listing

In HTML in the <body> of the page we won’t have much code:

<svg id="illustration"  
     width="768" height="340" viewBox="0 0 768 340"
     xmlns="http://www.w3.org/2000/svg"
     xmlns:xlink="http://www.w3.org/1999/xlink">
    <!-- The symbol that will be repeated -->
    <symbol id="triangle" viewBox="0 0 100 100">
        <polygon points="0,100 100,100 50,0"/>
    </symbol>
    <!-- Colored background -->
    <rect width="768" height="340" fill="#1098ad"/>
</svg>

<p>  
    <button type="button" class="button-redraw">Redraw</button>
    <button type="button" class="button-clear">Clear</button>
    <a href="#" class="link-download">Download generated SVG</a>
</p>  

The Patterns main JavaScript class:

class Patterns {  
    constructor({ svg, symbol }) {
        this.svgElement = svg;
        this.symbolElement = symbol;
        this.symbolLink = `#${symbol.id}`;
    }

    static get svgNamespace() {
        return "http://www.w3.org/2000/svg";
    }

    static get xlinkNamespace() {
        return "http://www.w3.org/1999/xlink";
    }

    /* Return a random integer between min (included) and max (excluded). */
    static getRandomInt(min, max) {
        const minInt = Math.ceil(min),
              maxInt = Math.floor(max);
        return Math.floor(Math.random() * (maxInt - minInt)) + minInt;
    }

    /* Return a random number between min (inclusive) and max (exclusive). */
    static getRandomArbitrary(min, max) {
        return Math.random() * (max - min) + min;
    }

    /* Add an instance of the symbol with random attributes. */
    addRandomSymbol(parentNode, fill = "#000000", opacity = 0.5) {
        const useElement = document.createElementNS(Patterns.svgNamespace, "use"),
              width = this.symbolElement.viewBox.baseVal.width,
              height = this.symbolElement.viewBox.baseVal.height,
              x = Patterns.getRandomArbitrary(0, this.svgElement.viewBox.baseVal.width),
              y = Patterns.getRandomArbitrary(0, this.svgElement.viewBox.baseVal.height),
              angle = Patterns.getRandomArbitrary(0, 360),
              scale = Patterns.getRandomArbitrary(1, 4);
        useElement.setAttributeNS(Patterns.xlinkNamespace, "xlink:href", this.symbolLink);
        useElement.setAttribute("width", `${width}`);
        useElement.setAttribute("height", `${height}`);
        useElement.setAttribute("fill", fill);
        useElement.setAttribute("fill-opacity", `${opacity}`);
        useElement.setAttribute("transform", `translate(${x} ${y}) scale(${scale}) rotate(${angle})`);
        parentNode.appendChild(useElement);
    }

    /* Add a group of n symbols. */
    drawRandomSymbols(n = 10, fill = "#000000", opacity = 0.5) {
        // Prepare a group element containing all the symbol instances.
        const gElement = document.createElementNS(Patterns.svgNamespace, "g");
        for (let i = 0; i < n; i++) {
            this.addRandomSymbol(gElement, fill, opacity);
        }
        gElement.classList.add("patterns");

        // Add the group.
        this.svgElement.appendChild(gElement);
    }

    clearSymbols() {
        const gElements = this.svgElement.querySelectorAll("g.patterns");
        for (let i = 0; i < gElements.length; i++) {
            this.svgElement.removeChild(gElements[i]);
        }
    }

    get svgString() {
        return this.svgElement.outerHTML;
    }

    exportAsSvgFile(clickedLinkElement) {
        const blob = new Blob([this.svgString], {
                  type: "image/svg+xml"
              }),
              url = window.URL.createObjectURL(blob);

        clickedLinkElement.target = "_blank";
        clickedLinkElement.download = "Patterns.svg";
        clickedLinkElement.href = url;
    }
}

Then we can instantiate this class when the document has loaded when the DOMContentLoaded event is triggered by the browser:

document.addEventListener("DOMContentLoaded", () => {  
    const patterns = new Patterns({
        svg: document.getElementById("illustration"),
        symbol: document.getElementById("triangle")
    });

    // Draw a group of 40 black symbols.
    patterns.drawRandomSymbols(40, "#000000", 0.15);
    // Draw a group of 30 white symbols.
    patterns.drawRandomSymbols(30, "#ffffff", 0.4);

    document.querySelector(".button-redraw").addEventListener("click", () => {
        patterns.clearSymbols();
        patterns.drawRandomSymbols(40, "#000000", 0.15);
        patterns.drawRandomSymbols(30, "#ffffff", 0.4);
    });

    document.querySelector(".button-clear").addEventListener("click", () => {
        patterns.clearSymbols();
    });

    document.querySelector(".link-download").addEventListener("click", (evt) => {
        patterns.exportAsSvgFile(evt.target);
    });
});

Well this was a bit long for an article! I myself have learned a few things by writing these code snippets and this is just an overview of all the fun things we can do with SVG.