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.

Dynamically Changing preserveAspectRatio

In Example 8-2 and the following sections in the book, we built a resizable inline SVG, for testing the effect of viewBox and preserveAspectRatio values in viewports of different sizes and aspect ratios.

Example 8-X1, goes one step further, creating a dynamic web page in which you can switch between the options using form inputs.

Example 8-X1. Dynamic demonstration of preserveAspectRatio options

HTML markup:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <title>preserveAspectRatio options</title>
    <link rel="stylesheet" href="flex-scale-viewBox.css" />  1
    <style>
        /* additional styles for form */
        form {
            max-width: 25em;
            text-align: right;
        }
        label {
            display: block;
            margin: 0.2em 0;
        }
    </style>
    <script src="flex-scale-preserve-options.js"></script>   2
</head>
<body>
    <form id="preserve-options">                             3
        <label><code>preserveAspectRatio</code>
                mode
            <select id="preserve">
                <option value="none" >none</option>
                <option value="meet" selected >meet</option> 4
                <option value="slice" >slice</option>
            </select>
        </label>
        <label><i>x</i>-alignment
            <select id="x-align">                            5
                <option value="xMin" >min</option>
                <option value="xMid" selected >mid</option>
                <option value="xMax" >max</option>
            </select>
        </label>
        <label><i>y</i>-alignment
            <select id="y-align">
                <option value="YMin" >min</option>
                <option value="YMid" selected >mid</option>
                <option value="YMax" >max</option>
            </select>
        </label>
    </form>
    <div class="wrapper">
        <svg id="preserve-svg" viewBox="0,0 20,20">
            <title>Spade</title>
            <path d="M9,15C9,20 0,21 0,16S6,9 10,0C14,9 20,11 20,16
                     S11,20 11,15Q11,20 13,20H7Q9,20 9,15Z" />
        </svg>
    </div>
</body>
</html>
1

The stylesheet from Example 8-2 is re-used, to create the resizable SVG. That could include any modifications you added to make the SVG resizable without the CSS resize property.

2

A linked script will add the behavior to listen for user input and update the SVG accordingly.

3

The webpage includes a <form> with the three drop-down lists for each of the three independent parts of the preserveAspectRatio attribute. The lists are created using the HTML <select> and <option> elements. A <label> wrapped around each selection set associates the description with the form field.

4

The meet option is selected by default, to match the default preserveAspectRatio value on the SVG.

5

For the x- and y-alignment options, the value attribute for each <option> is the exact keyword that will be used in the final preserveAspectRatio value (including capitalization), but the visible text is more readable. Again, the default xMidYMid options are checked.

JavaScript: flex-scale-preserve-options.js

window.addEventListener("load", function(){                  1
    var doc = document;
    var svg = doc.getElementById("preserve-svg");            2

    var preserve = doc.getElementById("preserve");
    var xAlign = doc.getElementById("x-align");
    var yAlign = doc.getElementById("y-align");

    doc.getElementById("preserve-options")
       .addEventListener("change", update);                  3

    function update(event) {                                 4
        var p, string;
        p = preserve.value;
        if (p == "none") {
            xAlign.disabled = yAlign.disabled = true;        5
            string = p;
        }
        else {
            xAlign.disabled = yAlign.disabled = false;
            string = xAlign.value + yAlign.value + " " + p;  6
        }
        svg.setAttribute("preserveAspectRatio", string);
    }

    update();                                                7
});
1

As usual, the script is contained inside an anonymous function. However, instead of running the function automatically, it is set to run when the document finishes loading (and the load event is triggered on the window). The script can therefore be included in the HTML <head>, instead of at the end, and it will still not run until all the elements had been initialized.

2

All the key elements in the markup have id attributes, so they are easy to access from the script. They won’t change, so they are selected once and stored in variables. The doc variable is just a convenient alias for document, to reduce typing.

3

A single listener function will react to all changes within the form, regardless of which input element triggered the change. We don’t need to access the form again, so it isn’t saved to a variable; instead, the addEventListener method is called directly on the object returned by getElementById.

4

The update(event) function determines the currently selected options in the form, and sets the SVG’s preserveAspectRatio accordingly. The value property of an HTMLSelectElement is equal to the value attribute of the currently selected <option>.

5

If the selected mode is none, the alignment options are disabled. These only have an effect for meet and slice.

6

For options other than none, the current alignment selections are accessed, and all three keywords are composed into a single preserveAspectRatio string (paying attention to the spacing between values).

7

The update function is also called once when the script is run after loading. That way, the SVG matches the form selections if the browser remembers choices from a previous visit to the web page.

The web page, with the SVG set to xMinYMax slice is displayed in Figure 8-X1. To get the complete effect, view the live example in your browser. Try out all the options, and resize the SVG for each.

A web page, consisting of the three labelled drop-down inputs, followed by a frame with the spade shape in it.  The selected options are slice, x-min, and y-max. The frame is wider than it is tall, and only the bottom half of the spade shape is visible, scaled to fit the width of the frame.
Figure 8-X1. Setting the graphic to slice using a dynamic web form

As you experiment, it should become clear that only one of the alignment options has an effect at a time. The graphic always exactly fits one dimension of the available space. Whether its the x or y dimension is determined both by the shape of the space, and by meet versus slice.

In Chapter 10, we look at preserveAspectRatio again, for images. We can, of course, adapt the form and script from Example 8-X1 to instead set the attribute on the <image> element. Example 8-X2 does that, using the multi-image layout from Example 10-4.

Example 8-X2. Demonstrating preserveAspectRatio options on embedded images, dynamically
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <title>preserveAspectRatio for Images</title>
    <style>
        body { margin: 0; }
        form {
            max-width: 400px;
            text-align: right;
        }
        label {
            display: block;
            margin: 0.2em 0;
        }
        svg {
            display: block;
            max-height: calc(100vh - 5em);
            max-width: 100%;
        }
    </style>
</head>
<body>
    <form id="preserve-options">                             1
        <label><code>preserveAspectRatio</code>
                mode
            <select id="preserve">
                <option value="none" >none</option>
                <option value="meet" selected >meet</option>
                <option value="slice" >slice</option>
            </select>
        </label>
        <label><i>x</i>-alignment
            <select id="x-align">
                <option value="xMin" >min</option>
                <option value="xMid" selected >mid</option>
                <option value="xMax" >max</option>
            </select>
        </label>
        <label><i>y</i>-alignment
            <select id="y-align">
                <option value="YMin" >min</option>
                <option value="YMid" selected >mid</option>
                <option value="YMax" >max</option>
            </select>
        </label>
    </form><svg xmlns="http://www.w3.org/2000/svg" xml:lang="en"
     xmlns:xlink="http://www.w3.org/1999/xlink"
     width="400" height="600" viewBox="0 0 400 600">       2
    <style>
        text {
            font: 20px Tahoma, sans-serif;
            text-anchor: middle;
            fill: darkRed;
        }
        .bird-colors rect { fill: #5f6cb9; }
        .bird-colors text { fill: #bbde60; }
        .theme-colors rect { fill: #00aaa9; }
        .theme-colors text { fill: white; }
    </style>
    <svg viewBox="50 5 500 750" width="50%" height="50%">
        <image height="900" width="600"
               class="preserve-image"
               aria-label="Using SVG cover image"
               xlink:href="using_svg_cover.png"/>          3
    </svg>
    <g class="bird-colors">
        <rect x="50%" height="50%" width="50%" />
        <text x="75%" y="45" dy="-0.5em">Amelia
            <tspan x="75%" dy="1em">Bellamy-Royds</tspan></text>
        <image x="50%" y="80" height="220" width="200"
               class="preserve-image"
               aria-label="Amelia in the sunshine"
               xlink:href="Amelia.jpg"/>                   4
    </g>
    <g class="bird-colors">
        <rect y="50%" height="50%" width="50%" />
        <text x="25%" y="345">Kurt Cagle</text>
        <image y="380" height="220" width="200"
               class="preserve-image"
               aria-label="Kurt in a top hat"
               xlink:href="Kurt.jpg"/>
    </g>
    <g class="theme-colors">
        <rect x="50%" y="50%" height="50%" width="50%" />
        <text x="75%" y="345">Dudley Storey</text>
        <image x="50%" y="380" height="220" width="200"
               class="preserve-image"
               aria-label="Dudley, re-imagined as a tiny warrior"
               xlink:href="Dudley.jpg"/>
    </g>
    </svg>
    <script>
    window.addEventListener("load", function(){
        var doc = document;
        var images = doc.getElementsByClassName("preserve-image");

        var preserve = doc.getElementById("preserve");
        var xAlign = doc.getElementById("x-align");
        var yAlign = doc.getElementById("y-align");

        doc.getElementById("preserve-options")
           .addEventListener("change", update);

        function update(event) {
            var p, string;
            p = preserve.value;
            if (p == "none") {
                xAlign.disabled = yAlign.disabled = true;
                string = p;
            }
            else {
                xAlign.disabled = yAlign.disabled = false;
                string = xAlign.value + yAlign.value + " " + p;
            }
            for (var i=0, n=images.length; i<n; i++) {
                images[i].setAttribute(
                    "preserveAspectRatio", string);          5
            }
        }

        update();
    });
    </script>
</body>
</html>
1

The form hasn’t changed from Example 8-X1; since the embedded images are not SVG, the defer option is not relevant for preserveAspectRatio.

2

The main SVG has a fixed height and width in the markup, but CSS max-height and max-width will scale it down to fit the screen.

3

Each image is identified by the class preserve-image to allow it to be selected in the script. (However, the book-cover image exactly matches its aspect ratio, and won’t ever change.)

4

All three photographs are given the same dimensions, in the SVG’s coordinate system: 200px wide and 220px tall.

5

Because we are modifying multiple elements, the script has been changed to cycle through them in a loop; otherwise, it is the same as Example 8-X1.

View the live example.