Using SVG with CSS3 and HTML5 — Supplementary Material

Example code and online extras for the O'Reilly Media book by Amelia Bellamy-Royds, Kurt Cagle, and Dudley Storey.

Picking the Perfect View

In a complex web design, you may wish to change the view of your SVG to fit the screen. For example, your full SVG may look best in landscape orientation. On a mobile device in portrait mode, you might want to switch to a cropped version of the graphic that zooms in on the important parts.

We can’t currently adjust the crop with CSS media queries inside the SVG file, since CSS can’t control viewBox. But an SVG view can.

Both pre-defined <view> elements inside your SVG file and custom #svgView() fragments can change the root viewBox of the SVG file. But in order to activate a view, you must include the correct target fragment in the URL that embeds the SVG file.

So, what we need is a way to change the URL of an embedded SVG when the screen is in portrait mode instead of landscape.

The HTML <picture> element can do that.

The media attribute for <source> elements within a <picture> was originally intended to allow you to switch to a completely different (raster) image file for different media queries. But it also works with SVG views.

Example 9-X1 uses <picture> and SVG views to crop a large header image. The graphic (a diagram of a peach by contributor LadyofHats) is taken directly from Wikimedia Commons. Instead of editing the file to add a <view>, the crop is defined from the HTML file, with the SVG view fragment syntax.

Figure 9-X1 shows the resulting page, in browser windows of different dimensions.

Two web browser windows, overlapping on a computer desktop.  One is very tall and narrow, the other short and wide.  Both have a botanical diagram of a peach at the top, then the heading 'A Rosaceae by any other name…'.  In the narrow window, the image is a square tightly cropped around the peach.  In the wide window, the image is wider, showinglabels describing the different parts of the fruit's anatomy.
Figure 9-X1. The same web page, in browsers of different dimensions
Example 9-X1. Using views and nested SVGs to arrange multiple icons in a single file
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <title>Using SVG Views and &lt;picture&gt;
        to crop for different layouts</title>
    <style>
        header    { color: cadetBlue; }
        header h1 { margin-top: 0.2em; }
        header img {
            border: solid thin;
            display: block;
            margin: auto;
            width: 86vw;
            height: 64vw;
            max-height: 80vh;               1
        }
        @media (orientation: portrait) {
            header img {
              width: 90vw;
              height: 90vw;                 2
            }
        }
    </style>
</head>
<body>
    <header>
        <picture>
            <source type="image/svg+xml"
srcset="Drupe_fruit_diagram-en.svg#svgView(viewBox(20,10,300,300))"
                    media="(orientation: portrait)" />     3
            <img src="Drupe_fruit_diagram-en.svg"
                 type="image/svg+xml"
                 alt="Botanical diagram of a peach." />    4
        </picture>
        <h1>A <i lang="la">Rosaceae</i> by any other name…</h1>
    </header>
    <p>The rose family is arguably one of the six most economically
    important crop plant families, and includes apples, pears,
    quinces, medlars, loquats, almonds, peaches, apricots, plums,
    cherries, strawberries, raspberries, sloes, and roses among the
    crop plants belonging to the family.</p>
    <small>Text adapted from
<a href="https://en.wikipedia.org/wiki/Rosaceae">Wikipedia</a>
    </small>
</body>
</html>
1

The basic dimensions of the header image are defined with CSS vw viewport units, to fill up most of the screen width, while still preserving the 4:3 aspect ratio of the original SVG. A max-height setting limits the image size on very wide and short screens, to leave some room for the header text.

2

On portrait screens, the media query kicks in, making the image square. The orientation media query only has two options (portrait and landscape), but you could use the aspect-ratio media query if you needed more control.

3

Within the markup, the same media query is used in the media attribute of a <source> element. The srcset attribute for that source includes the SVG view URL for the square-cropped version of the graphic. As we mentioned in Chapter 2 when we introduced <picture> for SVG fallback, you have to use srcset with a picture <source>, not src, even when you only have one source value.

4

The <img> element within the <picture> has the basic URL for the SVG, which will be used if the media query doesn’t apply. Alternatively, you could use two different SVG <source> elements, and have a raster fallback (e.g., PNG) in the <img>.

View the live example in your browser.

If views aren’t supported—or if <picture> isn’t supported—you would get the regular view of the SVG, scaled according to the default viewBox to fit the height and width of the <img>. The width and height, however, would still be adjusted by the media query in the main page’s CSS. The result won’t be perfect, but it will still be functional.

Warning

As we warn a few times in the book, Safari/WebKit only applies SVG views on HTTPS pages, for same-origin SVG embeds.

The view only crops the image, it doesn’t edit it. Which means that even though we’ve cropped out the labels in the portrait version of Figure 9-X1, the lines connecting those labels to the graphic are still visible.

If you did want to edit the SVG file to add a <view>, you could also add :target rules to hide extraneous content. Because the actual target is the <view> element, you’d need to place the <view> near the top of the file, and then use CSS sibling selectors to select the graphics.

For example, we could edit the SVG to add a <view> with an id of cropped, like this:

<view id="cropped" viewBox="20,10,300,300" />

We would put that <view> near the top of the file (e.g., right after the <title>), and then group all the drawing code together with a <g> element that is a sibling to the <view>.

Then we would give the elements that we want to remove a shared class (e.g., hide-when-cropped), and add a <style> block to our SVG file, with a CSS rule like this:

#cropped:target ~ g .hide-when-cropped {
    display: none;
}

Finally, the <picture> element in our main web page would be updated to use the id of the <view> instead of the #svgView() fragment:

<picture>
    <source type="image/svg+xml"
            srcset="Drupe_fruit_diagram-en-2.svg#cropped"
            media="(orientation: portrait)" />
    <img src="Drupe_fruit_diagram-en-2.svg"
         type="image/svg+xml"
         alt="Botanical diagram of a peach." />
</picture>

Now, when the web browser switches to portrait mode, not only will the SVG be cropped to only show the diagram, but the labels and their lines will disappear, too.

View the modified example in your browser.

Warning

Older versions of Firefox (prior to 39) did not apply the :target pseudoclass for <view> element targets. So you’d get the same result as Figure 9-X1.

However, whether the labels are visible or not, remember that text inside an <img> is not accessible. And, unfortunately, there is no equivalent to <picture> for embedded objects; you’d need to use JavaScript to swap the view of an SVG embedded with <object> or <iframe>. JavaScript is also currently needed to update views of inline SVG (by directly changing the viewBox attribute).

Note

If you look at the source code for the SVG file we’re using, you’ll discover that the inaccessible text doesn’t actually change anything—the text in the labels has already been converted to inaccessible paths!

Remember to check for text-that-isn’t-text when using diagrams from Wikipedia or other clip-art sources.

You should also be able to use views for SVG used as CSS background images. A media query in your CSS can change the background-image property to use the URL with the SVG view fragment. However, as we’ve warned a few times in the book, WebKit and older Blink browsers do not support URL target fragments for images in CSS. Those browsers would show the regular view of the SVG, scaled to fit the image dimensions.