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"
  <!-- the content goes here -->

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.


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(`${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


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 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.


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


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 mod_headers.c> #2
  Header append Vary Accept env=REDIRECT_accept #i

AddType image/webp .webp #3


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).


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



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.en,, 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 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
-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
[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

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.


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.

WP-CLI: Posting content from the command line

One thing that has always bugged me about WordPress is that it has a GUI only administrator interface and it made it more cumbersome than it needs to be, even for simple publishing workflows like mine where I copy the Markdown text, spell check it, preview it, fix any problems in the preview and scheduled publication.

WordPress CLI (wp-cli) adds some flexibility for command line operations for WordPress installations. It also allows you to manage remote installations of WordPress. It’s not a perfect solution but it beats having to do everything through the GUI hands down.


As a Mac user, I will install the CLI via Homebrew because it makes it easier and because sooner rather than later I’ll forget that I installed it manually and will run it manually anyway.

Instructions for other operating systems are in the Installation section of the CLI handbook.

Creating a global configuration file

# Global parameter defaults
user: carlos
  - db drop
# Aliases to other WordPress installs
# An alias can include 'user', 'url', 'path', 'ssh', or 'http'
    path: <path removed for security>
    ssh: <ssh removed for security>
    path: <path removed for security>

Using in a local dev environment

The basic installation will allow you to work in your local development environment. For example, the following commands will create a post. These are some of the things I’m looking to use as part of my workflow.

The first example creates a post and schedules for a future date.

# Create post and schedule for future
wp @local post create --post_title='A future post'\
  --post_status=future \
  --post_date='2020-12-01 07:00:00'

The second example takes the content of the file as the body of the post and assigns it to categories indicated by the numbers in the field.

# Create a post with content from given file
# and assign it to pre-defined categories
wp @local post create ./post-content.txt \
  --post_category=201,345 \
  --post_title='Post from file'\

The last example combines future posting using the content of a file as the body of the post.

This gives you the most flexibility to review and make changes before the date of the post; It allows you to preview in the admin screen but it requires some discipline working far enough ahead of time that you won’t be with your back against the wall with deadlines.

wp @local post create ./post-content.txt \
  --post_title='A future post'\
  --post_status=future \
  --post_date='2020-12-01 07:00:00'

The next step is to run the same commands on our remote production server.

Using in a remote installation

In order for WP-CLI to work on the remote server is to install it on your server. The installation requires the following items

  • Shell access to your production server
  • Write access to the WordPress installation

If you don’t meet the pre-requisites you will not be able to run these scripts.

These examples duplicate those in the previous sections.

The first one creates a post and schedules it in the future.

# Create post and schedule for future
wp @production post create\
  --post_title='A future post'\
  --post_status=future \
  --post_date='2020-12-01 07:00:00'

The second example takes the content of a file, assigns it to categories and gives it a title.

# Create a post with content from given file
wp @production post create \
  --post_content="$(cat post-content.txt)" \
  --post_title='Post from file'\

The final example expands the previous example by removing the categories, changing the status from draft to future and assigning the date when the post will become available.

# Create a post with content from file
# and schedule it into the future
wp @production post create \
  --post_content="$(cat" \
  --post_title='A future post'\
  --post_status=future \
  --post_date='2019-02-07 07:00:00'

The posts may take a long time to appear in the target blog so be ready and publish ahead of time, longer than you normally would.

Duplicating my workflow using WP-CLI

So now that I have working examples, I can duplicate the workflow I currently use with WP-CLI.

I want to be able to do two things:

  • Upload draft posts to my blog so I can proofread them and make any changes in the admin post editor
  • Schedule posts when I feel confident that they are ready to go

The first script takes advantage of positional parameters to feed the data we need into the script. The first parameter is the file that we want to publish and the second one is the title.

This will get us a draft post ready for review

#!/usr/bin/env bash
# Positional parameters
# $1 is the file we want to publish
# $2 is the title of the post.
wp @production post create \
  --post_content="$(cat $1)" \

The second use case is a little more complicated both in the number of parameters and how we build the script to handle them.

#!/usr/bin/env bash

wp @production post create \
  --post_content="$(cat $1)" \
  --post_status=future \
  --post_date="$3 $4"

The script has additional parameters. They are listed and explained below

  • $1 Name of the file
  • $2 Title
  • $3 Date for the post. Format 2019-02-07
  • $4 Time for the post. Format 07:00:00

Using this script would let you run it from the command line and, if needed, include it as part of a build automation process.


Using WP-CLI you can automate a lot of the management tasks that would normally require access to the administrator UI and give you a more direct way of handling posting to your blog or blogs.

We haven’t covered all the functionality available in WP-CLI. There is a lot more you can do and it’s definitely worth exploring.

Links and Resources