DEV Community

Cover image for Handcrafting your own SVG
Nicolas Fränkel
Nicolas Fränkel

Posted on • Originally published at blog.frankel.ch

Handcrafting your own SVG

A couple of months ago, I wanted to design a custom logo. The goal was to create 4 different variations for a single logo: horizontal, vertical, "condensed", and square. All have to display the same bevel-like effect.

I'm a developer, and not a web designer For this reason, I chose to use an existing point-and-click web application to create a SVG. It did the job. But I'm curious, and checked the generated XML: I was disappointed.

This post aims to describe the lessons learned with my first steps into the wondrous world of SVG.

Issues with generated code

First things first, the issues described are not specific to the web application I used, nor to SVG. I can remember 20 years ago generating HTML with Macromedia (now Adobe) Dreamweaver: the HTML was far from optimal - to say the least.

With the generated SVG, issues are, in no particular order:

  • The shape and the bevel use different paths objects
  • Moreover, each logo is designed in a different way: one uses an image-encoded in base 64, one uses 2 different <path> objects, one 3, and the last one 4
  • Because of point-and-click, there's no way to handle metrics precisely
  • Transformations are using the matrix tag, instead of the more specific translate
  • Transformations position the text elements, as opposite to directly set the position
  • The rounded corners using Bezier curves is much more complex than required
  • A single group encompasses all shapes, while I want a group for each logo flavor. Granted, I didn't use the Group tool in the webapp

Reusing the same form

The main form and the bevel share the same coordinates: the difference is that the bevel is translated.

Main form and its bevel

It doesn't make any sense to write the same coordinates twice, and translate one. In that case, the DRY is relevant. SVG allows to define a form once, and use it as many times as desired. For that, it respectively offers a defs directive, and a use one.

<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg">
  <defs>
    <path id="path" d="M 0 0 H 400 V 250 H 0 V 0 Z" /> <!-- 1 -->
  </defs>
  <use href="#path" />                                 <!-- 2 -->
  <use href="#path" transform="translate(0 30)" />     <!-- 2 -->
</svg>
Enter fullscreen mode Exit fullscreen mode
  1. Define the path
  2. Display the defined path using href. SVG removed the xlink namespace, so don't use xlink:href

I must confess that I also tried to achieve a bevel effect with filtering, to no avail.

Coordinates and referential

The web application uses absolute coordinates to define the paths' points. It makes sense. Yet, to manually write coordinates, it's easier to use coordinates in the origin referential, then translate them accordingly.

<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg">
  <defs>
    <path id="path" d="M 0 0 H 400 V 250 H 0 V 0 Z" /> <!-- 1 -->
  </defs>
  <g transform="translate(30,30)">                     <!-- 3 -->
    <use href="#path" />
    <use href="#path" transform="translate(0 30)" />   <!-- 2 -->
  </g>
</svg>
Enter fullscreen mode Exit fullscreen mode
  1. Define coordinates starting from the origin
  2. Translate the bevel
  3. Translate the group

Matrix transforms

SVG allows to use different transform operations: scale, rotate, translate, skewX and skewY. Two syntaxes are available to apply those transforms:

  1. A "compound" syntax using a single matrix attribute e.g. transform="matrix(3 1 -1 3 30 40).
  2. A "disjointed" syntax using the different above attributes e.g. transform="translate(0 20) rotate(-10 50 0)

While the first syntax is much more concise, it makes understanding the transform operation much harder. Besides, I used strictly translations: I re-wrote all transforms with the translate property.

CSS transforms

While SVG offers transform operations out-of-the-box, there are other options to achieve the same. One can apply CSS styles to SVG elements as well. In particular, CSS provides the transform property for the same purpose.

This allows to apply the same translation to all shapes in a SVG document. It's a great way to uniformly apply... a bevel effect.

<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg">
  <defs>
    <style>
/* <![CDATA[ */
use       { fill:#0C3C60; stroke-width:4; stroke:#0C3C60; }
use.bevel { fill:#D1E0EB; transform:translate(0px, 30px); }  <!-- 1 -->
/* ]]> */
    </style>
    <path id="path" d="M 0 0 H 400 V 250 H 0 V 0 Z" />
  </defs>
  <use href="#path" />
  <use href="#path" class="bevel" />                         <!-- 2 -->
</svg>
Enter fullscreen mode Exit fullscreen mode
  1. Define the translation at the bevel class level
  2. Apply the class and the associated transformation

From that point on, the SVG rendered applies the translation to all shapes with the bevel class.

Beware that the syntax is a bit different in SVG and in CSS: in CSS, units are required.

Rounded corners

Paths allow rounded corners by defining Bézier curves. SVG offers 3 different kind of available Bézier curves, cubic, quadratic and shortcut. It depends on the wanted precision: the higher the precision, the higher the number of control points necessary to define the pathL

The web application uses the cubic way, which requires 6 control points (see the above link for the complete documentation). In this case, because the curve needs to be asymptotical to two consecutive borders, the quadratic approach is also suitable. As a bonus, it's both more concise and easier to read as it requires just 4 control points.

<path
  id="path-horizontal"
  d="M 25 0                        <!-- 1 -->
     H 500                         <!-- 2 -->
     Q 525 0 525 25                <!-- 3 -->
     V 135
     Q 525 160 500 160
     H 25
     Q 0 160 0 135
     V 25
     Q 0 0 25 0
     Z" />                         <!-- 4 -->
Enter fullscreen mode Exit fullscreen mode
  1. Start from point defined by x=25 and y=0
  2. From the previous point, draw a horizontal line up to x=500. In effect, it thus ends at (500, 0)
  3. The first control point defines a geometrical line with the last point in the path; it plays the role of an asymptote to the curve. The second control point, with the first one, defines a second line. It's also an asymptote.

    Defining a cubic Bézier curve

  4. Close the path

Grouping

Defining groups in an adequate way offers several options for each group:

  • Apply a transform
  • Set a CSS style
  • Set a CSS class
  • etc.

Don't be afraid to use them!

Miscellaneous considerations

Twitter cards are not able to use configured SVG images. Likewise, OpenGraph i.e. Facebook doesn't allow SVG images for preview. For such reasons, it's a good idea to export SVG in a bitmap format.

The SVG displayed on the production site should be as lightweight as possible. In particular, carriage returns characters are not used by the SVG renderer. On the flip side, they are helpful to maintainers. Hence, I'd recommend to minify the SVG.

If the SVG is never touched again, one can apply minification directly on the source. Otherwise, then minification should be part of the CI pipeline, and applied on the target file. As an example, SVGO is a plugin for Node.js that offers this feature.

Conclusion

Generating XML (or code) with a tool is always easier - at first: No need to know about the underlying syntax, just click around, and it yields the expected results.

Yet, if one is willing to invest some time reading the documentation, it's possible to achieve much better results. Granted, it's much more time-consuming. It's also so much more satifsfying.

Top comments (0)