How to create svg elements with Javascript
Tags: javascript, webdev, css, html
Published: 2021-04-06 17:56:38

In a nutshell: What are SVGs?
-----------------------------

If you've ever taken a small image and tried to scale it up in size, you know the struggle: It gets pixelated and the fonts become an unreadable raster of black-to-white'ish squares. Fortunately, there are resolutions to the matter, one of which has been standardized within the `.svg` file format. While other common formats, such as  `.png`, are based on a grid of pixels, svgs consist out of a fixed set of shapes. The way these are drawn and aligned is described with [XML - markup](https://developer.mozilla.org/en-US/docs/Web/XML/XML_introduction), more specifically with [paths](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/path). This allows for a more dynamic scaling.

![this image shows a comparison between the letter S being rendered with a raster format and the letter S being rendered with as an SVG](https://upload.wikimedia.org/wikipedia/commons/6/6b/Bitmap_VS_SVG.svg)

[Yug, modifications by 3247](https://commons.wikimedia.org/wiki/File:Bitmap_VS_SVG.svg), [CC BY-SA 2.5](https://creativecommons.org/licenses/by-sa/2.5), via Wikimedia Commons

In a nutshell, raw SVG files in the wilderness:

*   are namespaced within their xml namespace (xmlns) - standard.
*   contain one or several paths within the <svg /> - tags that make up the actual graphc.
*   can be styled with css and inline styles.

Consider this example from _[Heroicons](https://heroicons.com/)._ If you drop the markup into an html file, it will render into the actual icon.

```html
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1" />
</svg>
```

(note: I replaced the tailwind classes with a style attribute, the result is about the same though)

Now that you've got a glimpse about the format, you might already have an idea how the post's topic is to be solved - by means of DOM - manipulation. So let's try and recreate the element above with Javascript.

Dynamic XML-node creation - boilerplate
---------------------------------------

XML differs from HTML in several aspects, the most relevant being that XML does not have predefined tags. Instead, it allows you to define these yourself within so-called namespaces.

This also allows for dynamically adding SVG icons to data from a remote location you'd like to bind to a client's interface while - or after - the data is being rendered. Let's assume you run a blog and would like to dynamically add the 'link'-icon from above before every post's heading. For a user's convenience, we'll add an anchor tag that permits the reader to scroll this post directly scroll it into their center of attention. To illustrate this example, let's start with the following boilerplate:

*   We use a simple `index.html` file that holds a list of posts.
*   These posts are fetched from jsonplaceholder and dynamically added to the DOM by a function inside the `main.js` file.
*   `main.css` provides us a few basic styles for our list.

So launch your favorite text editor and add them to a free directory of your choice.

### _index.html_

```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="stylesheet" href="main.css">
  <title>Create SVGs with Javascript - Demo</title>
</head>
<body>
  <h1 class="site-header">
    Posts from today
  </h1>
  <main id="posts" class="post-list"></main>
  <script src="main.js"></script>
</body>
</html>
```

### _main.js_

```javascript
async function getPostData() {
  const url = 'https://jsonplaceholder.typicode.com/posts';
  const response = await fetch(url);
  return await response.json();
}

function renderPosts(app, posts) {
  const postNodes = posts.map((post) => {
    // Create the DOM elements
    const postCard = document.createElement('div');
    const postHeader = document.createElement('div');
    const postTitleAnchor = document.createElement('a');
    const postTitle = document.createElement('h2');
    const postText = document.createElement('p');

    // Add some classes and attributes
    postCard.classList.add('post-card');
    postHeader.classList.add('post-header');
    postTitle.classList.add('post-title')
    postTitle.id = post.title;
    postTitleAnchor.href = '#' + post.title;

    // Place the text content
    postTitle.textContent = post.title;
    postText.textContent = post.body;

    // TODO: Add the icon here

    // Put together the DOM nodes
    postHeader.appendChild(postTitleAnchor)
    postHeader.appendChild(postTitle);
    postCard.appendChild(postHeader);
    postCard.appendChild(postText);
    app.appendChild(postCard);

    return postCard;
  });
  return postNodes;
}

async function mountPosts() {
  const app = document.querySelector('#posts');
  const posts = await getPostData();
  renderPosts(app, posts);
}

mountPosts();
```

### _main.css_

```css
* {
  scroll-behavior: smooth;
}

body {
  font-family: 'Gill Sans', 'Gill Sans MT', Calibri, 'Trebuchet MS', sans-serif;
  background-color: blueviolet;
  margin: 0;
  padding: 0;
}

h1 {
  padding: 2rem 0;
  margin: 0;
}

.site-header {
  position: sticky;
  text-align: center;
  width: 100%;
  background-color: #fff;
}

.post-list {
  padding: 0 20vw;
}

.post-card {
  border-radius: 2rem;
  background-color: #fff;
  padding: 1rem 2rem;
  margin: 2rem;
}

.post-icon {
  transition: 0.25s all;
  border-radius: 0.25rem;
  height: 2rem;
  width: 2rem;
  margin-right: 0.5rem;
  padding: 0.25rem;
}

.post-icon:hover {
  transition: 0.5s all;
  background-color: blueviolet;
  stroke: white;
}

.post-header {
  display: flex;
  align-items: center;
}

@media only screen and (max-width: 1200px) {
  .post-list {
    padding: 0 10vw;
  }
}

@media only screen and (max-width: 600px) {
  .post-list {
    padding: 0 2vw;
  }
}
```

You'll receive a UI that looks like this, a simple and clean post collection.

![this image shows a screenshot of a few posts that have been fetched from jsonplaceholder](https://q-bit.me/content/images/2021/04/svg-start-screenshot.png)

* * *

Add a function to create the XML
--------------------------------

Let's take a look into the xml-file again:

```html
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1" />
</svg>
```

*   It has a <svg /> tag as a wrapper which includes the namespace and some attributes.
*   Within, there's one (or several) <path /> tags that describes the shape of the SVG.
*   Inside the browser's context, both of these are interpreted and rendered like html-tags.

The last point also implies that said xml-tags can be created and composed like html-elements. An <svg /> tag, for instance, can be created like this:

```javascript
// Create an element within the svg - namespace (NS)
document.createElementNS('http://www.w3.org/2000/svg', 'svg');
```

From then on, the svg can be mostly be handled like any other element. You can add styles, classes and also - most importantly - [attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes).

So let's add the following function to the `main.js` file. It will take in the anchor tag into which we will inject the created graphic, making it suitable for our scrolling feature.

```javascript
function renderLinkIcon(node) {
  const iconSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
  const iconPath = document.createElementNS(
    'http://www.w3.org/2000/svg',
    'path'
  );

  iconSvg.setAttribute('fill', 'none');
  iconSvg.setAttribute('viewBox', '0 0 24 24');
  iconSvg.setAttribute('stroke', 'black');
  iconSvg.classList.add('post-icon');

  iconPath.setAttribute(
    'd',
    'M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1'
  );
  iconPath.setAttribute('stroke-linecap', 'round');
  iconPath.setAttribute('stroke-linejoin', 'round');
  iconPath.setAttribute('stroke-width', '2');

  iconSvg.appendChild(iconPath);

  return node.appendChild(iconSvg);
}
```

Making it all functional
------------------------

Now that we have all building blocks in place that adds the icon, let's put it to action.

Add the following inside the `main.js` file, right after placing the text-content:

```javascript
// TODO: Add the icon function here
renderLinkIcon(postTitleAnchor);
```

And that's it. The icons are prepended to each post and can easily be used as anchor links for smooth scrolling. Below goes the final result:

![this image shows the final user interface that includes the svg-icons that serve as anchor tags](https://blog.q-bit.me/content/images/2021/04/svg-done-screenshot.png)
> This post was originally published at https://blog.q-bit.me/how-to-create-svg-elements-with-javascript/
Thank you for reading. If you enjoyed this article, let's stay in touch on Twitter 🐤 [@qbitme](https://twitter.com/qbitme)