Expedite progress – think in the future, become estranged from the now.

  • >
  • articles
  • svg-spritesheets-vs-spritestacks
  • 2019-05-07

    17:35:50

    SVG Spritesheets versus Spritestacks

    Table of Contents

    Introduction

    This article presents two of the major ways one can use a single SVG image to render multiple images. It is by no means entirely thorough, but rather presents semi-complex working examples that reveal the major advantages and disadvantages of each method.

    It is assumed that an even width to height ratio is used.[1]

    We will be using the following SVG images in our working examples.

    stand blink mouth1 mouth2 fly1 fly2 fly3 fly4

    Method 1: Spritesheet

    • Animatable via: CSS
    • Colorable via: SVG
    • Defined in: SVG, as CSS background
    • Pros
      • Animatable via CSS background-position or as an element inside a container with top/left offsets.
      • Simple HTML declaration.
      • Lightly more complex SVG.
    • Cons
      • Complicated CSS declaration, especially if hand-coding.

    A common method for rendering multiple images from a single source is to use a spritesheet. In the case of SVG, this would be a collection of symbols placed one after another. They may be stacked vertically, horizontally, or even a combination of the two.

    These images are all rendered and will be completely visible if the SVG is used within an img tag. However, we can clip out individual portions of the SVG is we use the SVG as a background in CSS and set the background-size and background-position styles to correspond to specific positions and dimensions.

    SVG

    The SVG for a spritesheet would look something like:

    <?xml version="1.0" encoding="utf-8"?>
    <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
    <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0" y="0" viewBox="0 0 100 200">
      <symbol id="image-1">
        <!-- SVG data -->
      </symbol>
      <symbol id="image-2">
        <!-- SVG data -->
      </symbol>
      <use xlink:href="#image-1" x="0" y="0" />
      <use xlink:href="#image-2" x="0" y="100" />
    </svg>
    
    

    The important things to note are:

    • svg's viewBox property should have sufficient room for the desired size of the spritesheet. In our case the base image size is 100x100, so having two images requires an svg viewBox of 100x200.
    • We define use after defining our symbols. This method is not necessary, as we could just do without use and symbol, but it is easier to manage symbols if we set up their positional attributes after their declaration.

    CSS

    With this style of SVG we will have to use CSS to size and clip our spritesheet.

    .Image {
      width: 128px;
      height: 128px;
      background-image: url("mySpritesheet.svg");
      background-repeat: no-repeat;
      background-size: 100%;
    }
    .Image-1 {
      background-position-y: 0;
    }
    .Image-2 {
      background-position-y: -128px;
    }
    
    

    HTML

    Then, our HTML:

    <div class="Image Image-1"></div>
    <div class="Image Image-2"></div>
    

    Live Example

    SpritesheetCSS.testo-image .testo-stand.testo-image .testo-fly
    .testo-image {
      width: 128px;
      height: 128px;
      background-image: url("testo-spritesheet.svg");
      background-repeat: no-repeat;
      background-size: 100%;
    }
    .testo-stand {
      background-position-y: 0px;
    }
    .testo-fly {
      background-position-y: -512px;
    }
    <div class="testo-image testo-stand"></div>
    <div class="testo-image testo-stand"></div>

    Animation

    It is fairly easy to animate with spritesheet as we can use CSS selectors and animations to adjust the background-position offsets.

    Hover over each image to play their animations

    CSS

    The following CSS is used to accomplish the animation. It is quite long, however this could be simplified by using CSS variables and calc() to get the proper background offsets.

    .a-testo-image {
      width: 128px;
      height: 128px;
      background-image: url("testo-spritesheet.svg");
      background-repeat: no-repeat;
      background-size: 100%;
      animation-iteration-count: infinite;
      animation-timing-function: steps(1, end);
      animation-play-state: paused;
    }
    .a-testo-image:hover {
      animation-play-state: running;
    }
    .a-testo-stand {
      animation-name: TestoStand;
      animation-duration: 1600ms;
      background-position-y: 0px;
    }
    .a-testo-fly {
      animation-name: TestoFly;
      animation-duration: 800ms;
      background-position-y: -512px;
    }
    
    @keyframes TestoStand {
      0% {
        background-position-y: 0;
      }
      14.285714% {
        background-position-y: -128px;
      }
      28.571428% {
        background-position-y: 0;
      }
      42.857142% {
        background-position-y: -256px;
      }
      57.142856% {
        background-position-y: -384px;
      }
      71.42857% {
        background-position-y: 0;
      }
      85.714284% {
        background-position-y: 0;
      }
      99.999998% {
        background-position-y: 0;
      }
    }
    @keyframes TestoFly {
      0% {
        background-position-y: -512px;
      }
      25% {
        background-position-y: -640px;
      }
      50% {
        background-position-y: -768px;
      }
      75% {
        background-position-y: -896px;
      }
    }
    

    Non-background Alternatives

    If one wishes to avoid using backgrounds for the SVGs, it is entirely possible to use a containing div with overflow:auto and an internal img or object tag for rendering. The basic CSS would be something akin to:

    .container {
      position: relative;
      overflow: hidden;
      width: 128px;
      height: 128px;
    }
    .container > * {
      position: absolute;
      width: 100%;
    }
    .container .frame-2 {
      top: -128px;
    }
    .container .frame-3 {
      top: -256px;
    }
    

    And the HTML similar to:

    <div class="container">
      <img src="mySpritesheet.svg" class="frame-2" />
    </div>
    

    Example

    <div class="testo-div">
      <img src="svg-spritesheets-vs-spritestacks/testo-spritesheet.svg" class="testo-fly1" />
    </div>
    <div class="testo-div">
      <object data="svg-spritesheets-vs-spritestacks/testo-spritesheet.svg" type="image/svg+xml" class="testo-fly1" ></object>
    </div>

    Method 2: Spritestack

    • Animatable via: JavaScript
    • Colorable via: CSS, SVG
    • Defined in: SVG, HTML
    • Pros:
      • Refer to symbols directly by name.
      • No need to position symbol/g offsets for each image.
      • Simpler SVG.
    • Cons:
      • Cannot use CSS for animations.

    In the context of SVG, I define a spritestack as a collection of images(symbols) stored in the same space as one another.[2] One could think of each image as a separate layer such as in The GIMP, Photoshop, or other graphics editors.

    These symbols are not rendered on their own and if the SVG is used in an img tag, nothing will be visible. However, if defined through an svg tag containing a use tag, we can expose the individual images stored in the SVG and access them through an id attribute.

    SVG

    The SVG format would be something like:

    <?xml version="1.0" encoding="utf-8"?>
    <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
    <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0" y="0" viewBox="0 0 100 100">
      <symbol id="image-1" viewBox="0 0 100 100">
        <!-- SVG data -->
      </symbol>
      <symbol id="image-2" viewBox="0 0 100 100">
        <!-- SVG data -->
      </symbol>
    </svg>
    
    

    Note that we are setting the exterior viewBox and the interior symbols to have a width and height of 100. This is to ensure that our frames line up and are sized appropriately.[3]

    HTML

    With this SVG we can choose to display either image-1 or image-2 with the following HTML:

    <svg>
      <use xlink:href="myImages.svg#image-1" />
    </svg>
    

    As can be seen, we note the symbol we wish to use by an octothorp[4] followed by the symbol’s id.

    Live Example

    Spritestack#stand#fly1
    <svg>
      <use xlink:href="testo-spritestack.svg#stand" />
    </svg>
    <svg>
      <use xlink:href="testo-spritestack.svg#fly1" />
    </svg>

    Animation

    It is possible to animate with spritestacks if we use JavaScript to control which xlink:href we are targetting.

    Hover over each image to play their animations

    The following JavaScript implementation is less than ideal but it shows the basic functionality for animating at fixed intervals.

    let elState = {
      element: document.getElementById("testo-spritestack1"),
      use: document.getElementById("testo-spritestack1-use"),
      isAnimating: false,
      ms: 200,
      accumulator: 0,
      lastTime: 0,
      currentFrame: 0,
      frames: ["fly1", "fly2", "fly3", "fly4"],
      animate: function(time) {
        if (!elState.isAnimating) return
    
        let currentTime = performance.now()
        elState.accumulator += currentTime - elState.lastTime
        while (elState.accumulator >= elState.ms) {
    
          elState.currentFrame = elState.currentFrame+1 < elState.frames.length ? elState.currentFrame+1 : 0
          elState.use.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', "testo-spritestack.svg#"+elState.frames[elState.currentFrame]);
    
          elState.accumulator -= elState.ms
        }
        elState.lastTime = currentTime
    
        requestAnimationFrame(elState.animate)
      },
      mouseover: function() {
        elState.isAnimating = true
        elState.lastTime = performance.now()
        requestAnimationFrame(elState.animate)
      },
      mouseout: function() {
        elState.isAnimating = false
      },
    }
    elState.element.addEventListener("mouseover", elState.mouseover)
    elState.element.addEventListener("mouseout", elState.mouseout)
    

    Addendum: Colors

    Any above method that places the svg tag in the browser itself can have some rendering customization for the fill color, stroke color, stroke size, and some others. This method, in brief, would be as such:

    .testo-colorized {
      fill: #1A6EB1;
      fill-opacity: 0.5;
    }

    Although not demonstrated here, it is also reportedly possible to do more than fill and stroke color replacement through the use of CSS variables. These variables would be defined in the in-HTML svg element styling and used in the internal SVG markup.

    Conclusion

    For most use case scenarios it is apparent that using spritestacks is largely the superior method, as you do not have to calculate obtuse frame position information and the declaration is still fairly simple. However, for situations where CSS animations are preferred or required, spritesheets still win out. With the use of tools or scripts to aid the calculation of frame positions, spritesheets come up about even with spritestacks.

    Hopefully this article has been of some use for you.

    Cheers!


    1. Uneven ratios can be used but are slightly harder to calculate positional offsets for. ↩︎

    2. I am sure this phrase or one like it exists -- I hold no claim to uniqueness! ↩︎

    3. We could use different sized frames/images here. It would just be a matter of setting the appropriate viewBox dimensions. ↩︎

    4. Potentially also referred to as a hashtag. ↩︎