Menu Search
Jump to the content X X
SmashingConf London Avatar

We use ad-blockers as well, you know. We gotta keep those servers running though. Did you know that we publish useful books and run friendly conferences — crafted for pros like yourself? E.g. our upcoming SmashingConf London, dedicated to all things web performance.

Creating Cel Animations With SVG

What if I told you there was an image format like GIF, but it worked with vectors? What if I said it was possible to reverse the direction of its animation? What if you could take one base image and animate different parts of it separately, at different speeds? Well, the image format, SVG, already exists. It just needs a little gentle encouragement.

In this article, I’ll be mixing old with new, taking a somewhat primitive art and breathing new life into it. With the help of Sass, I’ll be streamlining the necessary workflow and hopefully demonstrating that automation can, sometimes, be a friend to creativity.

Further reading on Smashing: Link

Animations, Old-School-Style

I’m not an animation expert, but I know what I like: things that move around. This is just as well, because I design for the web and web animation is in its ascendancy. In fact, the support for animation technology is so strong now that we’ve had the luxury to shift our focus onto what the purpose and the meaning of our animations might be.

Expressive and flexible though the CSS animation spec is, there is one thing that it isn’t terribly good at. Since the mode of animation I’m referring to has had a primacy since long before I was born, this is a bit disappointing.

An Enduring Charm

“Do everything by hand, even when using the computer.”
― Hayao Miyazaki

Hayao Miyazaki’s Studio Ghibli, which has produced a slew of wonderful features including Spirited Away, Howl’s Moving Castle and Princess Mononoke, is loyal to the traditional animation technique of hand-drawing individual animation “cels”8. Unlike CGI or the keyframe animations we’re used to using with CSS, these animations are inherently time-consuming. Miyazaki is famous for cooking vats of ramen for his animators9 as they work late into the night, for countless nights in succession.

But their commitment to this way of working isn’t a mere fetishization of the past. The reason paintings have an enduring appeal after the advent of the camera is that you see life in the brush strokes: the deliberation of the artist is intrinsic and evident. The same applies to animation. Where each frame is drawn by the artist rather than being merely the artifact of a plotting vector, the product is inevitably richer. There is a tacit realism, superior to technical accuracy, when every frame is informed by observation and created by hand.

A friendly wave: the left animation uses a smooth, keyframe-based transform, while the right animation plays three independently drawn pictures (or cels) one after another. Firefox users may notice that the transform-origin for the inferior animation is not honored anyway, due to a bug10

An Unpleasant SMIL

It would be untrue to say the ability to create cel-based animations is entirely absent from the web. The clunkily named synchronized multimedia integration language (SMIL) can do just that. In fact, Jonathan Ingram has a nice tutorial on using SMIL to create a cel-based, looped animation11 of a Mortal Kombat character.

  repeatCount="indefinite" />
The SMIL animate element used to define the animation state of its parent path.

But there are grave concerns. Despite being a rather old specification, SMIL is not supported in Internet Explorer. Not only that, but there are no plans for it to be supported in IE12, 15 or even 38. Meanwhile, it is in the process of being deprecated in Blink12, meaning Chrome support is diminishing. Google’s Paul Kinlan tells me that the Chrome 45 beta actually throws related warnings13.

Support holes aside, I find the idea of using XML markup to define animations more than a little odd. I’m used to animation being a separate concern, defined in my style sheets, and I feel this is where it belongs. After all, animating the visual position of something doesn’t actually change where it appears in the document. Only JavaScript can do that.

Unfortunately, there is no explicit or terse way to create cel-based animations using CSS but, as I shall describe, it is possible to exploit a rarely used feature of CSS keyframe animation.

Taking Steps

The @keyframe animations you’re no doubt familiar with support a number of timing functions14 expressed via the animation-timing-function property. For example, the ease-in value will slow the animation rate as it reaches completion.

The little used steps() timing function is of interest to us because it staggers the animation, giving a simulated jerky – or “janky”, to quote the vernacular – motion which looks like distinct cels being played in sequence. For example, steps(5) would execute an otherwise smooth animation in five discrete steps.

All the steps() value does is put a twist on a keyframe animation; it doesn’t magically switch to a frame-by-frame idiom. However, by applying steps(1) I effectively suppress animation in order to simply switch between the keyframes. By animating the opacity property from 1 to 0 using steps(1) we can show and hide the animated element in a single step: it’s there and then it isn’t. This is vital to showing and hiding the hand-drawn cels I am going to create.

Elements As Cels

So far, I’ve concerned myself with just one element, or cel, which by itself does not make for a body of animation work deserving of Miyazaki’s homemade ramen. It’s just a picture that appears and disappears.

There’s only one thing for it, and that is to create more pictures: a set of cels that will constitute my animation. This part is inevitably quite arduous, but I won’t make any apologies for that: drawing the individual cels is what makes these animations special. What I will be able to help with later is generating the animation logic to string the drawings together.

The Markup

I’m going to be working with SVG for this. There’s no reason why the same animations can’t, technically, be applied to any set of sibling markup elements, but in SVG we can easily define complex paths and overlay them. In the example below, I’ve created a container for the animation using an SVG <g> (group) element, inside which are our cels. These cel elements should be included in the order you wish them to appear as the animation proceeds.

<g class="animation-name">
<path d="[path coords for first cel]"></path> 
<path d="[path coords for second cel]"></path> 
<path d="[path coords for third cel]"></path> 

Making The Cels

There are a number of visual editors that can handle SVG, but Inkscape15 is designed for SVG, and includes an inbuilt XML editing pane. This will make our lives easier. Oh, and it’s free to download16.

I’m going to keep this quick run-through simple, just to demonstrate the mechanics. Open up a new Inkscape document and draw three shapes of a similar size. They don’t have to look exactly like the ones in my screenshots.

Three shapes: a square, a star and a circle.17
Three shapes: a square, a star and a circle. (View large version18)

Next, place the shapes on top of one another and adjust the document size to fit. The easiest way to do this is to choose File → Document Properties and select Resize page to drawing or selection. Make sure no individual shape is selected first, or the document will fit that shape alone.

The shapes laid on top of one another19
The shapes laid on top of one another. (View large version20)

Now select all the shapes at once and hit Object → Group. This will create the containing <g> element. Then, with the new group still selected, open up Edit &→ XML Editor and give your group the .shapes class.

Inkscape dialog to set the shapes class on the group container element21
Inkscape dialog to set the .shapes class on the group container element. (View large version22)

Excellent. Now you just need to save your SVG. Choose Optimized SVG from the drop-down and make sure the Enable viewboxing option is checked. Now your SVG is ready for animation.

Choosing Enable viewboxing in the export dialog23
Choosing Enable viewboxing in the export dialog. (View large version24)
A Note On Optimization Link

Dealing with simple shapes like these keeps the SVG data to a minimum. In this case, the file size of the animated SVG (including the CSS we are about to write) is just 2.3KB. Generally speaking, the simpler the path data for each cel, the greater the number of cels you can afford to include. Animating more complex drawings, like the wobbly scribbles included throughout this article, I recommend using Jake Archibald’s stellar visual optimization tool, SVGOMG25.

Layering Animations

As I explored earlier, using steps(1) I can switch the visibility of an element on and off using opacity. This is not possible using the visibility and display properties because they do not take ordinal values (there’s nothing to animate between). To begin, I should set all of the cels within our container to opacity: 0, hiding them by default.

.shapes > * { 
  opacity: 0; 
  animation-duration: 0.75s; 
  animation-iteration-count: infinite; 
  animation-timing-function: steps(1);

As well as setting the step-based timing function, I’ve also chosen an infinite number of iterations and set the duration of the animation to 0.75s. Since each cel will appear for an equal duration and there are three cels in total, this will give me a simulated frame rate of 0.25s, or 4 frames per second.

So, how do I make each cel appear and disappear one after another? The answer is to give each element its own 0.75s animation, then run those animations concurrently, layering them up. Having three cels, each should be visible for successive thirds of the animation. I express these proportions in percentages (as expected in @keyframe syntax) and apply each named animation to the corresponding nth-child.

@keyframes shape-1 { 
  0% { 
    opacity: 1; 
  33.33333% { 
    opacity: 0; 

.shapes > :nth-child(1) { 
  animation-name: shapes-1; 

@keyframes shapes-2 { 
  33.33333% { 
    opacity: 1; 
  66.66667% { 
    opacity: 0; 

.shapes > :nth-child(2) { 
  animation-name: shapes-2; 

@keyframes shapes-3 { 
  66.66667% { 
    opacity: 1; 
  100% { 
    opacity: 0; 

.shapes > :nth-child(3) {  
  animation-name: shapes-3; 
(View large version27)

Embedding And Including

Now that I have my CSS, I can embed it in the head of my SVG file, finalizing the creation of my simple, scalable “GIF”. Since I had no task runner set up, I prefixed my animations with an online version of autoprefixer28.

<svg viewBox="0 0 100 100"> 
<!-- CSS here --> 
<g class="animation-name"> 
<path d="[path coords for first cel]"></path> 
<path d="[path coords for second cel]"></path> 
<path d="[path coords for third cel]"></path> 

Some browsers will preserve the animation of your SVG when including the file via an <img/> tag, but not all. A more consistent and reliable method is to use an <object> as specified below.

<object type="image/svg+xml" data="path_to/shapes.svg" role="img" aria-label="shapes animation"> 
  <div role="img" class="shapes-fallback" aria-label="shapes animation"></div>

Note the use of a WAI-ARIA img role and an aria-label on both the object and its fallback <div>, to provide appropriate semantics to assistive technology users. I have avoided using an <img /> fallback because some browsers will load this resource in addition to a supported SVG. You will, of course, have to provide the static image via the CSS background-image property on .shapes-fallback.

One Image, Multiple Animations

Aside from the scalability of SVG, perhaps the biggest improvement on the traditional GIF format is the ability to animate different parts of the same image, at – if so desired – different rates and overall durations. In my shark example, the tail and eyes are animated at slightly different rates, using differing numbers of cels.

The left drawing shows the cel groups of the source SVG, and the right drawing my shark brought to life.

Mathematically speaking, something rather nice is happening here: if you consider the two independent animations as one composite animation, then the overall duration is much longer. That is, if I have one animation that is three frames long and one that is four, the composite animation length is twelve frames – three times longer than the longest of the two constituent animations.

Using a GIF, where each frame is mapped directly to a cel, this would only be possible by including several more pictures. Importantly, the file size would increase accordingly.

Comparing the economy of composite animation with SVG versus gif
In the diagram, the black squares represent original cels and the grey squares represent reused cels. (View large version29)

Alternating Animations

In my shark.svg example, both the wagging tail and the blinking eyes use alternating, symmetrical animations, wherein the cels are shown in forward, then in reverse order ad infinitum. Fortuitously, each constituent cel animation starts simultaneously, making the effect possible by simply adding animation-direction: alternate to each of the cels:

.tail > * { 
  animation-direction: alternate; 

This is a further economy. To achieve the same effect with GIF, I would have to include the same pictures twice, first laid out in one direction, then in reverse.

True Cel-Based Animation

I began this article by conceding that traditional cel animation is inherently arduous compared with modern, keyframe animation. However, the innovation of animation cels was itself one of labor reduction. Before the advent of cel animations, each and every frame in an animation was drawn in its entirety — not just the animated parts. By drawing animated figures on transparent cels, static backgrounds could be reused. Aside from saving time, this also increased consistency.

A cel from Disney's Sleeping Beauty on a pencil sketched background
A cel from Disney’s Sleeping Beauty on a pencil sketched background. (Image credit: Tom Simpson30)

SVG is a text-based image format divisible into as many “subtrees” of markup as you wish, making it ideal for combining static and animated imagery in the same context.

Automation With Sass

As my animations increased in ambition, creating individual, named animations for each cel became more than a little tedious. At this point, I employed Sass to do some of the grunt work. By harnessing a @for directive and some arithmetic, I was able to generate animations automatically, like so:

$cels: 6; 
$fraction: 100 / $cels;  

@for $i from 1 through $cels {  
  $name: shapes; 
  $start: ($fraction * $i) - $fraction;  

  @keyframes #{$name}-#{$i} { 
    #{$start * 1%} { 
      opacity: 1; 
    #{($start + $fraction) * 1%} { 
      opacity: 0;   

  > :nth-child(#{$i}) { 
    animation-name: #{$name}-#{$i}; 

Note that I have used the “shapes” string and appended the iteration count to create each named animation, for example: @keyframes shapes-1 {}. I later included this logic within a mixin31 and created the name dynamically by producing a unique identifier with unique-id().

$name: unique-id();

In the following example, where the 3 parameter represents the number of cels, a unique string is used to dynamically create the named animations, cel-u358d90ae, cel-uebf9a21c and cel-u05cf8ffe.

.shapes { 
  @include cel-animation(3); 

Finer Control

It became increasingly apparent as I worked with this setup that just defining the number of cels was too simplistic. I was bound to show each cell for an equal duration. In cases like the eyes in my shark animation, I wanted to show the eyes in an open state for proportionately longer than the sequence of cels that made up the blinking motion.

To begin with, I simply duplicated the open eye cel and included six identical versions to emulate a pause in the animation, but I wasn’t happy with this: It was unwieldy and produced redundant path data, increasing the file size. Ultimately, I wanted an interface that would let me define the number of cels and their durations simultaneously: a two-dimensional parameter. The answer was a Sass list.

.eyes { 
  @include frame-animation((6 1 1 1), 0.15); 

In this example, used to generate my shark’s animated eyes, the first parameter, $cels, defines a list of cels where the first cel will appear for a duration six times longer than each of the following three. The number of cels can be expressed as length($cels), and the number of frames by adding up the integer values of each cel in the list. As the second parameter, the frame rate, is 0.15, the duration of the animation will be (6 + 1 + 1 + 1) * 0.15 * 1s.

This more mature version of the mixin is available as a handy partial on GitHub32. Enjoy, and please send me your creations so I can start putting together a little showcase. You will be credited of course!


As a designer, I’m not fond of code for its own sake. I can’t get excited about a technology unless I can see how it might solve a bigger problem. The reason I conceived the technique and wrote the necessary code for this article is because I really wanted to make cel animations of my drawings. There was already a goal. Though there are a few conspicuous features missing from the web (and some things feel like they should just be easier to achieve), there’s usually a way of making this generous array of technologies do our bidding. The design part is in determining what we want to make in the first place, for whom, and whether it’s really such a good idea.

Many thanks to Hugo Giraudel33 for his help sanitizing and commenting the cel animation Sass mixin34 and Sara Soueidan35 for her suggestion of using <object> elements to include the animations reliably in their parent page. Using inline SVG is always an option, too.

(ml, og)

Footnotes Link

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12!topic/blink-dev/5o0yiO440LM
  13. 13
  14. 14
  15. 15
  16. 16
  17. 17
  18. 18
  19. 19
  20. 20
  21. 21
  22. 22
  23. 23
  24. 24
  25. 25
  26. 26
  27. 27
  28. 28
  29. 29
  30. 30
  31. 31
  32. 32
  33. 33
  34. 34
  35. 35

↑ Back to top Tweet itShare on Facebook

Heydon is a designer, writer and public speaker. He works with The Paciello Group as a UX and accessibility consultant. His book, Inclusive Design Patterns, is available in hardcover and as an ebook from Smashing Magazine.

  1. 1

    Amazing article. I love playing around with SVG and Sass functions, and now I have a concretely creative way of doing so. A project interesting from both the code and design sides — this is what frontend development is supposed to be like :)

    I hope this plants the seeds of a thousand creative SVG cel animations!

  2. 2

    Awesome article, thank for you explaining in so much detail. I am a veteran of hand-drawn gifs and vector illustration, but not animating vectors (and definitely not animating vectors on the web). As someone who was an animator and illustrator before touching any web dev, the ability to put responsive animations on a web page is not under-appreciated!! Here I was thinking I’d have to learn how to optimize gif animations again.

  3. 3
  4. 4

    This is a genius way of doing cel animation wihout SMIL, thanks so much for doing the work to flesh this out! I’ve no doubt this will spark more creative SVG animations across the field.

    When first reading, I thought I’d still miss the shape morphing capabilities of SMIL. But I suppose that when striving for natural seeming animation with ‘life’, shape morphing is of little use anyways.

  5. 5

    Amazing skill,thanks!

  6. 6

    Nice article. You could also do a motion blur with this technique, but having your frames drop lower in opacity before disappearing completely.

  7. 7

    The SMIL example in comparison to your CSS method makes so much more sense.

  8. 9
  9. 10

    Great article! My wife and I have been experimenting with SVG on our own website ( , it’s under heavy construction right now.). I love using inline SVG and animating the individual elements with CSS… when it works. It’s really frustrating to create something I think looks good and has the desired effect, but then I switch to IE and blammo, nothing works. I really wish IE hadn’t gotten rid of conditional statements.

    It can also be frustrating if you do anything more complex, like using drop shadows or filters, as those can cause otherwise smooth animations to stutter in some browsers. Safari particularly doesn’t seem to like animating drop shadowed SVG elements smoothly.

  10. 11

    Jonny Stalnaker

    September 16, 2015 9:21 pm

    “A friendly wave: the left animation uses a smooth, keyframe-based transform, while the right animation plays three independently drawn pictures (or cels) one after another. Firefox users may notice that the transform-origin for the inferior animation is not honored anyway, due to a bug” area under the main animation of the stick figure – seems to repeat itself three times.

  11. 12

    Michael Musgrove

    September 17, 2015 2:54 am

    Great article! Thank you for taking the time and effort to put it together. Bookmarked and shared.

  12. 13

    Mads Nedergaard

    September 17, 2015 7:37 am

    This is such an interesting approach and the Sass mixing makes it even easier! I fooled around with it for a bit myself, made this rather simple animation:

    Would love to see how well it works with more complex and demanding animations, and especially how it would effect performance :)

  13. 14

    Oh! Very cool article on this theme! We started write about this also, but you are ahead of us )) So I want to add some more about traditional animation in web:

  14. 15

    Great article- I’m gonna go put it to use!

    One (or two) edits to make: Take out 2 of the 3 instances of this caption on the waving stick figures-
    “A friendly wave: the left animation uses a smooth, keyframe-based transform, while the right animation plays three independently drawn pictures (or cels) one after another. Firefox users may notice that the transform-origin for the inferior animation is not honored anyway, due to a bug”

  15. 16

    Hi Heydon, great article.
    I used this technique myself just 2 weeks and really works like a charme. My only comment will be the 3 keyframes. They can be reduced to just one. Then with animation-delay you can arrange them in time.
    The result will be e bit better readable and cleaner css.

    • 17

      Great idea for simple versions, but in my eventual mixin, I control both the start point and duration of each frame’s appearance. Not sure how I’d do that without individual @keyframes per cel.

  16. 18

    Very interesting article!

  17. 19

    I do all my cool animations in Flash – not by hand. I can do complex super cool animations, and then export them animated svg’s using Thomas Byrne’s Flash to SVG plugin.

    I can’t believe that Flash hasn’t included this feature by default. I also can’t believe trying to animate all this slowly via code. So much faster and easier just in Flash and exporting.

    • 20

      I like how easy it sounds to do SVG animations in this Flash thing you’re talking about. I especially like how it apparently has no learning curve of its own.

      How do I get Flash? Is it on npm or what?

      • 21

        There is a Flash Player runtime that runs in the browser and on desktop and there is a Flash software application used for creating animations. The runtime is not popular right now for various reasons some true some false. Users don’t care what technology is used except when it doesn’t run on their iPhones. Then when one of their favorite animation sites, “Homestar Runner” doesn’t run they wish Flash was on their iPhone. Then, on their home computer an advertisement pops up and blocks their website and they’re upset and they wish Flash wasn’t on their computer. Flash is an authoring tool for animation and other content. Flash Player is a runtime for content. It is not good or bad. It is a software runtime. It, like all software, does what you program it to do.

        – an unbiased HTML5 and Flash developer

      • 22

        Now now… So, if you’re referencing the fact that Flash isn’t open source, well, neither is Photoshop. But let’s not give up on Photoshop just yet shall we; nor should we give up on every application just because you can’t get it for free. And if you’re referencing the fact that Flash is tricky to learn, try accessing the shadow DOM and spending hours tweaking curves to create a smiley face. The amount of code needed to generate a relatively simple animation in svg is absurd and ridiculous.

        For everyone else, if you truly want to do some fantastic animations that you simply can’t achieve tweaking code all day, I suggest getting a Creative Commons license from Adobe and creating animations in Flash and export to svg! Have fun!

        • 23


          Now now…

          Thank you for the condescending tone. You’re a real charmer :-)

          So, if you’re referencing the fact that Flash isn’t open source, well, neither is Photoshop. But let’s not give up on Photoshop just yet shall we

          What you’ve done here is made the assumption that I use Photoshop. I don’t, because it’s not fit for purpose in responsive web design.

          And if you’re referencing the fact that Flash is tricky to learn, try accessing the shadow DOM and spending hours tweaking curves to create a smiley face.

          Why are you using Shadow DOM to draw a smiley face? Are you okay? Are you trying to think of the most absurd and unlikely coding scenario possible? Because that doesn’t really work as an argument.

          The amount of code needed to generate a relatively simple animation in svg is absurd and ridiculous.

          You can’t cope with @include frame-animation((1 2 1), 0.25);? That’s way too much? Or do you mean the output? Because if you’re doing Flash -> SVG, SVG is still the output isn’t it?

          For everyone else, if you truly want to do some fantastic animations that you simply can’t achieve tweaking code all day, I suggest getting a Creative Commons license from Adobe

          I think you mean “Creative Cloud”- a paid license. Flash is not freely distributed like HTML, CSS, SVG, javascript etc.

  18. 24

    Really cool article. It would be interesting to see how it compares in performance to other animation methods.

    • 25

      As I explained in the article, there are intrinsic savings in terms of file size. The performance of the CSS animations is another question. Compared to complex transforms, I imagine these simple on/off opacity jumps are fairly performant. Anyone know more here?

  19. 26

    I made a mario animation using just CSS animations. It was for an animation challenge at work, where we weren’t allowed to use any JS:

  20. 27

    Freemen Muaddib

    October 2, 2015 11:20 am

    Those animations are not fluid at all. You should consider SMIL. It offers shape interpolations at 60 fps.
    If you are worried about browser support, just use this SMIL Polyfill:
    It allows SMIL to run on all browsers.
    Here is an example of a cartoon animation file in SVG SMIL format:
    (warning: the embedded audio track makes the download sizing 1.4 megabyte)
    This was created with the “Flash2Svg” plugin. It export flash animations as a single svg file using only the power of SMIL, no need for javascript:

  21. 28

    Simply Awesome. I always try to ignore SVG as much as I can but I think your article has given me enough inspiration to dip my feet in the waters. I have usually minimum animation requirements which I most of the time achieve with HTML5 or from code snippets like from sites like wifeo, codrops or smashingmagizne offcoursebut now I think its the time to give SVG a try as well. Sam Herbert’s svg animation library is cool to use for SVG animated loaders. Also sometimes I use CSS animated loaders like one from


↑ Back to top