Drawing with SVG: The Basics

Since SVG is a drawing format, it makes sense to cover drawing commands first.

The container

<svg> Wraps and defines the entire graphic. <svg> is to a scalable vector graphic what the <html> element is to a web page.

The example below is the minimum SVG example:

<svg  viewbox="0 0 100 100"
      xmlns="http://www.w3.org/2000/svg"
      xmlns:xlink="http://www.w3.org/1999/xlink">
  <!-- content goes here -->
</svg>

I’ve chosen to use CSS for the height and width of the SVG images and to explicitly list the default namespace for SVG along with the namespace for XLink, which we’ll use to create links inside the SVG element.

Drawing Primitives

Now that we’ve refreshed our minds about the SVG element and ViewBox attribute let’s draw.

Line

The simplest drawing primitive is line. It will draw a single straight line between two coordinates, defined by the pairs x1 y1 and x2 y2 and a stroke that represents the line’s color. These are all required attributes

<svg  viewbox="0 0 100 100"
      xmlns="http://www.w3.org/2000/svg"
      xmlns:xlink="http://www.w3.org/1999/xlink">

  <line   x1="10" y1="10
          x2="90" y2="50"
          stroke="rebeccapurple">
</svg>

Polyline

<polyline> creates straight lines connecting several points; usually creating open shapes as the last point doesn’t have to be connected to the first point.

The points attribute is mandatory and represents the coordinates for the different coordinates for the line.

fill represents the color inside the lines and stroke represents the color of the lines themselves.

<svg  viewbox="0 0 200 100"
      xmlns="http://www.w3.org/2000/svg"
      xmlns:xlink="http://www.w3.org/1999/xlink">

  <!-- Example of the same polyline shape with stroke and no fill -->
  <polyline points="0,
                    25 150,
                    25 150,
                    75 200,
                    0"
            fill="blue" stroke="black" />
</svg>

polyline is different than a polygon, discussed later.

Rect

rect allows us to create rectangles and squares in SVG. We can control whether the element has rounded corners and both the line (stroke) color and the color of the inside of the element.

<svg  viewBox="0 0 220 100"
      xmlns="http://www.w3.org/2000/svg"
      xmlns:xlink="http://www.w3.org/1999/xlink">
  <!-- Simple rect element -->
  <rect x="10" y="10" width="50" height="50" />

  <!-- Rounded corner rect element -->
  <rect x="120" y="10" width="50" height="50" rx="15" ry="15" />
</svg>

It takes four required attributes and, at least, four optional ones. The required attributes are:

  • x and y are the coordinates for the rectangle’s origin
  • width and height are the rectangle’s dimensions

The optional attributes I use the most are:

  • rx and ry control the size of the rounded corners. Combined they are similar to CSS’ border-radius property
  • stroke controls the color of the line drawing the element
  • fill controls the color inside of the element drawn with the stroke color

If you use stroke and fill you must use both. At least in Chrome, using one of these will render the element invisible.

Circle

Circles are self explainatory. They draw circles of the given radius at the specified coordinates.

<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
  <circle cx="25" cy="22" r="20"
    fill="navy" stroke="transparent" style="opacity: 0.25"/>
</svg>
  • cx and cy indicate the position of the ellipse
  • r indicates the radius of the circle

Ellipse

The ellipse element is what SVG uses to draw ellipses based on a center coordinate, and both their x and y radius.

<svg  viewBox="0 0 200 100"
      xmlns="http://www.w3.org/2000/svg"
      xmlns:xlink="http://www.w3.org/1999/xlink">
  <ellipse cx="100" cy="50" rx="100" ry="50" />
</svg>

The required attributes are:

  • cx and cy indicate the position of the ellipse
  • rx and ry represents the individual radius (X and Y) of the ellipse

Optional attributes

  • stroke controls the color of the line drawing the element
  • fill controls the color inside of the element drawn with the stroke color

If you use stroke and fill you must use both. At least in Chrome, using one of these will render the element invisible.

Polygon

Here’s where things get interesting. Polygons allow us to create multiline closed shape consisting of a set of connected straight line segments… think of all the faces of each shape of your RPG dice. The last point is connected to the first point, unlike the polyline element where the shapes are open.

<svg  viewBox="0 0 100 100"
      xmlns="http://www.w3.org/2000/svg">
  <polygon points="0 20, 40 20, 20 0"
            stroke="black" fill="purple">
  </polygon>
</svg>

The polygon has only one required attribute. points defines at least three sets of coordinates for the lines we want to draw.

Path

This is the heavy duty drawing element in SVG. I will cover the basics here and will refer you to MDN articles on the path element and the d attribute.

Where all the elements we’ve discussed so far allow you to draw discrete elements, the path allows you to create arbitrary elements using movement and drawing primitives exclusive to the path element.

SVG defines 6 types of path commands, for a total of 20 commands:

  • MoveTo: M, m
  • LineTo: L, l, H, h, V, v
  • Cubic Bézier Curve: C, c, S, s
  • Quadratic Bézier Curve: Q, q, T, t
  • Elliptical Arc Curve: A, a
  • ClosePath: Z, z

Note that the commands are case sensitive and that the upper case M command means something different than lower case m. See the reference for the d command on MDN for a full reference.

In the example below we perform the following tasks:

<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
  <path d=" M 10,30
            A 20,20 0,0,1 50,30
            A 20,20 0,0,1 90,30
            Q 90,60 50,90
            Q 10,60 10,30 z"
      fill="red" stroke="transparent"
    />
</svg>

In this example we move the drawing element to a set of coordinates, we draw two arcs and we draw two elements using quadratic bezier curves before we close the path to make a full shape.

Conclusion

I know that doing this by hand can be too much. Fortunately, tools like Adobe Illustrator from Adobe and Inkscape allow you to export illustrations as SVG to use on the web.

Finally Understanding SVG (I think)

SVG is a powerful vector graphics format that works particularly well for icons and other line artwork in web pages. This is my attempt at documenting what I understand of SVG and its component elements and children.


It is important for me to know how SVG works, even if there are tools that will generate the SVG for me.

The W3C began development of SVG in 1999 and released as a W3C specification in 2001. It is an XML-based vector image format for two-dimensional graphics and supports interactivity and animation.

SVG images and their behaviors are defined in XML text files and can be best thought of as a set of instructions for drawing shapes and defining behavior. The instructions in SVG read something like this: “Go to such-and-such coordinate; draw a rectangle that is so tall and so wide.

The fact that it is text means that they can be searched, indexed, scripted, and compressed. As XML files, SVG images can be created and edited with any text editor, as well as drawing software.

Let’s look at the biggest source of confusion for me: viewports and viewBoxes

Viewport versus viewBox

The basic SVG canvas element defines the viewport of the image with the origin at the top left corner, point (0, 0), the x-axis points towards the right, and the positive y-axis points down. One unit in the initial coordinate system equals one “pixel” in the viewport.

To create an 800px by 600px SVG element use the following code:

<svg  width="800px" height="600px"
      xmlns="http://www.w3.org/2000/svg"
      xmlns:xlink="http://www.w3.org/1999/xlink">
  <!-- the content goes here -->
</svg>

It will produce an SVG element like the one below, captured from Sara Soueidan’s Interactive SVG Coordinate System demo

SVG viewport with only height and width

Size attributes are optional. We’ll look at how to manage to size and control the aspect ratio of SVG images later in the post. One thing I’m not sure about is whether we need the dimensions to prevent unwanted reflow of the page… more research needed there.

In addition to deciding what size you want your SVG to be, you’re also going to have to decide how you want your graphic to scale to fit that size.

Viewbox

One of the hardest areas for me to wrap my head around is the fact that we can define our own viewports inside the <sv> element using the viewBox attribute of the SVG element and how it interacts with the SVG element dimensions and the preserveAspectRatio.

The viewBox is an attribute of the <svg> element. Its value is a list of four numbers, separated by whitespace or commas: x, y, width, height.

The width is the width in user coordinates/px units, within the SVG code, that should be scaled to fill the width of the area into which you’re drawing your SVG (the viewport in SVG lingo). Likewise, the height is the number of px/coordinates that should be scaled to fill the available height. Other units, such as inches or centimeters, will also be scaled to match the scale created by the viewBox.

The x and y numbers specify the coordinate, in the scaled viewBox coordinate system, to use for the top left corner of the SVG viewport. For simple scaling, you can set both values to 0. However, x and y values are useful for two purposes:

  • Create a coordinate system with an origin centered in the drawing (this can make defining and transforming shapes easier)
  • Crop an image tighter than it was originally defined.

Once you add a viewBox to your <svg> document you can use that SVG file as an image, or as inline SVG code, and it will scale perfectly to fit within whatever size you give it, it will not be stretched or distorted if you give it dimensions that don’t match the aspect ratio.

Preserve Aspect Ratio

The viewBox attribute has a sidekick: preserveAspectRatio. It has no effect unless there’s a viewBox to define the aspect ratio of the image. When there is a viewBox, preserveAspectRatio describes how the image should scale if the aspect ratio of the viewBox doesn’t match the aspect ratio of the viewport. The default behavior works well most of the time: the image is scaled until it just fits both the height and width, and it is centered within any extra space.

preserveAspectRatio default behavior can be explicitly set with preserveAspectRatio=”xMidYMid meet”. The first part, xMidYMid tells the browser to center the scaled viewBox region within the available viewport region, in both the x and y directions.

The second half of the default preserveAspectRatio, meet, is the part that tells the browser to scale the graphic until it just fits both height and width.

The attribute packs a lot in the definition of its possible values. The table in MDN’s preserveAspectRatio Syntax, explains the possible values and what they do.

Be careful with capitalization: SVG, like XML, is case sensitive. The x is lowercase but the Y is capital.

So, now that we have our SVG graphic complete with viewBox and preserveAspect, how do we scale the graphic without distorting it?

The viewBox attribute is really all you need, but you’re free to use preserveAspectRatio to adjust the alignment. The correct viewBox values will vary from image to image.

When an SVG file has a viewBox, and it is embedded within an <img> element, browsers will (nearly always) scale the image to match the aspect ratio defined in the viewBox, with the notable exception of Internet Explorer.

In many cases, it’s better to use inline SVG. Inline SVG reduces the number of HTTP requests, allows user interactions, and can be modified by the CSS in your main web page.

They will only scale if you’re using the latest Firefox or Blink browsers. Just set the viewBox on your </svg><svg>, and set one of height or width to auto. The browser will adjust it so that the overall aspect ratio matches the viewBox.

Test for compatibility with your target browsers and that the result will work as intended.

Links and resources

Extracting JSON into another JSON file

As part of a larger project, I had to figure out how to use node to scrape the content of a page to build a JSON file from the components using Node. Rather than scrape the page I discovered a REST API endpoint that will provide search results as JSON. This is the function I’m using to fetch the JSON, extract its components and build the new JSON object that I’ll pass to other functions.

It is not pretty… I’m the first one to admit that. But it does the work until I find something better 🙂

Problem Statement

As part of building an action for Google assistant that will add voice to the search results for my blog I came up with the following issue:

How do I provide a JSON payload for Assistant to use if the WordPress generated by the WordPress REST API provides way more information than we need

Different solutions

One quick solution is to scrape the page using tools like Cheerio but it still only provides HTML content, not the JSON I need. It is possible but the process is cumbersome and we need to change the code every time we change the page.

I could also use the JSON I received from WordPress as is but it makes for a large download for only using a fraction of the data in the resulting product.

I decided to go with node-fetch for my solution. Node-fetch is a Node implementation of the WHATWG fetch standard and allows me to do both promise and async/await code. I went with the latter option to make myself be comfortable with async/await.

The code below takes a single parameter representing the query that we want to search for. With that query the function will:

  1. Create an empty array to store the data we collect
  2. Create an async function
  3. Replace all the spaces in the query parameter with + signs
  4. Await for the fetching of the encoded query. We use a template string literal to interpolate the value of the query
  5. Convert the response to JSON
  6. Using a for loop
  7. Create an item array passing title, link, and excerpt. Since the excerpt begins with an HTML tag we strip it by slicing the element starting at position 3 (0-based) and going for 150 characters
  8. Run the element through JSON.stringify and push the result into the jsonData array that we created in step 1
  9. Log any errors to the console
  10. Execute the function with a parameter to search. We make sure that it has two words to exercise all aspects of the function
const fetch = require('node-fetch');

const jsonData = []; // 1
const buildJSON = async function(query) { // 2
  try {
    encodedQuery = query.replace(new RegExp('\\+', 'g'), ' '); // 3
    const response = await fetch(`https://publishing-project.rivendellweb.net/wp-json/wp/v2/posts?search=${encodedQuery}`); // 4
    const json = await response.json(); // 5
    // Do the work here instead of the console.log
    for (let i = 0; i < json.length; i++) { // 6
      const item = { // 7
        title: json[i].title.rendered,
        link: json[i].link,
        body: json[i].excerpt.rendered.slice(3, 150),
      };
      jsonData.push(JSON.stringify(item)); // 8
    }
  } catch (err) { // 9
    console.log('There\'s been an error getting the data', err);
  };
};

buildJSON('lazy loading'); // 10

Conclusion

While this function was first created to work converting one JSON file into another we can replace the for loop with whatever instructions that we need to accomplish our goals.

Replacing Images with WebP equivalents on the server

WebP provides smaller files and better quality than equivalent JPG or PNG files. The problem is that not all browser support the WebP format, only Chromium-based browsers like Chrome, Opera, plus Edge and Firefox (according to caniuse.com. There is no easy way to support browsers that support WebP and those that don’t on the same page without modifying the HTML on the pages.

Another option is to let the server replace images with their WebP versions for those browsers that support it. By configuring the server to replace the images, where supported, without having to change the HTML on your pages you gain the benefits of the WebP format

How to do this will depend on what server you’re using. For this, we’ll explore configurations for Nginx and Apache.

Nginx

If you’re using Nginx the following code will serve WebP images if the browser supports the format and there is an image available on the server.

Note that this example is also set up to experiment with

The configuration will do the following:

  1. Check if the accept header include webp
  2. Check if there’s a WebP image on the server
  3. If there is a file on the server then add the Vary Accept header
  4. If the browser supports WebP images then replace the image with the WebP equivalent
location / {
  if ($http_accept ~* "webp")
  {
    set $webp_accept "true";
  }

  if (-f $request_filename.webp) {
    set $webp_local  "true";
  }

  if ($webp_local = "true") {
    add_header Vary Accept;
  }

  if ($webp_accept = "true") {
    rewrite (.*) $1.webp break;
  }

  if ($http_user_agent ~* "(?i)(MSIE)") {
    proxy_hide_header Vary;
    add_header Cache-Control private;
  }

  # Rest of configuration goes here
}

Apache

The Apache configuration is a set of rewrite rules that will do the same thing for Apache that the configuration for Nginx did.

The example below can be used in the global server context (httpd.conf), virtualhost context (<VirtualHost> blocks), or directory context (.htaccess files and <Directory> blocks).

The process is almost the same as the one for Nginx:

  1. If the rewrite module is installed and active
    1. Activates the rewrite engine and sets the base for the following on steps
    2. Checks if the user agent matches Chrome or Opera
    3. Checks if the browser sent the Accept header
    4. Check if the WebP file exists on the server
    5. Replace the images with the WebP equivalent
      1. It uses case insensitive matching
      2. It forces the mime type of the result to be image/webp
      3. It sets the environment variable webp
      4. It stops the matching. This is the last step in the matching chain
  2. If the headers module is installed
    1. Set the Vary Accept header
  3. Add the WebP mime type and associate it with the .webp extension
<IfModule mod_rewrite.c> #1
  RewriteEngine On #i
  RewriteCond %{HTTP_USER_AGENT} Chrome [OR] #ii
  RewriteCond %{HTTP_USER_AGENT} Opera [OR] #ii

  RewriteCond %{HTTP_ACCEPT} image/webp [OR] #iii

  RewriteCond %{DOCUMENT_ROOT}/$1\.webp -f #iv

  RewriteRule (.+)\.(?:jpe?g|png)$ $1.webp [NC,T=image/webp,E=webp,L] #v
</IfModule>

<IfModule mod_headers.c> #2
  Header append Vary Accept env=REDIRECT_accept #i
</IfModule>

AddType image/webp .webp #3

Conclusion

It is possible to swap JPG and PNG images for equivalent WebP images without modifying the HTML documents. This is particularly important for older content that is unlikely to be updated.

This technique is not a replacement for client-side responsive images but a complement for when updating existing content with the code for responsive images is not feasible because of time or cost.

Serving the right language via content negotiation

The problem Statement: I want the server to deliver the right document (variant) based on the browser’s language preferences. Browsers send an Accept-Language header which lists their preferred language(s).

Apache

Before we configure our language negotiation parameters we need to set up things on the server. You can set this on the default httpd.conf configuration file (meaning that it’ll work in all virtual hosts) or you can set in in each virtual host configuration (meaning it’ll only work for that virtual host)

The two items are:

  • LanguagePriority allows you the language precedence during content negotiation. If more than one version of a file is available
  • ForceLanguagePriority allows you to serve a result page rather than MULTIPLE CHOICES (Prefer) in case of a tie or NOT ACCEPTABLE (Fallback) in case no accepted languages matched the available variants.
<IfModule mod_negotiation.c>

LanguagePriority en-US en ca cs de el es pt pt-BR zh-CN zh-TW

ForceLanguagePriority Prefer Fallback

</IfModule>

MultiViews

MultiViews works as follows: if you request a resource named foo, and foo does not exist in the directory, Apache will search for all files named foo.*, like foo.html, foo.html.de, foo.html.en, foo.de.html, foo.en.html, foo.en.html.gz, foo.gz.en.html, foo.html.gz.en, foo.html.en.gz, and so on.

If we have this and the client’s browser is configured with German as the primary language then the server will send index.html.de as the first option.

[email protected]:~# ls -la /var/www/
total 20
drwxr-xr-x  2 root root 4096 Jul 20 00:13 .
drwxr-xr-x 14 root root 4096 Feb 14 18:43 ..
-rw-r--r--  1 root root  196 Jul 20 00:06 index.html.de
-rw-r--r--  1 root root  186 Jul 20 00:03 index.html.en
-rw-r--r--  1 root root  207 Jul 20 00:09 index.html.fr
[email protected]:~#

MultiViews is a per-directory setting, i.e., it has to be set with an Options directive in a <directory>, <location>, or <files> section in the Apache configuration. It has to be set explicitly with Options MultiViews. Using Options All does not set it.

The documentation for options lists valid values you can use in the directive.

<Directory /var/www/>
  Options Indexes FollowSymLinks MultiViews
  DirectoryIndex index.html
    AllowOverride None
    Order allow,deny
    allow from all
</Directory>

That’s basically all we need for content negotiation. Using Language Priority, Force Language Priority and multiviews allow Apache to handle multiple versions of a page without us having to do anything on the client other than writing the different versions of the page.

Nginx

The code is taken from this serverfault answer.

If the browser sends headers like this one: accept-language: en,en-US;q=0.8,ja;q=0.6 we can configure the server to handle content negotiation with the following block.

The configuration does the following:

  1. Sets the variable $first_language to the value of the accept-language header
  2. Matches the string to the fir comma. This can be just the top level language code (en), the language and country code (en-US) or the language plus qualifier (en-US;q=0.8)
  3. Set the default suffix to a language. In this case, English (en)
  4. If the first language is Japanese (ja) then set the language suffix to Japanese

This will make the server render the page with the given language suffix.

set $first_language $http_accept_language; #1
if ($http_accept_language ~* '^(.+?),') {
    set $first_language $1;
}

set $language_suffix 'en';
if ($first_language ~* 'ja') {
    set $language_suffix 'ja';
}

One shortcoming of this approach is that we need to have a list of our supported languages in hand as we prepare this configuration and every time we add a language we have to add if statements to the later block.

The nginx_accept_language_module module parses the Accept-Language field in HTTP headers and chooses the most suitable locale for the user from the list of locales supported at your website.