Expedite progress – think in the future, become estranged from the now.
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 withtop
/left
offsets. - Simple HTML declaration.
- Lightly more complex SVG.
- Animatable via CSS
- 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 withoutuse
andsymbol
, 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
Spritesheet | CSS | .testo-image .testo-stand | .testo-image .testo-fly |
---|---|---|---|
|
|
|
|
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
|
|
|
|
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 |
---|---|---|
|
|
|
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:
|
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!
Uneven ratios can be used but are slightly harder to calculate positional offsets for. ↩︎
I am sure this phrase or one like it exists -- I hold no claim to uniqueness! ↩︎
We could use different sized frames/images here. It would just be a matter of setting the appropriate viewBox dimensions. ↩︎
Potentially also referred to as a hashtag. ↩︎