Resolution Independence with SVG in Adobe AEM (formerly CQ)

SVG, or Scalable Vector Graphics, is an XML-based vector image format that has lately had quite a resurgence in popularity amongst developers. This is largely due to the rise of responsive web design, as the benefits to using SVG neatly solve some of the design challenges of developing for a wide range of devices, as well as for high-resolution (retina) displays.

There are several ways to implement SVG, each with its respective advantages and disadvantages. The principal considerations are browser support, ease of implementation (which may include a fallback for less feature-rich browsers), and the ability to manipulate the SVG with CSS and JavaScript.

In this post, we’re going to focus on using inline SVG in HTML5 and using SVG in CSS backgrounds, as they both have good browser support and their implementations compliment one another. For the former, we’ll demonstrate how to insert an inline SVG from a DAM asset into a component, while we’ll integrate the latter into our front end workflow with Grunt.

My colleague, Jordan Miller, will be providing an assist with information about inserting an inline SVG from the DAM.

The Benefits to Using SVG

  • As a vector format, it is resolution independent, so it scales without any loss of quality
  • Can utilize CSS and media queries, although support varies by implementation
  • Typically has smaller file sizes than raster image formats
  • Can be manipulated with JavaScript, although again, support varies by implementation
  • Better for icons than an @font-face icon font, as SVGs allow for multiple colors and scale better.

Ways to Implement SVG

There are four basic ways to use SVG in a website: as inline SVG in an HTML5 document, as a CSS background image, and within an <img> tag or an <object> tag.

In general, the current browser support for SVG is very good, save for the usual suspects of IE8 and older versions of Android, although again, this varies by implementation.

Inline SVG in HTML5

  • Support: See http://caniuse.com/svg-html5
  • Benefits:
    No HTTP request for an external file
    Part of the DOM, so can be easily accessed by external (read: the site’s) CSS and JavaScript
  • Disadvantages:
    Can’t be cached like an image
    Difficult to implement a fallback for, especially within the context of a CMS
  • Requires: An HTML5 doctype

SVG in CSS Backgrounds

  • Support: See http://caniuse.com/svg-css
  • Benefits:
    Easy to implement a fallback for
    Can be made into a sprite to save HTTP requests
  • Disadvantages: Not part of the DOM, so cannot be manipulated by external CSS or JavaScript

How to Use With Adobe AEM (formerly CQ)

Inline SVG in HTML5

One convenience afforded by using AEM is access to Digital Asset Management or DAM. In most cases an asset, most commonly an image file, may be accessed via a simple path within AEM. In the case of an SVG file, a supported file type of the DAM, simply accessing the file itself is only the first step.

In order to include and manipulate an inline SVG, access to the underlying markup is needed. How is this to be achieved? Luckily, the DAM provides us with an easy-to-use Asset API. This provides access to a wide range of operations pertinent to a DAM asset, including getting the original, which represents the binary of the original file that was uploaded. In the case of an SVG, this is the underlying markup.

By obtaining an instance of the sling ResourceResolver by normal means, we can get the Resource representation of the SVG. This can then easily be adapted to an Asset object:

SlingHttpServletRequest request = (SlingHttpServletRequest) pageContext.getAttribute("slingRequest");
 
Asset svgAsset = request.getResourceResolver().getResource("path/to/svg/in/dam").adaptTo(Asset.class);

Once adapted to an Asset object, we can then use the methods provided to get the original binary in a stream, which can, in turn, be converted to a string:

StringWriter writer = new StringWriter(); IOUtils.copy(svgAsset.getOriginal().getStream(), writer, "UTF-8");
 
String svgMarkup = writer.toString();

This technique provides that if a path to an SVG in the DAM is provided, a string representation of the underlying markup is returned wherever needed.

SVG in CSS Backgrounds

A typical use case for SVG in CSS backgrounds is for icons, which we’ll assume won’t be authored in AEM, but rather implemented as part of the site’s design in a component or template.

To do this, we’ll use Grunt, which has a number of plugins for working with SVGs:

Of those plugins, we’ll go ahead and use Grunticon, although it should be noted that each plugin has its own strengths and weaknesses. You should evaluate each option against the requirements of your project to ensure that you’ve found a good fit.

The benefits to using Grunticon are that it generates PNG fallbacks for the icons, checks for browser support, and then asynchronously loads the appropriate file format, which results in icons that work in a wide range of browsers. It also allows for custom selectors, automated color variations, and custom widths and heights.

The drawbacks to Grunticon are the JavaScript dependency (if there’s no JS then the PNGs are served) and the URI embedding of the SVG and PNGs, which may cause performance problems. Even with those drawbacks, it’s still a very good tool for implementing SVGs in CSS backgrounds.

Grunt Task Configuration for SVG in CSS Backgrounds

With Grunt, we’ll clean our icons directory, optimize and minify the SVG icons to reduce file size, and then run those SVGs through Grunticon:

// clean the generated icons directory that’s used with grunticon
clean: {
  icons: ['<%= pathTo.jcrRoot %><%= pathTo.projectDesigns %>static/icons/dist/']
},
// minify SVGs
svgmin: {
  options: {
    plugins: [
      {
        removeViewBox: false
      }, {
        removeUselessStrokeAndFill: false
      }
    ]
  },
  build: {
    files: [{
      expand: true,
      cwd: '<%= pathTo.jcrRoot %><%= pathTo.projectDesigns %>static/icons/source/raw/',
      src: ['*.svg'],
      dest: '<%= pathTo.jcrRoot %><%= pathTo.projectDesigns %>static/icons/source/compressed/'
    }]
  }
},
grunticon: {
  build: {
    files: [{
      expand: true,
      cwd: '<%= pathTo.jcrRoot %><%= pathTo.projectDesigns %>static/icons/source/compressed/',
      src: ['*.svg'],
      dest: '<%= pathTo.jcrRoot %><%= pathTo.projectDesigns %>static/icons/dist/'
    }]
  }
}

Be sure to load the grunticon.loader.js with the rest of your JavaScript to ensure that Grunticon is able to serve the correct icon format.

SVG Feature Detection

If you need to support less capable browsers, it’s recommended to use Modernizr to test for the feature support. The library currently offers a half dozen SVG feature detects, although the SVG as an Image is the only test that has relevance for us. Testing for CSS background support is apparently not possible, so the SVG as an image test is the next best alternative.

There’s a test for inline SVG support, but our implementation will include the SVG without regard for support since there’s no good way to conditionally include the markup.

Fallback for SVG in CSS Backgrounds

With the Modernizr CSS class “.svgasimg” (or “.no-svgasimg”) you can conditionally load your background SVG and fallback PNG. You can either assume no support for SVG or alternately assume that the browser will have support:

// here we assume support for SVG
.my-background-class {
  background-image: url('my-icon.svg');
   
  // but if there’s no support, we use the PNG
  .no-svgasimg & {
    background-image: url('my-icon.png');
  }
}
 
// alternatively, we could use the SVG, but only when there’s support for it
.my-background-class {
  background-image: url('my-icon.png');
   
  .svgasimg & {
    background-image: url('my-icon.svg');
  }
}

Fallback for Inline SVG in HTML5

The best fallback technique for older browsers is to wrap your SVG markup in a group (<g>) tag, wrap your fallback image in a <foreignObject> tag, and then wrap both of those code blocks in a <switch> element:

<!DOCTYPE html>
<html>
  <head>
    <title>Fallback for Inline SVG in HTML5</title>
    <style type="text/css">
     .my-image {
        background: url('my-image.jpg');
        width: 100px;
        height: 100px;
     }
    </style>
  </head>
  <body>
   
    <!-- browsers that support SVG hide the fallback via this .fallback CSS class, otherwise this entire block of SVG markup is ignored by browsers that don’t support it  -->
    <svg xmlns="http://www.w3.org/2000/svg" width="0" height="0">
     <style>
     <![CDATA[ 
      .fallback { background: none; background-image: none; display: none; }
     ]]>
     </style>
    </svg>
     
    <!-- your inline SVG code -->
    <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100">
      <switch>
        <g>
          <!-- put inline SVG here -->
        </g>
        <foreignObject>
          <div class="my-image fallback"></div>
        </foreignObject>
      </switch>
    </svg>
     
  </body>
</html>

he switch tag will evaluate the children elements in order and the first that has the proper attributes will be rendered, which will be the group with the SVG. You can then hide the image within the foreignObject tag. Browsers that don’t support inline SVG will ignore the XML and instead render the fallback image.

Optimization

SVGs, like any other asset, should be optimized for file size. This can be done by hand, with an in-browser tool, via a Grunt task, or with the command line.

Summary

With the rise of responsive web design and the greater prevalence of high pixel density displays, it only makes sense to start incorporating SVGs into your design and development workflow. Join us in implementing SVG with Adobe AEM (formerly CQ) for a web that’s increasingly resolution-independent!