Handcrafting your own SVG
Nicolas Fränkel
Posted on August 23, 2020
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 specifictranslate
- 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.
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>
- Define the
path
- Display the defined
path
usinghref
. SVG removed thexlink
namespace, so don't usexlink: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>
- Define coordinates starting from the origin
- Translate the bevel
- 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:
- A "compound" syntax using a single
matrix
attribute e.g.transform="matrix(3 1 -1 3 30 40)
. - 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>
- Define the translation at the
bevel
class level - 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 -->
- Start from point defined by x=25 and y=0
- From the previous point, draw a horizontal line up to x=500. In effect, it thus ends at (500, 0)
-
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.
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.
Posted on August 23, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.