Interactive SVG with AngularJS – Part 1

When developing mobile web applications with responsive design, flexible images for interactive control and status elements pose a particular problem.
SVG offer a viable solution: They are much smaller than bitmaps of comparable size, and can be manipulated through their DOM API.
AngularJS in turn is well suited to wrap complex UI element logic in custom HTML directives, resulting in clean and maintainable modules.
Combining the two sounds attractive, but involves a couple of stumbling blocks to avoid.

Part 1 of this article explores several methods of employing SVG as flexible images in a cross-browser compatible manner.

Part 2 describes the use of AngularJS to construct custom control and status elements by manipulating SVG images.

Using SVG in Mobile Web Applications

When developing mobile web applications, responsive design is an attractive way of coping with the plethora of device resolutions, screens sizes, and aspect ratios. The various techniques do a good job of adjusting an application’s content and layout to any given device.

However, images still pose a problem in the mobile context, especially the somewhat unfortunate combination of a high resolution “retina” display and a slow network connection. Web applications usually employ bitmap (aka raster) images, whose encoding requires a lot of storage space; typically, the combined size and transmission time for images far outweighs that of their parent HTML document. When the web application employs large bitmaps throughout, it may cause an unacceptable delay on application startup. On the other hand, up-scaling smaller images inevitably degrades overall presentation quality.

Flexible Images

Flexible image techniques attempt to walk this fine line by providing just the right kind of image for a given device context. They typically require the graphics designer to prepare several variants of a given image, each suited to a specific display size and resolution. Then the application developer has to implement some mechanism for detecting the device context of the application, and selecting an appropriate image variant to load. Depending on the actual flexible image technique employed, this may involve multiple media queries and extensive scripting on the browser side, possibly augmented by variant retrieval and quality image scaling on the server side.

Nothing of this is particularly easy in the first place, but matters get worse when it comes to the interactive control and status elements of a web application: Each phase of a hover/press/release or ok/warning/alert cycle requires an additional bitmap to be prepared and handled in this manner! All this effort increases the complexity of the web application and makes it harder to maintain. So, is there any alternative solution?

Scalable Vector Graphics

Enter Scalable Vector Graphics. Instead of encoding each pixel of an image, they contain vector-based drawing instructions to be executed by the image renderer. SVG were specifically designed with high quality scaling in mind, so it is easy to adjust them to whatever a given device display requires. Despite being XML based, they can fit complex shapes and gradients into remarkably small file size – generally orders of magnitude less than bitmaps of comparable dimensions. Finally, SVG can be styled with CSS and manipulated through the well known DOM API, just like HTML documents. With this, a single SVG may contain all the phases of a complex control and status element, obviating the need for multiple image resources. So, what is the catch?

First off, the SVG specification has been around for over a decade, and all contemporary web browsers can display SVG images – most have been able to do so since at least three versions back. However, when actually asked to scale an SVG image, their behavior can be surprisingly quirky.

Theory

There are several methods for linking SVG images into a HTML document. This article will explore the three of them most suited for modern web applications:

  • <img>
  • <object>
  • arbitrary element styled with a CSS background-image

Other methods are not covered here for various reasons:

  • embed : similar to <object> but non-standard
  • <iframe> : similar to <object> when prepared right
  • <svg> : no repeated use, no separate caching

Whenever the HTML engine detects such a link, it must negotiate with the SVG engine to establish a viewport – a rectangular space on the page in which to render the image. Within the SVG document, the root <svg> element specifies the necessary attributes for this purpose.

width/height: Typically given in px, these provide the preferred size of the viewport. If absent, width/height default to the value "100%", i.e. the viewport will grow as large as the HTML render allows (see below).

viewBox: This defines the visible rectangle of the SVG image in its internal coordinate system. If given, the SVG renderer will map this rectangle to the established viewport, scaling the image as needed in the process. If absent, the SVG renderer will render the image as it is.

preserveAspectRatio: This only has an effect if the viewBox attribute is specified as well. A value of "none" scales the image to fill the entire viewport. Otherwise, scaling preserves the aspect ratio of the image as defined by the viewBox. It will grow only as high or wide as the larger side of the viewport, with any remaining space left unused. The value of preserveAspectRatio then specifies the placement of the scaled image within the viewport: "xMinYMin" in the top left corner, "xMaxYMax" in the bottom right corner, and so on. If absent, defaults to "xMidYMid", placing the image in the center.

Although the SVG document may specify a preferred viewport, the HTML engine ultimately decides its size. If the link element specifies a different width/height through attributes or CSS properties, they override the corresponding SVG values. If the link has no such constraints but its surrounding parent element does, they will be used instead, and so on according to HTML rendering rules.

In order to incorporate an SVG image into a flexible layout, the developer can tune all these parameters to achieve any of three distinct behavior:

  • Scale to fit the viewport, preserving aspect ratio; centered or border aligned as needed
  • Stretch to fill the entire viewport; commonly used for background gradients
  • Remain static in size; typically used in fixed sized containers only

As a final refinement, the developer may also modify the appearance of an SVG image through CSS. Like HTML, SVG supports both internal and external stylesheets. An internal stylesheet is specified in the usual <style> element, typically inside a <defs> section immediately following the root <svg>. An external stylesheet is referenced via the XML processing instruction <?xml-stylesheet?>, typically immediately after the <?xml?> declaration. Note that an SVG stylesheet will not affect the HTML document, nor vice versa. However, it is certainly possible to include the same external stylesheet in both a HTML and SVG document, to let all styling information reside in a single place.

Practice

Unfortunately, reality is rarely as neat as theory suggests. In practice, only certain combinations of SVG linking method, element CSS properties, and <svg> attributes will yield the desired flexible presentation behavior. Results in other combinations will range from minor annoyances to outright disasters.

The following recipes have been tested on the current browser generation: Firefox 26, Chrome 32, Safari 6, Internet Explorer 11 on the desktop, and iOS Safari 6, Android Chrome 32 on mobile devices. Your mileage may vary with earlier versions.

Note: When the recipe states to omit width/height on <svg>, alternatively set them explicitly to "100%". Likewise, instead of omitting preserveAspectRatio, set it explicitly to "xMidYMid", or whatever viewport alignment best matches the layout situation.

<img>

Linking an SVG via <img> is probably the most reliable method, because it will be treated just like any other image format. There is a good chance that even older browsers may support this via plugins.

scale to fit:

  • Set the flexible width/height on the <img> element
  • Omit width/height on the <svg> element
  • Set the viewBox to allow scaling
  • Omit preserveAspectRatio

stretch to fill:

  • Set the flexible width/height on the <img> element
  • Omit width/height on the <svg> element
  • Set the viewBox to allow scaling
  • Set preserveAspectRatio to "none"

Some browsers accept an alternate method, specifying width/height on the <svg> element together with preserveAspectRatio="none". However, the recipe above is closer to the standard, and thus more likely to work in the future.

remain static:

  • Omit width/height on the <img> element
  • Set width/height in px on the <svg> element
  • Omit the viewBox, it is not needed here
  • Omit preserveAspectRatio

Since the <img> element does not specify a viewport size, the browser will simply render it as requested by the <svg> attributes. The viewBox and preserveAspectRatio should have no effect, as they would merely perform a 1:1 mapping; however, their presence may confuse some SVG renderers. Similarly, most browsers will accept the same width/height on the <img> element, though some are known to apply faulty scaling in this case. Better omit these to be on the safe side.

<object>

This method uses the <object> element introduced in HTML4, so it should be almost as reliable as the <img> method. Make sure to always set type="image/svg+xml" for standards compliance. If necessary, a suitably configured <iframe> or even the non-standard ⟨embed⟩ element may serve as a fallback.

scale to fit:

  • Set the flexible width/height on the <object> element
  • Omit width/height on the <svg> element
  • Set the viewBox to allow scaling
  • Omit preserveAspectRatio

stretch to fill:

  • Set the flexible width/height on the <object> element
  • Omit width/height on the <svg> element
  • Set the viewBox to allow scaling
  • Set preserveAspectRatio to "none"

remain static:

  • Set width/height in px on the <object> element
  • Set the same width/height in px on the <svg> element
  • Omit the viewBox, it is not needed here
  • Omit preserveAspectRatio

Most browsers will work fine without the width/height on the <object> element, but it is recommended to set them just to be on the safe side.

background-image

For this method, define a custom CSS class specifying the SVG in the background-image property. Make sure to set background-repeat to "no-repeat" to get just a single image. Then assign this class to a controlling <div>, using width/height to define the viewport size. Alternatively, assign the class to a <span>, or whatever element best suites the layout situation.

In order to achieve flexible behavior, this method relies on the CSS3 background-size property. It has clearly defined semantics, so it should be less quirky than the other methods. However, it may not work in older browsers.

scale to fit:

  • Set the flexible width/height on the controlling element
  • Set the background-size to "contain"
  • Set the background-position to "center"
  • Omit width/height on the <svg> element
  • Set the viewBox to allow scaling
  • Omit preserveAspectRatio

To align the image in its viewport, specify the two values on the background-position, e.g. "top left", "bottom right", or similar. Do not set any preserveAspectRatio other that "xMidYMid", this is known to confuse several browsers.

stretch to fill:

  • Set the flexible width/height on the controlling element
  • Set the background-size to "100% 100%"
  • Omit width/height on the <svg> element
  • Set the viewBox to allow scaling
  • Set preserveAspectRatio to "none"

remain static:

  • Set width/height in px on the controlling element
  • Omit width/height on the <svg> element
  • Omit the viewBox to prevent scaling
  • Omit preserveAspectRatio

Though all these recipes can produce flexible images, only the <object> method guarantees access to the SVG DOM, which will be necessary for part 2 of this article. Some browsers may provide DOM access on <img> as well, but most are known to hide the inner details of what after all is supposed to be a generic image.

Also, <object> is the only method that reliably supports external stylesheets in SVG. For <img> or background-image, most browsers apparently do not resolve such dependencies, with the notable exception of IE 11. Consequently, styled SVG elements revert to their defaults, for example showing up with a plain black fill color. Internal SVG stylesheets work as expected in any case.

Clutter

When exporting or converting SVG images, it may be difficult to control the exact combinations of attributes required by the recipes above. However, SVG are based on XML, so they are easily fixable using a vanilla text editor. Just be sure to verify the result of the modification!

This is also a great opportunity to clean up some unnecessary clutter, which export and conversion tools notoriously like to add to their generated content. As a result, an SVG document may start like this:

&lt;?xml … ?&gt;
&lt;!-- Generator: ... --&gt;
&lt;!DOCTYPE svg ... &gt;
&lt;svg version=&quot;1.1&quot;
  id=&quot;Layer_1&quot;
  xmlns=&quot;http://www.w3.org/2000/svg&quot;
  xmlns:xlink=&quot;http://www.w3.org/1999/xlink&quot;
  x=&quot;0px&quot; y=&quot;0px&quot;
  width=&quot;156px&quot; height=&quot;117.064px&quot;
  viewBox=&quot;0 0 156 117.064&quot;
  preserveAspectRatio=&quot;xMidYMid&quot;
  enable-background=&quot;new 0 0 156 117.064&quot;
  xml:space=&quot;preserve&quot; &gt;

The marked parts are mostly harmless except for bloating the file size a bit. However, some of them may actually confuse SVG renderers, so it is generally best to remove them – except in rare cases where they serve a specific intention.

  • id : Only needed to manipulate the SVG as a whole, but using the rootElement for DOM access works just as well.
  • xmlns:xlink etc. : Only needed if the SVG actually contains hyperlinks, embedded bitmap images, or whatever requires the XML namespace.
  • x/y : These define the placement of nested SVGs, and serve no purpose whatsoever on the root element.
  • enable-background : Needed for advanced image filters, which are not very well supported by current browsers; usually just a waste of memory.
  • xml:space="preserve" : Only needed if the SVG contains embedded HTML texts, and rarely even then.

Conclusion

Although SVG support is still a bit quirky in current browsers, they provide a viable alternative to bitmaps for the purpose of defining flexible images in responsive design. SVG work particularly well for more stylized content such as logos, backgrounds, and icons, where their significantly smaller size helps to reduce application loading times. Finally, their ability for CSS styling and DOM manipulation makes SVG an ideal candidate for interactive control and status elements. Part 2 of this article will explore this aspect in more detail.