Develop a Windows 8 app in 30 days
The first article in this series covered the basics of SVG, an overlooked, underappreciated aspect of HTML5. While different browsers implement SVG in different ways, the previous walkthrough described how to create text, incorporate SVG images and build basic shapes such as circles, polygons and paths in a way that all browsers recognize.
The result was the start of quite possibly the world’s first SVG-driven Zombie Apocalypse Survival Predictor. Thus far, the page is pretty (ish) but completely nonfunctional. To take the page to the next level, this walkthrough delves deeper into paths and their myriad uses, demonstrates how to build the prediction meter and touches on some basic animation to add that little extra to the page (for the browsers that can understand it).
The Text Element (Continued)
Take a closer look at SVG text. In Part 1, the <text>
element was used within an <svg>
block for the page title. It shares the same styling attributes as HTML text, but instead of using the "style" attribute to enumerate them, the example used individual elements for maximum cross-browser compatibility.
<svg id="cityBox" width="600" height="400">
<text x="300" y="50" font-family="sans-serif" font-size="36"
text-anchor="middle" stroke="#000000" fill="#000000">
Surviving the Zombie Apocalypse
</text>
</svg>
This example should display correctly in every browser, but it doesn’t even scratch the surface of what sets SVG text apart from regular text.
The Transform Attribute
To start, consider the "transform" attribute. Previously, this attribute was used to shrink SVG images to their proper sizes, thusly:
<image id="human" x="2750" y="1750" width="343" height="483"
transform="scale(.10,.10)" xlink:href="human.svg" />
However, the attribute can also be applied to <text>
elements. For instance, using the "rotate" transform adds a little tilt to the title:
<text x="300" y="50" font-family="sans-serif" font-size="36"
text-anchor="middle" stroke="#000000" fill="#000000" transform="rotate(30 100,100)">
This says, "at the coordinates 100,100, rotate the text 30 degrees (clockwise)." The result should look like Figure 1.
Figure 1. SVG Text Title with transform="rotate" Applied
The transform attribute includes several other possible values:
- translate: Shifts text vertically or horizontally by the specified amount
- scale: Scales text by the specified vertical and horizontal ratios
- skewX: Skews text along the x-axis by the specified angle
- skewY: Skews text along the y-axis by the specified angle
- matrix: Allows for a custom transformation definition
These transformations apply to other SVG objects as well, as demonstrated by the <image> scale transform. Transforms can also be nested by listing them in the order of the transforms you want. For example, note the result of the following code in Figure 2. Two words in the same position, with the same transformations, but applied in reverse:
<text x="100" y="100" text-anchor="middle" transform="rotate(30 100,100)
skewX(50) scale(2)">Zombie</text>
<text x="100" y="100" text-anchor="middle" transform="scale(2)
skewX(50) rotate(30 100,100)">Apocalypse</text>
Figure 2. Result of Nested Transforms on SVG Text
For more information, read the W3 spec on the transform attribute.
The tspan Element
In the previous example, two separate <text>
elements were created so that transformations can be applied to each word. However, within <text>
elements with common styling and transformations, nested <tspan>
elements can be used to define individual attributes, such as position and color.
In addition, position can be defined either absolutely (x,y) or relatively (dx,dy). Combined with <tspan>
, this sets up some interesting text tricks. Note the use of absolute and relative coordinates in the following example, illustrated in Figure 3.
<text x="50" y="50" text-anchor="left" fill="green">
<tspan>Surviving the</tspan>
<tspan x="50" y="50">Zombie</tspan>
<tspan x="50" y="50">Apocalypse</tspan>
</text>
<text x="200" y="50" text-anchor="left" fill="red">
<tspan>Surviving the</tspan>
<tspan dx="50" dy="50">Zombie</tspan>
<tspan dx="50" dy="50">Apocalypse</tspan>
</text>
Figure 3. Applying Absolute and Relative Coordinates to <tspan> Elements
One neat trick that the origin coordinates enable is to specify multiple coordinates, which then get applied to each letter. While this can be done with absolute coordinates, it’s a bit easier with relative coordinates, especially when applied to an individual <tspan>
element, as in Figure 4. (The "letter-spacing" attribute has also been applied in the following example to give the letters a little breathing room.)
<text x="300" y="50" text-anchor="middle">
<tspan>Surviving the Zombie</tspan>
<tspan dy="-2 5 -3 -4 6 -5 3 -5 9 -4" letter-spacing="3" fill="red">Apocalypse</tspan>
</text>
Figure 4. Multiple Relative Y Positions Applied to a <tspan> Element
Going back to the original "cityBox
" element created in Part 1 of this series, apply the "rotate" transform to the text as a whole, and the multiple relative Y-coordinates to the last word. Now the terrified little human has something to run from, even before adding the zombies, as you can see in Figure 5.
<svg id="cityBox" width="600" height="400">
<text x="300" y="50" font-family="sans-serif" font-size="36"
text-anchor="middle" stroke="#000000" fill="#000000" transform="rotate(30 100,100)">
Surviving the Zombie
<tspan dy="-2 5 -3 -4 6 -5 3 -5 9 -4" letter-spacing="3" fill="red">Apocalypse</tspan>
</text>
<image id="human" x="2750" y="1750" width="343" height="483" transform="scale(.10,.10)"
xlink:href="human.svg" />
</svg>
Figure 5. Run Little Human! Run!
The Animate Element, or Introduction to SMIL
Before leaving the title alone in peace, there’s one more interesting trick that can be applied to it for dramatic effect: the <animate>
element. This one, however, is not cross-browser compatible.
Like SVG, Synchronized Multimedia Integration Language (SMIL) has been around for a while, with v1.0 becoming a W3C Recommendation in 1998. It essentially provides animation markup for text and media. Since the same working group that developed SMIL also worked on aspects of SVG, the latter incorporates certain features defined in the SMIL spec.
To date, not all browsers recognize SVG animation via the SMIL spec. However, those that don’t will simply ignore it rather than break. So consider animation a nice treat for a subset of users. (See "Can I use…" for a complete list of browsers and versions that recognize SMIL animation.)
The primary elements making up SVG animation include <animate>
, <set>
, <animateMotion>
and <animateTransform>
. Many browsers still recognize <animateColor>
, though that element has been deprecated in favor of <animate>
.
To use SMIL animation, the element should be nested within the SVG element being animated. In this example, the stroke color of the title text will be animated (attributeName
). It is set to begin 2 seconds after being loaded, last 10 seconds (dur), and go from black to red. The animation is then set to freeze on its end state by setting fill="freeze"
(not to be confused with fill color).
<text x="300" y="50" font-family="sans-serif" font-size="36" text-anchor="middle"
stroke="#000000" fill="#000000" transform="rotate(30 100,100)">
Surviving the Zombie
<tspan dy="-2 5 -3 -4 6 -5 3 -5 9 -4" letter-spacing="3" fill="red">Apocalypse</tspan>
<animate attributeName="stroke" begin="2s" dur="10s" from="black" to="red" fill="freeze" />
</text>
If viewed in a compliant browser, this results in a slow, subtle effect suggestive of bleeding text. (Skip to the sample page listed at the end of this walkthrough for a demonstration.)
The Path Element
Part 1 of building the Zombie Apocalypse Survival Predictor covered the basics of creating a <path>
element. While different elements exist for different types of shapes, such as <polygon>
and <rect>
, the <path>
element can be used to create these same shapes. Not mentioned last time, the Closepath (Z) command facilitates closed shapes by drawing the final leg automatically, as in this snippet, which is illustrated in Figure 6:
<polygon points="50,50 100,25 100,75" stroke="black" stroke-width="1" fill="blue" />
<path d="M 150 50 L 200 25 L 200 75 L 150 50" stroke="black" stroke-width="1" fill="red" />
<path d="M 250 50 L 300 25 L 300 75 Z" stroke="black" stroke-width="1" fill="green" />
Figure 6. <polygon> vs. <path> vs. <path> with Closepath Command
But Paths can also do much more, from creating simple lines to setting the baseline for text to defining a motion path for animation.
Creating Straight Lines
As shown in the preceding snippet, a line consists of nothing more than the Moveto (M) or starting point and the Lineto (L) or connecting point. The following creates three consecutive lines of varying stroke widths:
<path d="M 100 100 L 175 100" style="stroke: black; stroke-width: 1;" />
<path d="M 100 150 L 175 150" style="stroke: black; stroke-width: 2;" />
<path d="M 100 200 L 175 200" style="stroke: black; stroke-width: 3;" />
These use absolute coordinates to define precise placement of the line nodes. However, relative coordinates can also be used where it makes more sense by using lowercase "l" for Lineto. The following creates an identical set of lines as the above example (and, as a bonus, note how the "style" attribute is replaced with individual attributes so that the stroke color can be set for the entire group instead):
<g stroke="black">
<path d="M 100 100 l 75 0" stroke-width="1" />
<path d="M 100 150 l 75 0" stroke-width="2" />
<path d="M 100 200 l 75 0" stroke-width="3" />
</g>
In this case, rather than defining a Lineto coordinate of "175,100", for example, "l 75 0" says "Draw a line to the right for 75 points."
If you’re drawing multiple lines of the same style, the relative Moveto (m) can be used the same way by establishing the first origin, then adding new relative origins in the same <path> element. In this way,
<g stroke="black" stroke-width="2">
<path d="M 100 100 l 75 0" />
<path d="M 100 150 l 75 0" />
<path d="M 100 200 l 75 0" />
</g>
is identical to
<path d="M 100 100 l 75 0 m -75 50 l 75 0 m -75 50 l 75 0" stroke="black" stroke-width="2" />
When you’re drawing vertical or horizontal lines, things get even easier. The Horizontal Lineto (H or h) and Vertical Lineto (V or v) commands reduce the coordinate to a single dimension, either absolute (H or V) or relative (h or v). The following draws the same three lines as the earlier examples (note, too, the elimination of superfluous whitespace between the commands and numbers):
<path d="M100 100 h75 m-75 50 h75 m-75 50 h75" stroke="black" stroke-width="2" />
In addition to all the straight lines, paths can create curvy lines. In fact, the <path> element includes multiple commands for implementing curves:
- Quadratic Bézier curve (Q): Draws a curve by specifying a control point and end point
- Smooth quadratic Bézier curveto (T): Smoothly continues a quadratic curve to a new point
- Curveto (C): Draws a cubic Bézier curve by specifying an end point and two control points
- Smooth curveto (S): Smoothly continues a cubic curve to a new point
- Elliptical arc (A): Draws an arc by describing two ellipses and their intersection
Since curved paths are not strictly necessary to escape from zombies, they won’t be covered here. For more information, see the W3C spec on <path> curve commands.
Creating the Prediction Meter Pane
With <path>
basics covered, what this Survival Predictor needs is an actual meter. So, before adding any new elements, add a third pane to the two panes created in the previous walkthrough. First, change the previous id style from "#controlPanelBox
" to a class style, ".svgBox
". For now, the panes will have borders for visibility.
.svgBox {
border: 1px solid #000000;
float: left;
}
Also be sure to add the class to the controlPanelBox <svg>
element:
<svg id="controlPanelBox" class="svgBox" width="400" height="400">
Create a new element of the same class to serve as the container for the prediction meter:
<svg id="meterBox" class="svgBox" width="200" height="400">
</svg>
This meter will be pretty basic, consisting of some simple shapes, such as thermometer tics and a pointer that will eventually be repositioned based on the prediction algorithm. It will also include a button that will be made interactive in the last article in this series. For now, add a text label and some groups to contain the other elements:
<text id="oddsText" x="100" y="50" font-size="50"
font-family="sans-serif" text-anchor="middle">0%</text>
<g id="oddsMeter">
</g>
<g id="oddsButton" >
</g>
The oddsText
element will be manipulated through JavaScript later to reflect the thoroughly scientific calculation, but in the meantime it will remain static.
With SVG objects, order counts. Since the tics will be on the bottom of the stack, draw those now. Prior to this walkthrough, those lines may have looked something like this:
<path d="M 100 100 L 175 100" stroke="#000000" stroke-width="3" />
<path d="M 100 150 L 150 150" stroke="#000000" stroke-width="2" />
<path d="M 100 200 L 175 200" stroke="#000000" stroke-width="3" />
<path d="M 100 250 L 150 250" stroke="#000000" stroke-width="2" />
<path d="M 100 300 L 175 300" stroke="#000000" stroke-width="3" />
However, by pulling stroke information into a group element, as well as using relative Movetos and Horizontal Linetos, this code can be reduced:
<g id="oddsMeter" stroke="#000000" stroke-width="2">
<path d="M100 100 h75 m-75 100 h75 m-75 100 h75" stroke-width="3" />
<path d="M100 150 h50 m-50 100 h50" />
</g>
Beneath the tics but within the oddsMeter
group, draw a triangle like the one used in the control panel. Only this time, since it’s a single object and won’t need to be duplicated, use the <polygon>
element:
<polygon id="oddsPointer" points="150,300 100,275 100,325" fill="#FF0000" stroke-width="1" />
This element is contained within the oddsMeter
group, so a new stroke width is specified to override the group’s stroke-width attribute.
Last, add a rounded rectangle at the bottom of the same group, which will cause it to appear on the top of the stack. The <rect>
element hasn’t been used yet in the Predictor, but its attributes are self-explanatory. Note the radius-x (rx) and radius-y (ry) instructions for creating the rounded corners:
<rect x="90" y="75" rx="15" ry="10" width="20" height="250" fill="#0000FF" />
Below the meter, add the button for calculating the odds of survival. The button consists of two SVG elements, <text>
and <rect>
, ordered so that the text appears on top of what is essentially a white-filled rectangle. What’s important to consider here is that when interactivity is added later, it needs to be added to the button as a whole, which is why these two elements are grouped and named. Otherwise, adding an event handler either to the text or to the rectangle behind it could produce unsatisfactory user interaction. Grouping them also provides an easy mechanism for adding a cursor:
<g id="oddsButton" style="cursor: pointer;">
<rect x="35" y="340" rx="10" ry="10" width="130" height="40" fill="#FFFFFF" stroke="#000000"
stroke-width="2" />
<text x="100" y="365" font-family="sans-serif" font-size="18"
text-anchor="middle">Calculate Odds</text>
</g>
The finished prediction meter panel should look like Figure 7.
Figure 7. Zombie Apocalypse Survival Prediction Meter
Creating a Text Baseline
As previously mentioned, paths can also provide a baseline for <text> elements. To see this in action, go back to the title in the "cityBox
" SVG element, which currently looks like Figure 5. Rather than apply a "rotate" transform, we’ll fix the baseline of this text to a basic path.
First add a crooked path at the top of the "cityBox
" SVG pane. Go ahead and include a black stroke for now (stroke weight will default to 1). Since this is not a straight line and can be seen as a potential polygon, set fill to "none". Give it the name "titlePath
", which will be referenced in the next step. By default, if the path is not long enough to serve as a baseline for the entire text, any remaining text will be truncated, so this path is given an extra-long leg at the end: "l250 100".
<path id="titlePath" d="M0 100 l100 -50 l60 25 l150 -40 l250 100" stroke="#000000" fill="none" />
The path, without the additional elements, should look like Figure 8.
Figure 8. Path for Title Text
To anchor the text to the path, nest the text content and any <tspan>
elements within a <textPath>
element, which itself is nested within the <text>
element. The <textPath>
element then specifies an xlink referencing the path. (While you’re at it, remove the transform and switch the path’s stroke color to "none" to render it invisible.)
<svg id="cityBox" width="600" height="400">
<path id="titlePath" d="M0 100 l100 -50 l60 25 l150 -40 l250 100" stroke="none" fill="none" />
<text x="300" y="50" font-family="sans-serif" font-size="36" text-anchor="middle"
stroke="#000000" fill="#000000">
<textPath xlink:href="#titlePath">
Surviving the Zombie
<tspan dy="-2 5 -3 -4 6 -5 3 -5 9 -4" letter-spacing="3" fill="red">Apocalypse</tspan>
</textPath>
<animate attributeName="stroke" begin="2s" dur="10s" from="black" to="red" fill="freeze" />
</text>
<image id="human" x="2750" y="1750" width="343" height="483" transform="scale(.10,.10)"
xlink:href="human.svg" />
</svg>
Unfortunately, here’s where browser differences poke their ugly, rotting head. Some browsers ignore the "x" attribute and position the text to the baseline according to the "text-anchor
" attribute. Other browsers ignore the "text-anchor
" attribute and position the text to the baseline according to the "x
" attribute. So, to find a fix that works on both, change text-anchor to "left" and x to "15":
<text x="15" y="50" font-family="sans-serif" font-size="36"
text-anchor="left" stroke="#000000" fill="#000000">
The universal result should look something like Figure 9.
Figure 9. Title Set to Baseline Path
Creating an Animation Path
Though the full potential of the <path>
element has not yet been plumbed, nor will it be in this walkthrough, it can be used to add one more bit of sizzle. Similar to the way a path can provide a baseline for text, it can also provide a baseline for animation. The caveat here is that this falls under the heading of SVG/SMIL animation, so it is not fully cross-browser compatible. Again, consider it a bonus for browsers that recognize it since it doesn’t break anything for those that don’t.
In Part 1, the control panel pane included a toggle for Zombie Speed, as rendered in Figure 10:
<g id="speedGroup">
<circle id="slowCircle" cx="75" cy="325" r="40" stroke="red" fill="white" stroke-width="4" />
<image id="slowZombie" x="375" y="1875" width="175" height="304" transform="scale(.16,.16)"
xlink:href="zombie.svg" />
<text id="speedLabel" font-size="15" font-family="sans-serif" text-anchor="middle" fill="red"
x="175" y="315">Zombie Speed</text>
<text id="speedText" font-size="25" font-family="sans-serif" text-anchor="middle" fill="red"
x="175" y="350">Slow</text>
<circle id="fastCircle" cx="275" cy="325" r="40" stroke="black" fill="white"
stroke-width="2" />
<image id="fastZombie" x="1630" y="1875" width="175" height="304" transform="scale(.16,.16)"
xlink:href="zombie.svg" />
</g>
Figure 10. Zombie Speed
To have a little something extra, it would be interesting if those zombies were actually moving around. To do that, two sets of new elements will be added: invisible paths and the <animateMotion> element, which is similar to the <animate>
element used in the title.
Before making the zombies walk, or rather shamble, it’s helpful to know how animation paths work, since it’s not necessarily intuitive. The key difference between using a <path>
element for animation and using one for a text baseline is that the path does not define its target element’s actual placement on the page, but rather its relative movement.
To explore this, create an empty <svg>
element of the class "svgBox
" with dimensions 400 by 400. Add a simple circle and a path:
<svg class="svgBox" width="400" height="400">
<circle cx="100" cy="100" r="50" fill="red" />
<path id="testPath" d="M 100 100 L 300 50" stroke="#000000" fill="none" />
</svg>
In this example, the goal is to move the red circle from 100,100 to 300,50. So a path is placed starting at 100,100 and ending at 300,50. To add motion, open up the <circle>
element and add the <animateMotion>
element within it, with a duration (dur) of two seconds. Then add an <mpath>
element within that specifying the path name as its xlink reference:
<svg class="svgBox" width="400" height="400">
<circle cx="100" cy="100" r="50" fill="red">
<animateMotion dur="2s">
<mpath xlink:href="#testPath" />
</animateMotion>
</circle>
<path id="testPath" d="M 100 100 L 300 50" stroke="#000000" fill="none" />
</svg>
When the page is loaded (in an animation-friendly browser), something interesting happens. Instead of moving along the path from 100,100 to 300,50, the circle jumps down to 200,200 and moves to 400,150, which brings it halfway out of the <svg> container. This occurs because of how the <path> element’s instructions are interpreted. Regardless of whether it’s absolute or relative, the path’s initial Moveto instruction gets applied as a relative placement for the element being animated. So in this case, the first thing that happens on page load is that the circle, with an origin of 100,100, gets moved +100, +100 before movement begins, based on the path’s "M" value.
This is easy enough to work with. For paths used in animation, simply start them at 0,0, the only drawback being that the path becomes a little trickier to develop and debug since, as in this example, it may extend out of the <svg>
element:
<path id="testPath" d="M 0 0 L 200 -50" stroke="#000000" fill="none" />
Now the circle moves as expected relative to its starting origin. In fact, because animation paths define relative movement, the circle can be moved to a different origin entirely and will still move in the same way at its new location, without having to modify any <path>
values.
Now, zombies. First, create the paths that will be used. The goal here is for the zombies to move within the confines of their underlying <circle>
elements. Since the paths need to start at 0,0 and will include subtle Lineto changes, the easiest way to refine the movement is to go ahead and implement the animation, then make adjustments through trial and error.
In the following revised code for the Zombie Speed toggle, two paths are added with approximately the same number of points. The key difference here is the duration of the <animateMotion>
element: Walking Dead zombies take four seconds to complete the path, while World War Z zombies take half a second. Also, a "repeatCount
" attribute of "indefinite" has been added to create an endless loop.
<g id="speedGroup">
<path id="slowPath" d="M 0 0 L 12 0 L -9 0 L 8 -3 L -8 3 L 0 0" fill="none" stroke="none" />
<path id="fastPath" d="M 0 0 L -3 3 L 4 -2 L -2 0 L 3 1 L -1 -3 L 0 0" fill="none"
stroke="none" />
<circle id="slowCircle" cx="75" cy="325" r="40" stroke="red" fill="white" stroke-width="4" />
<image id="slowZombie" x="375" y="1875" width="175" height="304" transform="scale(.16,.16)"
xlink:href="zombie.svg" >
<animateMotion dur="4s" repeatCount="indefinite" >
<mpath xlink:href="#slowPath"/>
</animateMotion>
</image>
<text id="speedLabel" font-size="15" font-family="sans-serif" text-anchor="middle" fill="red"
x="175" y="315">Zombie Speed</text>
<text id="speedText" font-size="25" font-family="sans-serif" text-anchor="middle" fill="red"
x="175" y="350">Slow</text>
<circle id="fastCircle" cx="275" cy="325" r="40" stroke="black" fill="white"
stroke-width="2" />
<image id="fastZombie" x="1630" y="1875" width="175" height="304" transform="scale(.16,.16)"
xlink:href="zombie.svg" >
<animateMotion dur=".5s" repeatCount="indefinite" >
<mpath xlink:href="#fastPath"/>
</animateMotion>
</image>
</g>
The result: the Zombie Speed toggle gives the user additional visual information about the function of the buttons and what their impact may be. Shamble vs. rage. Romero vs. Snyder. It’s right there on the page.
What’s Next
At this point, all of the basic visuals of the Zombie Apocalypse Survival Predictor are in place, plus a few bells and whistles. The <text>
element has been explored more deeply, and the <path>
element has gotten a lot of love as well. The updated Predictor uses it not only to draw lines and finish out the UI but also to provide a baseline for the title and animation paths for the zombie icons.
But quite a bit of work remains to be done. The final part of this series hooks up all of the user interaction. It explores a few different methods of interacting with <svg>
elements, including adding event handlers directly to the elements themselves and adding calls to JavaScript functions. Said JavaScript also gets used for manipulating the SVG DOM by changing text content, modifying SVG attributes and adding entirely new SVG elements to the page.
The code thus far can be seen in action here.
This article is part of the HTML5 tech series from the Internet Explorer team. Try-out the concepts in this article with 3 months of free BrowserStack cross-browser testing @ http://modern.IE
Justin Whitney is a freelance mobile app developer, Web developer, and filmmaker. While writing this article, he dreamed that he was caught in an infestation of rage zombies infected by toxic turkey dander. Fortunately, he was rescued by The Doctor.