Chapter 6. SVG and Vector Images

So far our focus has been on raster image formats—and for good reason. Raster formats are the dominant image format on the Web. They’re capable of representing highly detailed and photographic images, whereas vector formats are not. Combine that with the large ecosystem of tooling and editors supporting them, and it’s little wonder that they’ve reigned supreme.

However, raster images are not without limitations. They don’t scale well, and there is an almost linear relationship between display size and file size. Both of these issues have become glaringly obvious thanks to the rapid increase in the diversity of devices accessing the Web. Confronted with the variety of screen sizes being used, web designers and developers have become painfully aware of these issues. There have been two major outcomes of this:

  • New responsive image standards have evolved.

  • There has been an increased interest in vector formats, most notably the Scalable Vector Graphics (SVG) format.

We will be discussing the responsive image standards in Chapter 11, Responsive Images. In this chapter, let’s take a look at how vector formats overcome the limits of raster formats, and how and when to use them.

The Trouble with Raster Formats

As we saw in Chapter 2, raster images are composed of a matrix of pixels. This matrix matches the dimensions of the actual image, and each cell represents a single pixel of the image. While this matrix enables raster formats to represent detailed photographic images, it also causes issues when trying to scale raster images.

If a browser or other application needs to scale a raster image beyond the size of that matrix, it needs to create new pixels. For example, scaling a 100x100-pixel image to 120x120 means the browser needs color data for an additional 4,400 pixels (120x120–100x100) that are not represented in the original matrix.

Lacking the proper information for these additional pixels, the browser does the next best thing: it guesses. The browser looks at the surrounding pixels to get an idea of what color values are present, and then uses that information to figure out approximately what color values these additional pixels should contain. The result of all these extra pixels, with their approximated values, is that a scaled raster image will appear blurry and full of digital artifacts.

If a developer wants to serve a raster image intended to be displayed at a variety of sizes without scaling artifacts, he or she will need to produce the image at a number of different dimensions and serve them using the appropriate responsive standards (discussed in Chapter 11). This helps to minimize the scalability issues, but it highlights another limitation of raster formats: the almost linear relation between image dimensions and file size.

Imagine you were going to include a simple square image on your site. The square would need to be displayed at 100x100 pixels on the smallest screen, 700x700 pixels on the largest, and 400x400 pixels somewhere in between. Even though the image is not complex at all, the way raster formats work means that all pixels will need to be processed, compressed, and outputted to the final file. This means our 700x700 version of the square is likely to be significantly heavier than our 100×100 pixel version of the square—it has to store 490,000 pixels (700×700) of data compared to 10,000 pixels of data (100×100) for the smaller image.

What Is a Vector Image?

Vector formats help to address these limitations by describing how an image should be constructed, rather than storing all the pixel data. Vector images comprise a series of points, lines, curves, shapes, and colors that are described by mathematical expressions. This allows the client to calculate how the image should be displayed, maintaining these basic expressions no matter the size of the image ultimately being displayed.

Let’s go back to our square. Using a vector format like SVG, the instruction for building the square would look like this:

<svg xmlns="http://www.w3.org/2000/svg"
  width="100" height="100" viewBox="0 0 100 100">
  <rect height="100" width="100" fill="#f00" />
</svg>

If the specific markup there doesn’t make sense to you yet, don’t worry—we’ll get to the basics of the SVG format in a minute. For now, it’s enough to know that these three lines of code describe a perfect square, filled in with the color red. This same markup accurately describes the square no matter the resolution; whether displayed at 100x100 or 700x700, these commands are all the client needs to determine just how to draw the image—no difference in file size necessary.

There’s no such thing as a free lunch, though—there is a tradeoff being made here. While using markup to describe the content of an image can greatly reduce the file size, it does require the client to do more work processing that information and drawing it out to the screen. Typically, this process involves the data in the SVG being software rendered to a bitmap (rasterized), and then uploaded to the GPU. The more complex the image, the more instructions the software needs in order to perform rasterization. The more instructions, the longer it will take to process and follow them all. This is why you will frequently see poor performance on older browsers, particularly on low-powered mobile devices, for SVG. It’s a complex task that can tax the device.

Thankfully, this situation is improving. Browsers are continuing to evolve and improve the efficiency of how they display and process SVG images, eliminating the bulk of the on-device overhead. Still, it’s a good idea to remember that the more complex your SVG image, the more work you’ll be requiring the client to do in order to display it. That complexity also means a heavier file size. As a result, vector formats should be used primarily on simple images: those with a limited number of colors and shapes.

SVG Fundamentals

On the Web if you want to use a vector format, it’s going to be SVG. There aren’t any other contenders with any semblance of cross-browser support. Before we get into the specifics of how to optimize SVG files for performance, let’s spend a few pages getting to understand a few core concepts of the format.

Hungry for More SVG Information?

It’s beyond the scope of this chapter to try to provide a comprehensive, detailed resource for the SVG format—we’re primarily focused on the basics and the performance impacts. If you’d like to dig deeper, Sara Souedian has done a tremendous job writing detailed posts on her blog.

If you’re just starting to get familar with the SVG format, it can be useful to remember that it’s markup, like HTML. In HTML, a root element (<html>) and any number of other elements enable you to define particular sections of your page. A distinct paragraph goes in the <p> element. A table is marked up with the <table> element.

SVG works similarly. There is a root element (<svg>) and a number of different core elements. The difference is that in the case of SVG, those core elements are describing shapes and lines. Just as with elements in an HTML page, we can style the individual elements within an SVG document using CSS and interact with them using JavaScript. This not only enables far more granular control over how the individual pieces constituting an SVG element are displayed, but also allows you to animate or otherwise alter these elements.

The Grid

SVG elements are positioned based on a coordinate system. The top-left corner, for example, has the coordinates (0,0). Any element positioned within the document is measured from that top-left corner. If you’re thinking that sounds similar to what you learned in school about graphs, you’re right—with one wrinkle. In SVG, the x attribute pushes you to the right and the y attribute pushes you toward the bottom—the opposite of what you may be used to.

Consider, again, a simple red square:

<rect x="10" y="10" height="100" width="100" fill="#f00" />

This element would result in a square that starts 10 px from the left and 10 px from the top, then spans 100 px to the right and 100 px down. Where things get interesting is when you consider the canvas that the coordinate system relates to.

Understanding the Canvas

In the context of a web page, there are two variables in terms of size: the dimensions of the page itself, and the dimensions that can be viewed at a single moment in time. For example, you may have a web page that, with all content and images loaded, is 3,000 px high. Yet the browser window itself may only show 800 px vertically at a single time. Similarly, your page may be wider than the width of the browser. In either case, some content will not be visible—it’ll be cut off from view.

The same is true of SVG. You have a canvas that can be thought of as infinite in height and width, and you have a viewport that defines what part of that SVG canvas is actually visible—anything that lies outside of that viewport area is cut off.

To define the viewport, you apply the width and height attributes to the root <svg> element:

<svg xmlns="http://www.w3.org/2000/svg"
  width="100" height="100">
  <!-- super exciting SVG content to be drawn -->
</svg>

This example establishes a viewport of 100x100 pixels. In addition, by establishing the viewport, we’ve also initialized two different coordinate systems: the initial viewport coordinate system and the initial user coordinate system.

The initial viewport coordinate system is based on the viewport. Its origin is the top-left corner of the viewport—point (0,0). The initial user coordinate system is based on the SVG canvas. Initially, it’s identical to the viewport coordinate system and has the same origin point of (0,0). The primary difference between the two is that the user coordinate system can be altered via the viewBox attribute.

viewBox

The viewBox attribute accepts a value made up of four parameters: min-x, min-y, width, and height. The first two parameters, min-x and min-y, set the upper-left corner of the viewbox, while the final parameters, width and height, determine the dimensions. The viewBox attribute is crucial—without it browsers will not scale SVG elements.

If we revisit our example from before, we could set the viewBox to be identical to the SVG viewport by applying the viewBox attribute as follows:

<svg xmlns="http://www.w3.org/2000/svg"
  width="100" height="100" viewBox="0 0 100 100">
  <!-- super exciting SVG content to be drawn -->
</svg>

That in itself is not particularly interesting. Where it gets fun is when we start to alter the viewBox so that it has different dimensions from the viewport or shift it from the top-left corner a bit.

For example, let’s alter the viewBox to be a quarter of the size of the viewport itself:

<svg xmlns="http://www.w3.org/2000/svg"
  width="100" height="100" viewBox="0 0 25 25">
  <!-- super exciting SVG content to be drawn -->
</svg>

In this example, because we’ve kept min-x and min-y set to “0” (the first two values of the viewBox attribute), the top-left corner of the viewBox is still the same point as the viewport: (0,0). However, the dimensions of the viewBox itself are only a quarter of the size of the full viewport.

You can imagine the viewBox in this case to be an instruction to the client to zoom in on a certain portion of the viewport area. In this case, the selected area will be the first 25x25 area, starting at the top-left corner. Anything that is part of the SVG element that does not lie within the viewBox will be ignored.

With that 25x25 area selected, the next thing that happens is that the client will zoom in on it—scaling it up until the area within the viewBox is stretched to fill the full 100x100-pixel viewport of the SVG itself (see Figure 6-1). What we’ve essentially done here is select a subset of the SVG image and zoom in by a factor of four.

images/hpim_0601.png
Figure 6-1. Zoomed in area of an SVG image subset

We’ve also changed the user coordinate system. It no longer matches the viewport coordinate system. Instead, one user coordinate unit is equivalent to four units in the viewport coordinate system (because we zoomed in by a factor of four).

Sara Soueidan has compared this to Google Maps. You can choose a specific region within a given map, and Google Maps will zoom in on it. The rest of the map is still there, it’s just not visible as it extends beyond the viewport. This is exactly what happens here: the rest of the content within the SVG file still exists, it’s just not visible. It’s cropped out.

It’s a Matter of Scale

Just as you’ll often want to keep the height and width attributes around, you’ll almost certainly want to make sure the viewBox attribute is present and accounted for. If a browser sees an SVG image without a defined viewBox, it will not scale that image—effectively defeating one of the primary reasons why the SVG format exists.

Getting into Shape

Most drawing within an SVG image is done by including a number of different elements that correspond to various shapes. The basic shapes included within SVG are listed in Table 6-1.

Table 6-1. Basic SVG shapes
Basic shape Corresponding element Example

Rectangle

rect

<rect x="0" y="0" width="50" height="50"/>

Circle

circle

<circle cx="50" cy="75" r="20"/>

Ellipse

ellipse

<ellipse cx="75" cy="75" rx="20" ry="5"/>

Straight line

line

<line x1="0" y1="110" x2="20" y2="150"/>

Group of connected lines

polyline

<polyline points="0,0 30,0 15,30"/>

Polygon

polygon

<polygon points="10,0 60,0 35,50"/>

In addition to the basic shapes, there is also the <path> element. If using basic shapes is roughly the equivalent of tracing predefined stencils on a paper, then the <path> element is the equivalent of handing you a pencil and letting you draw whatever you wanted. Because of this, the <path> element is incredibly powerful and expressive. That power comes at the cost of complexity, though.

For example, if you wanted to draw a 100x100-pixel red square using the <rect> element, the code would look something like this:

<svg xmlns="http://www.w3.org/2000/svg"
  width="100" height="100" viewBox="0 0 100 100">
  <rect height="100" width="100" fill="#f00" />
</svg>

In contrast, accomplishing the same thing with the <path> element would involve quite a bit more information:

<svg xmlns="http://www.w3.org/2000/svg"
  width="100" height="100" viewBox="0 0 100 100">
  <path d="M 0 0 H 100 V 100 H 0" fill="#f00" ></path>
</svg>

Let’s break down that <path> description attribute (d) to see exactly what’s happening here:

M 0 0

The M represents the “MoveTo” command and moves to a specific point within the coordinate system. It’s necessary at the start of a path description to establish the starting point. Here, we’ve specified that the starting point should be the top-left corner of the user coordinate system—point (0,0).

H 100

H is a shortcut for drawing a horizontal line. In this case, we’re telling the client to draw a horizontal line 100 px wide, starting at the current position (which we defined using the initial “MoveTo” command).

V 100

At this point, we’ve drawn a 100 px horizontal line from point (0,0), bringing us to point (100,0). Now we need to tell the client to draw a vertical line, which we do using the V command. In this example, we’ve just told the client to draw a 100 px vertical line, bringing us to point (100,100).

H 0

We’re almost there! All we need to do now is tell the client to draw one more horizontal line, back to point (0,100). We don’t need to tell it to close the shape—it will connect the final point to the origin by default.

With the <rect> element, we didn’t have to go through the process of explaining each individual command. That’s because the <rect> element is essentially a shortcut—it understands the basic process of drawing out a rectangular shape, so all it needs are the starting point and the dimensions.

This is true of each basic shape: they’re essentially recipes. They know the steps needed to construct a very specific type of shape and, as a result, don’t need as many detailed instructions as required with the <path> element.

Our square example wasn’t too unwieldy, but that’s not always the case: the more complex the path you are describing, the more instructions you’ll need to provide. This can lead to significant bloat within the file. It also makes the client work harder to rasterize those paths. Basic shapes aren’t just shortcuts for you, the person coding the SVG files—they’re shortcuts that the browser can take as well.

For this reason, whenever possible, you’ll want to favor using basic shapes for drawing purposes—lightening the load over the network as well as the load on the client to display the image once it has been downloaded. Save the <path> element for more complex shapes and use them sparingly.

This isn’t necessarily a hard and fast rule—it would be boring if it were that easy. There are exceptions. The most typical are the polygon and polyline shapes, both of which can get rather complicated for more complex shapes. At times in those situations, the <path> element may actually be less complicated and bloated. As with anything in terms of performance, be sure to measure the impact to ensure you’re getting the results you’re after.

Grouping Shapes Together

One of the really nice features of SVG is that you can group and reuse elements within the SVG image, helping you avoid unnecessarily repetitive markup. There are four basic elements that enable this behavior: <g>, <use>, <defs>, and <symbol>. These elements will become particularly important in Chapter 10 when we discuss SVG sprite sheets.

The <g> element

Let’s say that we’ve re-created the basic shape of the Olympic rings in SVG using the following markup:

<svg xmlns="http://www.w3.org/2000/svg"
  viewBox="0 0 120 65">
     <circle stroke-width="3" fill="none" cx="25"
      stroke="rgb(11,112,191)" cy="25" r="15"></circle>
     <circle stroke-width="3" fill="none" cx="40"
      stroke="rgb(240,183,0)" cy="40" r="15"></circle>
     <circle stroke-width="3" fill="none" cx="60"
      stroke="rgb(0,0,0)" cy="25" r="15"></circle>
     <circle stroke-width="3" fill="none" cx="75"
      stroke="rgb(13,146,38)" cy="40" r="15"></circle>
     <circle stroke-width="3" fill="none" cx="95"
     stroke="rgb(214,0,23)" cy="25" r="15"></circle>
</svg>

Each circle is its own separate element. We define the position of each circle and apply a colored stroke to create our rings. When viewed together they appear as the Olympic rings (see Figure 6-2). Since we really want them as a unified object in this case, we can group them together using the group element (<g>), which is intended for logically grouping related elements together.

hpim 0602
Figure 6-2. Five individual circle elements are used, but the resulting image is one unified group

Here’s what our markup looks like if we group the circles together:

<svg xmlns="http://www.w3.org/2000/svg"
  viewBox="0 0 115 65">
  <g id="rings">
     <circle stroke-width="3" fill="none" cx="25"
      stroke="rgb(11,112,191)" cy="25" r="15"/>
     <circle stroke-width="3" fill="none" cx="40"
      stroke="rgb(240,183,0)" cy="40" r="15"/>
     <circle stroke-width="3" fill="none" cx="60"
      stroke="rgb(0,0,0)" cy="25" r="15"/>
     <circle stroke-width="3" fill="none" cx="75"
      stroke="rgb(13,146,38)" cy="40" r="15"/>
     <circle stroke-width="3" fill="none" cx="95"
      stroke="rgb(214,0,23)" cy="25" r="15"/>
  </g>
</svg>

Nothing has changed visually; we’ve just added a little more structure to our image by grouping those circles together and providing an id to refer to the group.

Structure is nice from a maintenance perspective, but by grouping these elements, we can also simplify our markup a little. If you notice, while the position and color of each circle differ, the fill and stroke-width remain the same. Attributes that are applied to a group element also apply to descendants of the group element. We can take advantage of this and pull out the stroke-width and fill, applying them to the group instead:

<svg xmlns="http://www.w3.org/2000/svg"
  viewBox="0 0 115 65">
  <g id="rings" stroke-width="3" fill="none">
     <circle cx="25" stroke="rgb(11,112,191)" cy="25" r="15"/>
     <circle cx="40" stroke="rgb(240,183,0)" cy="40" r="15"/>
     <circle cx="60" stroke="rgb(0,0,0)" cy="25" r="15"/>
     <circle cx="75" stroke="rgb(13,146,38)" cy="40" r="15"/>
     <circle cx="95" stroke="rgb(214,0,23)" cy="25" r="15"/>
  </g>
</svg>

Our markup is now both easier to read and less repetitive. Grouping also provides the benefit of making it much easier to transform these elements in CSS, or make them interactive using JavaScript. Instead of having to wrangle each individual element, we can apply our CSS or attach our JavaScript directly to the group element, and the descendants will all follow along.

The use element

Grouping these elements together also allows us to easily repeat the group by taking advantage of the <use> element.

Note

In order to use the xlink:href attribute without any errors, you’ll need to include the xlink namespace on the root svg element like so:

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

The <use> element allows you to refer to other content (like a group element) using the xlink:href attribute. You can then use the x and y attributes to move the new group’s origin to a new location. It’s important to note that the x and y coordinates are relative to the position of the original element.

For example, if we wanted to add another set of rings below our first set, we could expand our viewBox slightly and then reuse the group like so:

<svg xmlns="http://www.w3.org/2000/svg"
  xmlns:xlink="http://www.w3.org/1999/xlink"
  viewBox="0 0 115 130">
  <g id="rings" stroke-width="3" fill="none">
     <circle cx="25" stroke="rgb(11,112,191)" cy="25" r="15"/>
     <circle cx="40" stroke="rgb(240,183,0)" cy="40" r="15"/>
     <circle cx="60" stroke="rgb(0,0,0)" cy="25" r="15"/>
     <circle cx="75" stroke="rgb(13,146,38)" cy="40" r="15"/>
     <circle cx="95" stroke="rgb(214,0,23)" cy="25" r="15"/>
  </g>
  <use x="0" y="65" xlink:href="#rings" />
</svg>

In this example, we reference the rings group using the xlink:href attribute. We then specify that we want the starting x coordinate to be identical to the starting x coordinate of the original element (x="0") and that we’d like to shift the image down from the starting y coordinate of the original element (y="65"). The result is two sets of identical rings, stacked vertically (Figure 6-3).

hpim 0603
Figure 6-3. By using the use element, we’re able to duplicate the original group of rings without having to repeat ourselves in the markup

We could also have referenced an external SVG file inside our use element. The following snippet shows how you would do that:

<use x="0" y="65" xlink:href="/other/svg/file.svg#rings" />

Depending on what you’re trying to accomplish, referencing these files externally may be the ideal option, as it enables each SVG file you are using to be cached individually. There are browser support issues (in Internet Explorer prior to version 11) to be aware of, but we’ll discuss how to navigate those when we explore SVG spriting.

The defs element

Hopefully by now it’s becoming clear how useful the group element can be. However, you may have also noticed a catch: the group element is rendered onto the canvas. In the examples we’ve been showing, that’s OK. There are times, though, where you may want to have a pattern grouped together that you don’t want to render until you’ve included it elsewhere using the <use> element. To accomplish this, we can wrap the group element within a <defs> element—allowing us to define the rings as a set of grouped elements without having them rendered:

<svg xmlns="http://www.w3.org/2000/svg"
  xmlns:xlink="http://www.w3.org/1999/xlink"
  viewBox="0 0 115 65">
  <defs>
    <g id="rings" stroke-width="3" fill="none">
       <circle cx="25" stroke="rgb(11,112,191)" cy="25" r="15"/>
       <circle cx="40" stroke="rgb(240,183,0)" cy="40" r="15"/>
       <circle cx="60" stroke="rgb(0,0,0)" cy="25" r="15"/>
       <circle cx="75" stroke="rgb(13,146,38)" cy="40" r="15"/>
       <circle cx="95" stroke="rgb(214,0,23)" cy="25" r="15"/>
    </g>
  </defs>
  <use x="0" y="65" xlink:href="#rings" />
</svg>

In this example, we’ve wrapped our group within a defs element, which stops it from being rendered. It’s defined but not displayed. This alters the way the x and y coordinates on the use element behave. With group elements, since they are displayed, the x and y coordinates on the use element are relative to the original group. Now that the defs element has stopped the group from being displayed, however, there is no original element to position relative to. Instead, we’re back to the familiar process of positioning our use element relative to the user coordinate system.

The symbol element

The final element related to grouping and reuse is the symbol element. The symbol element has some similarities both to the <g> element and the defs element. Like the <g> element, it is intended as a way to logically group related elements together. Like the defs element, however, any symbols that are defined are not rendered until they’re referenced later by a use element.

The symbol element also differs from the group element in that it can have its own viewBox and preserveAspectRatio attributes, letting you alter the way it fits within the viewport.

Other than that, it’s used in the same way as the <g> element, so the markup is nearly identical:

<svg xmlns="http://www.w3.org/2000/svg"
  xmlns:xlink="http://www.w3.org/1999/xlink"
  viewBox="0 0 115 65">
  <symbol id="rings">
     <circle stroke-width="3" fill="none" cx="25"
      stroke="rgb(11,112,191)" cy="25" r="15"/>
     <circle stroke-width="3" fill="none" cx="40"
      stroke="rgb(240,183,0)" cy="40" r="15"/>
     <circle stroke-width="3" fill="none" cx="60"
      stroke="rgb(0,0,0)" cy="25" r="15"/>
     <circle stroke-width="3" fill="none" cx="75"
      stroke="rgb(13,146,38)" cy="40" r="15"/>
     <circle stroke-width="3" fill="none" cx="95"
      stroke="rgb(214,0,23)" cy="25" r="15"/>
  </symbol>
  <use x="0" y="0" xlink:href="#rings" />
</svg>

We’ll go into more detail about some of the other differences and gotchas around each of these grouping elements in Chapter 10.

Filters

If you’ve ever opened Photoshop (or something similar), you’re probably familiar with the idea of filters—graphic operations such as drop shadows and blurs that you can apply to your image. The advantage of a vector format in this regard is that the filtering can be done through markup or applied with CSS.

Filters are essentially post-processing steps for your images. For an HTML page to be loaded within a browser, the browser needs to go through a series of steps to construct the page: grab all the elements, apply the styles, lay out the elements on the screen, and finally render the page. Filtering introduces one more step in the process. Just before the page is displayed on the screen, the filter is overlaid on any images it is being applied to.

Applying filters

You can apply filters to your SVG images in one of two ways. The first is to use the filter element to define a filter that can then be referred to later in the SVG file.

Here’s an example of what a drop shadow might look like when applied to our Olympic rings:

<svg xmlns="http://www.w3.org/2000/svg"
  viewBox="0 0 115 65">
  <defs>
    <filter id="dropShadow">
      <feGaussianBlur in="SourceAlpha" stdDeviation="1"></feGaussianBlur>
      <feOffset dx="1" dy="1" result="offsetblur"></feOffset>
      <feFlood flood-color="#000000"></feFlood>
      <feComposite in2="offsetblur" operator="in"></feComposite>
      <feMerge>
        <feMergeNode></feMergeNode>
        <feMergeNode in="SourceGraphic"></feMergeNode>
      </feMerge>
    </filter>
  </defs>
  <g id="rings" stroke-width="3" fill="none" filter="url(#dropShadow)">
     <circle cx="25" stroke="rgb(11,112,191)" cy="25" r="15"/>
     <circle cx="40" stroke="rgb(240,183,0)" cy="40" r="15"/>
     <circle cx="60" stroke="rgb(0,0,0)" cy="25" r="15"/>
     <circle cx="75" stroke="rgb(13,146,38)" cy="40" r="15"/>
     <circle cx="95" stroke="rgb(214,0,23)" cy="25" r="15"/>
  </g>
</svg>

The only thing we changed regarding our original group of rings is that we’ve added the filter attribute, referring to the id of the new filter we just defined. The rest of the magic is happening inside that filter element. Let’s walk through it.

The fourth line defines the filter and gives it an id of "dropShadow" so that we can refer to it later.

Lines 5 through 10 are filter primitive elements. The filter primitives are what actually describe the work to be done. The browser will walk through each primitive, applying it step by step, to create the overall filter. Digging deep into all of the native filters is a little beyond the scope of this chapter: we’re introducing this concept only to highlight a few performance gotchas. The result of each of those primitives in this case is a small drop shadow. When we apply it to our rings group, each individual circle will have a drop shadow applied.

You can achieve a similar effect by taking advantage of CSS filter effects. In fact, the whole thing becomes much simpler. Here’s a drop shadow applied to our rings using CSS (see Figure 6-4):

<svg xmlns="http://www.w3.org/2000/svg"
  viewBox="0 0 115 65" style="-webkit-filter: drop-shadow(2px 5px 7px black);">
    <g id="rings" stroke-width="3" fill="none">
       <circle cx="25" stroke="rgb(11,112,191)" cy="25" r="15"></circle>
       <circle cx="40" stroke="rgb(240,183,0)" cy="40" r="15"></circle>
       <circle cx="60" stroke="rgb(0,0,0)" cy="25" r="15"></circle>
       <circle cx="75" stroke="rgb(13,146,38)" cy="40" r="15"></circle>
       <circle cx="95" stroke="rgb(214,0,23)" cy="25" r="15"></circle>
    </g>
</svg>

Unfortunately, support for CSS filters is still limited at the time of writing, with most browsers that do support them having hidden them behind a prefix or configuration flag.

hpim 0604
Figure 6-4. We can apply filters with CSS or the SVG filter element to add effects such as blurring and drop shadows

Performance concerns

Because this does introduce a new step in the process of displaying a page, you want to be careful with how many filters you apply: they’re not free. Usually, if you’re careful about how many filters you apply, it’s not too much of an issue. Where it becomes a bit more serious is when you try applying any filter that involves some sort of blurring—effects such as applying a drop shadow or, well, a blur. This is because the process of creating the blur is rather involved.

Let’s say you apply a 10-pixel blur to your SVG image. In order for the browser to display it properly, it needs to walk through the original image, pixel by pixel. For each pixel of the original image, it must also look at the surrounding 10 pixels in every direction in order to properly calculate the colors to be displayed. This process becomes more unwieldy the larger the radius. The browser will try to optimize this process as much as possible, but you can still run into some rather serious performance issues—particularly on an underpowered device.

The wrong way to do filters

Filters are incredibly powerful and useful. Mostly, if they’re kept in check, the performance isn’t too bad, other than those filters involving some level of blurring. If you’re using a graphics editing program to apply these filters, you’ll want to pay very close attention to the outputted code: it may not be doing exactly what you think.

hpim 0605
Figure 6-5. Adobe Illustrator has many image effects you can choose from, but you’ll want to be very careful to double-check the resulting code

For example, Adobe Illustrator has a number of filters and image effects listed in the Effect menu option (see Figure 6-5). If you apply a drop shadow from here and then view the resulting code, you’ll see something like this:

<svg xmlns="http://www.w3.org/2000/svg"
  viewBox="0 0 115 65" style="-webkit-filter: drop-shadow(2px 5px 7px black);">
    <g id="rings" stroke-width="3" fill="none">
      <image overflow="visible" width="451" height="212"
       xlink:href="78ABE.png"  transform="matrix(1 0 0 1 37 146)">
      </image>
      <circle cx="25" stroke="rgb(11,112,191)" cy="25" r="15"></circle>
      <circle cx="40" stroke="rgb(240,183,0)" cy="40" r="15"></circle>
      <circle cx="60" stroke="rgb(0,0,0)" cy="25" r="15"></circle>
      <circle cx="75" stroke="rgb(13,146,38)" cy="40" r="15"></circle>
      <circle cx="95" stroke="rgb(214,0,23)" cy="25" r="15"></circle>
    </g>
</svg>

Instead of using the native SVG filters, the effect creates a rasterized image of the drop shadow (78ABE.png) that is then linked to and layered into the final image. In this example, that external image was 22 KB. For reference, the original example where we used the filter element weighs in at a mere 853 bytes uncompressed. On top of the file size differences, we’ve seen that rasterized images don’t scale as efficiently. Needless to say, this output is less than ideal.

If you’re applying filters inside of Illustrator, make sure you explicity instruct Illustrator to use an SVG filter by using the Effects → SVG Filters option. From there you can handcode your filter and have that applied, avoiding the inefficiency of Illustrator’s default process (see Figure 6-6).

hpim 0606
Figure 6-6. Using Illustrator’s SVG Filters option will allow you to handcode the filter to be applied to the image

SVG Optimizations

We’ve covered most of the basics of the SVG format. Along the way we touched on a few things for you to keep in mind in terms of performance, but it’s time now to take a deeper look at specific optimizations that you can apply to reduce the footprint and performance impact of your SVG images.

Enabling GZip or Brotli

One of the advantages of the SVG format being text-based is that it compresses very well. As we’ve seen, the SVG format is an XML-based format and XML formats tend to be fairly repetitive. This plays right into the hands of compression algorithms such as GZip and the up-and-coming Brotli (see “What About Brotli?”). As a result, applying one of these compression methods to your SVG files should be the very first optimization that you consider by default. Even if you were to do nothing else, you would still see a significant savings in file size.

You can apply GZip to your SVG files ahead of time, or have your server compress the files when served. If you compress them ahead of time (which is ideal, because it means you can use the high-efficiency Zopfli compressor), it’s recommended that you use the .svgz extension to helpfully distinguish between the compressed and uncompressed SVG files you’re providing. You’ll also need to make sure that the server communicates to the browser that the image has GZip applied to it. Otherwise, the browser will not be able to display it.

If you’re using Apache, you’ll want to add the following lines to your .htaccess file to ensure the browser knows that any SVGZ images have GZip applied to them:

<IfModule mod_mime.c>
  AddEncoding gzip svgz
</IfModule>

If you want the server to apply compression on the fly, you’ll need to configure it to do so. In Apache, that means making sure the following lines are in your .htaccess file:

<IfModule mod_filter.c>
  AddOutputFilterByType DEFLATE "image/svg+xml"
</IfModule>

Regardless of whether you’re compressing ahead of time or on the fly, you’ll also want to ensure that any SVG and SVGZ images are served with the appropriate MIME type:

AddType image/svg+xml svg svgz

You can verify that SVG files are being served appropriately, with GZip correctly applied, by checking to make sure that any SVG images are sent with the following headers:

Content-Type: image/svg+xml
Vary: Accept-Encoding

Any SVGZ files passed from the server should also carry the Content-Encoding header:

Content-Type: image/svg+xml
Content-Encoding: gzip
Vary: Accept-Encoding

If you’re seeing these headers correctly returned and your images are displaying, you’re successfully reaping the rewards of GZip compression on your SVG images.

While enabling compression and minifying your SVG files will greatly reduce the file size, they don’t help in terms of reducing the complexity of the image to begin with. There are, however, a few simple optimizations you can apply that will help on that front.

Reducing Complexity

Many of the optimizations you can apply to the SVG format all revolve around the same basic principle: reduce complexity. We touched on this briefly when we discussed the basic shapes defined in SVG and compared them to the path element. Using the basic shapes wherever possible not only makes it easier for browsers during the rasterization process, but it also reduces the complexity and amount of markup necessary to produce the image in the first place.

There are several other optimizations that accomplish the same basic goal:

Simplify paths

If you use paths within your image (and you’ll need to in order to draw anything a bit more complex), you’ll want to try to keep those paths as simple as possible. The fewer points and commands you include in a path, the less markup you’ll use and the fewer instructions the client will have to parse through and decipher. The end result will be a lighter image that displays more quickly.

Reduce the number of decimal points

If you’re using a graphics editing program (like the popular Adobe Illustrator) to produce your SVG images, you’ll likely see that many of the points defined within the various shapes and paths are incredibly precise. Rarely is that level of precision needed. In most cases, you can safely round off to one or two decimal points without any noticeable visual degradation in the image. It sounds fairly trivial, but removing those unnecessary decimal points can really add up in terms of file size.

Combining similar paths

If an SVG image has several paths of a similar shape and style, it may make sense to merge them together—reducing the number of paths necessary for the SVG image to be drawn. You’ll want to be careful with this one, though: if paths overlap, then trying to merge them can result in some serious visual degredation. This is one optimization you’ll want to do by hand.

Converting Text to Outlines

SVG makes including actual text pretty trivial. In fact, it can be as simple as using the <text> element, like so:

<svg xmlns="http://www.w3.org/2000/svg"
  viewBox="0 0 100 100">
  <text font-family="Times New Roman" font-size="10" x="0" y="20">
    Hi. I'm text.
  </text>
</svg>

That’s it. That’s all there is to it. We set our font styling, and type out our text, and it’s now included within our SVG image. Beyond making it easy to add text to an SVG image, the <text> element adds a few other benefits as well:

  • Because the <text> element allows you to embed your text plainly, it’s searchable and accessible.

  • The <text> element relies on the font itself. Many well-designed fonts have features, like hinting, that enable them to adjust the display for different sizes, maximizing visual appeal and readability.

Unfortunately, this second feature also causes some problems. If the font is not one that is available on the system, the text will revert to the system default. Considering that this text lives in an image, and is therefore most likely specifically chosen to appear in a certain way, this is not exactly ideal.

Now it is entirely possible to use a web font to render that text, but that requires the additional request and weight associated with making that request—and when it comes to web fonts, that weight is usually not trivial.

In order to ensure that your text appears as styled, no matter the font available on the viewer’s operating system, you can elect to convert that text into outlines. This process involves the graphic editor creating an outline of the text, then creating a path that matches the shape of that outline.

This eliminates the need for the additional request and weight needed to include the web font, but it does mean that the SVG file itself becomes substantially more complex. This tradeoff is quite frequently acceptable when viewed purely from a performance standpoint: as we’ve discussed, SVG compresses incredibly well and the extra bloat within the SVG file itself is not likely to come anywhere near the size of the actual font.

You also lose a bit of the accessibility that the <text> element provides. Because the text is no longer text, but instead a path comprising a series of points, the text becomes inaccessible and unsearchable. You can bring that back to an extent by using the <title> element and including an appropriate textual representation of the image there.

Automating Optimization Through Tooling

There are a few options for SVG optimization tools, but SVG Optimizer (SVGO) stands above the rest for its large number of optimizations and the wide range of tools that take advantage of it. At its core, SVGO is a Node.js-based tool, but there is also a web app, a Grunt task, a Gulp task, and plug-ins for Sketch, Illustrator, and Inkscape. For the purpose of this section, we’ll zero in on the command-line interface provided by the core tool and the web app: SVGOMG.

Installing the SVGO Node Tool

If you have Node.js already installed on your machine, then you can install SVGO using the npm install command (optionally using sudo):

[sudo] npm install -g svgo

You can verify that it’s properly installed by entering svgo into your command line and pressing Enter. If SVGO is ready to go, you’ll see a list of options that can be used with SVGO.

To run SVGO on an SVG image, you type the svgo command followed by the file name:

svgo myimage.svg

Even if you run SVGO using the default configuation, you’re almost certainly going to see some performance gains in terms of file size. Just about every optimization we’ve discussed—reducing decimal points, merging and simplifying paths—can be done automatically by SVGO.

You will want to pay close attention to the defaults, however, because it’s unlikely that everything is going to be set up the way you want. While many of the default optimizations are harmless, a few are riskier to automate. I’ve seen many cases where combining shapes ended up distorting the image in some way. Other “optimizations,” such as removing the viewBox, can actually be detrimental for reasons we’ve already discussed.

You can override the default configuration by creating your own configuration and passing that file to the SVGO command. The configuration file can be as simple as a list of the SVGO plug-ins you’d like to execute, stored in a Yet Another Markup Language (YAML) file. For example:

plugins:
 - removeDoctype
 - removeComments
 - cleanupAttrs
 - minifyStyles
 ....

If we stored this in a file called myconfig.yml, we could then pass that configuration to SVGO using the --config flag as follows:

svgo --config myconfig.yml myimage.svg

SVGOMG: The Better to See You With, My Dear

The command-line interface, as well as the Grunt and Gulp tasks built on top of it, is an excellent option for automation. If you need to dig deeper into a specific file, though, then the web app SVGOMG, built by Jake Archibald and shown in Figure 6-7, may be a better option.

hpim 0607
Figure 6-7. SVGOMG provides a nice GUI on top of the powerful SVGO Node.js tool

SVGOMG provides all the functionality of the underlying SVGO Node.js tool, but with one notable improvement: it lets you visualize the difference both in the code and the image itself.

Using SVGOMG, you upload your SVG file and are immediately presented with it in all its visual glory. You can then select and deselect SVGO options to see what, if any, difference they make visually as well as in terms of file size.

SVGOMG also allows you to view the code of the optimized SVG image. The code is updated as each SVGO optimization is toggled on or off, allowing you to see exactly what is being changed.

When you’re done tweaking the image, you can download it back to your machine.

Pick Your Flavor

Whichever of the many SVGO-based tools you decide to use, including SVGO in your workflow will greatly simplify the process of optimizing every last little byte of the images you produce. It’s an essential tool for anyone producing SVG images.

Summary

In this chapter we dug deeper into the differences between raster formats and vector formats. We walked through the basics of the SVG format, and talked about how you can minimize the performance impact through a series of optimizations.

You now have a really solid understanding of the various image formats and how to effectively compress them. In Part II of this book, we’ll shift our focus to how those images are actually loaded by the browser.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset