Bryan Rasmussen Nowadays, I typically focus on JavaScript-intensive projects, React frontend work, accessibility, Node.js backends, ElasticSearch, and automation. I'm also proficient in documentation and technical writing.

Create cooler loading animations with SVG

7 min read 2229

Create Cooler Loading Animations With SVG

Some years ago, I made a tool for finding operational problems for customers of a major Danish telecom company (you can see it here, but it’s in Danish, so you might not care). One of the things that I thought put the service a step above most projects was the design, particularly the custom loading animation that was displayed while a user searched for problems at a particular location.

This custom animation was a little YouSee repair van speeding along to fix the customer’s problems. It was originally designed by Ronnie S. Jakobsen as a proof-of-concept placeholder, and it proved to be so popular that it was kept on.

As SVG becomes more common, I would expect to see more individualized loading animations, and in this article, I will share some techniques on how you can adapt pretty much any SVG icon to be a loading animation.

Finding an icon to use

So, first off, we should have an icon to play with. Let’s try this one from The Noun Project as a starting point.

See the Pen
User Icon from Noun Project
by Bryan Rasmussen (@bryanrasmussen)
on CodePen.

I have done some minimal changes to this in removing unneeded markup and giving the head and body parts of the icon descriptive IDs.

There are a couple things I don’t like about the icon:

  1. If you look at the markup, you will see that the user’s head is represented by a path instead of a circle element — specifically, this path:
    <path id="head" fill="#000000" d="M49.732-0.239L49.732-0.239h-0.001C38.062-0.193,28.649,9.407,28.725,21.194 c-0.076,11.904,9.337,21.501,21.006,21.441h0h0.001c11.668,0.061,21.082-9.537,21.006-21.441C70.814,9.407,61.4-0.193,49.732-0.239z"></path>
  2. If you zoom in a bit, you should see that the top is also cut off slightly.

Therefore, I have made some adjustments to the markup, which, of course, makes it look slightly different, but I think these differences aren’t really discernible for the average user.

See the Pen
User Icon from Noun Project, improved markup
by Bryan Rasmussen (@bryanrasmussen)
on CodePen.

We made a custom demo for .
No really. Click here to check it out.

Making a loading animation

The most common form of loading animation is one that just repeats infinitely, turned on and off by some event in the page where it is shown, generally controlled by JavaScript.

If we wanted to turn the user icon into a loading animation, the easiest way would be to use an SVG <feFlood> filter with an animation running indefinitely, as seen below.

See the Pen
User Icon filtered 1
by Bryan Rasmussen (@bryanrasmussen)
on CodePen.

The relevant change from the static icon is the addition of a filter:

     <defs>
              <filter id="iconfilter" primitiveUnits="objectBoundingBox" >
                    <feFlood flood-color="green"/>


             <feOffset>
                      <animate attributeName="dy" from="1" to="0" dur="4s" repeatCount="indefinite" />
             </feOffset>

              <feComposite operator="in" in2="SourceGraphic" />
              <feComposite operator="over" in2="SourceGraphic" />
        </filter>
      </defs>

And applying that filter to the group of graphical elements composing the icon:

     <g id="userIcon" filter="url(#iconfilter)">

How the animation works

I guess I should probably describe how this filter works. If all you had in the filter was the <feFlood> element, then you would basically just have a green square, as seen here:

See the Pen
User Icon Green Square
by Bryan Rasmussen (@bryanrasmussen)
on CodePen.

That’s because the filter is put on the <g> element holding both the paths. Therefore, the subregion the flood fills represents the width and height of the SVG itself.

The objectBoundingBox

If you didn’t have the attribute primitiveUnits="objectBoundingBox" on the filter, you would just see a green user icon, as seen here:

See the Pen
User Icon flooded 1
by Bryan Rasmussen (@bryanrasmussen)
on CodePen.

The way primitiveUnits works is described here, but not that well. So, to clarify some points: when it is not set, it takes the default value of userSpaceOnUse, which means that units used in the filter will be the same size of units on the object, generally 1 pixel.

The reason we want it to be objectBoundingBox is because it sets values in relation to the object bounding box of the <g> element when the filter is applied. This is especially useful because when using objectBoundingBox, 0 is the start of the bounding box and 1 is the end, meaning that our animation can move from 0 to 1 or vice versa and be understood.

How feOffset works

This is done using feOffset, which creates an offset of the image. That offset image is basically just sitting at the same location as our underlying image. feOffset is described in this MDN resource, but, again, not that well; you can find a better description in the specification.

In reading the spec, you can see that if the dx and dy of the feOffset are not specified, then the default values will be 0. So if your offset looked like the following:

    <feOffset dy="0" dx="0" />

Then you would once again have a green icon:

See the Pen
User Icon feoffset dy example 1
by Bryan Rasmussen (@bryanrasmussen)
on CodePen.

If you play around with the dy attribute — for example, something like this:

<feOffset dy="0.4" />

You will see that the top part of the head is black and the rest of the icon has the green flood. If you increase that dy to be 1, you will see a small line of green at the bottom.

So, if you animate dy from 1 to 0, it will animate the offset from the bottom to the top, meaning the black offset image will go from being so offset it nearly covers the original one, to not being seen at all, repeating indefinitely.

Obviously, if you changed it from 0 to 1, it would be from the top down. And if you changed it to instead animate dx from 1 to 0, it would animate right to left, and 0 to 1 would animate left to right. This would not be a pleasing effect with the user icon, but check out this arrow with an animated offset from left to right:

See the Pen
Arrow Icon filtered 1
by Bryan Rasmussen (@bryanrasmussen)
on CodePen.

In this example, we have changed the feFlood color to be the darker color and the fill of the path to be green. You can see that the dx is being animated from 1 to 0; if we hadn’t done that, it would look like it was the dark color that was “moving,” which is not the effect one wants in a loading animation.

How feComposite works

If you didn’t have this:

<feComposite operator="in" in2="SourceGraphic" />

Then you would have a black icon and a green square would animate from the bottom of the graphic to cover the icon, as seen here:

See the Pen
User Icon flood 3
by Bryan Rasmussen (@bryanrasmussen)
on CodePen.

If you didn’t have this:

<feComposite operator="over" in2="SourceGraphic" />

You would see an animation, but the whole icon would be coming up from the bottom of its area — not just of the color filling the icon — as seen here:

See the Pen
User Icon flood 2
by Bryan Rasmussen (@bryanrasmussen)
on CodePen.

You can read about how feComposite works here, but it basically combines images. By combining the in and over operators, we end up with the top-level image that has the animation run on it.

Making a loading component

Now that we know enough about how this animation works (although not all, as SVG is a big standard), I think it would be useful to turn what we’ve learned into code so we can have a generic loading animation that will take any SVG paths as its basis.

I will use React to make a component, although obviously you could do the same thing in whichever framework you prefer, or even in vanilla JavaScript.

The following component makes a simple loader out of any paths it gets sent, and an instruction startAt, which can take the string values left, up, or down. If it is anything other than these values, the assumption is that the animation will run right to left.

It sets some default values for you, which you can, of course, override. It is assumed that in a real project, you might want to require some properties like the style, or to always start at the left and go right, etc.

The component also uses nested ternary expressions in the interest of compactness, but in a real-world environment, one might want to unpack these expressions for legibility.

Regardless, here we have a working example:

    class Loader extends PureComponent {
        render() {
            const {
                    styles = {},
                    speed = "3s",
                    width = "100%",
                    height = "100%",
                    repeatCount = "indefinite",
                    startAt
            } = this.props;
            const isHoriz =  (startAt === "top" || startAt === "bottom");
            const from = (startAt === "left" || startAt === "bottom") ? 0 : 1;
            const to = (from === 1) ? 0 : 1;
            const attName = (isHoriz) ? "dy" : "dx";
            const fill = (styles.fill) ? styles.fill : (startAt === "left" || startAt === "bottom") ? "#fff" : "#000";
            const flood = (styles.flood) ? styles.flood : (fill === "#fff") ? "#000" : "#fff";
            const stroke = (styles.stroke) ? styles.stroke : "none";

            return (
                <svg xmlns="http://www.w3.org/2000/svg" width={ width } height={ height }>
                    <defs>
                   <filter id="fullFill"
                    primitiveUnits="objectBoundingBox" 
                    x="0%" y="0%" 
                  width="100%" height="100%">
                          <feFlood x="0%" y="0%" 
                            width="100%" height="100%" 
                        floodColor={ flood } />
                          <feOffset>
                             <animate attributeName={attName}
                               from={from} to={to}
                               dur={ speed } 
                               repeatCount={ repeatCount }/>
                           </feOffset>
                           <feComposite operator="in"
                             in2="SourceGraphic" />
                          <feComposite operator="over"
                             in2="SourceGraphic" />
                            </filter>
                    </defs>
                    <g fill={ fill } filter={ `url(#fullFill)` } 
                         stroke={ stroke }>
                          { this.props.children }
                    </g>
                </svg>
            );
        }
    }

So, if we call that with something like the following:

    <Loader startAt="left">
         <rect  x="0" width="100" height="10" />
    </Loader>

We would get a rectangular loading icon going from black to white every three seconds. You can see it here:

See the Pen
React Simple Loading Icon
by Bryan Rasmussen (@bryanrasmussen)
on CodePen.

In the same way, if we want to animate from the bottom to the top, we can do:

    <Loader startAt="bottom">
        <rect  x="0" height="100" width="10" />
    </Loader>

See here:

See the Pen
React Simple Loading Icon 2
by Bryan Rasmussen (@bryanrasmussen)
on CodePen.

And for the sake of completeness, here are a couple example counting-down loaders. Going from right to left:

See the Pen
React Simple Loading Icon 3
by Bryan Rasmussen (@bryanrasmussen)
on CodePen.

Going from top to bottom:

See the Pen
React Simple Loading Icon 4
by Bryan Rasmussen (@bryanrasmussen)
on CodePen.

And here is an example using one of our previous selections of paths being given to the loading icon (startAt="bottom"):

See the Pen
React Simple Loading Icon Body
by Bryan Rasmussen (@bryanrasmussen)
on CodePen.

Of course the component takes a number of other props. Their uses should be evident from the previous examples, but take a look at the CodePen below as a reminder. Here, the styles set are {{flood: "green", fill: "white"}}

See the Pen
React Simple Loading Icon Body Styles set
by Bryan Rasmussen (@bryanrasmussen)
on CodePen.

You should note that if you set the Fill to be transparent, then the flood will only flood the stroke, so you should probably define a stroke at that point.

You can also set a stroke color in the styles object if you want the object to have a border that does not change with the loading filter. Here, we’ve adjusted the speed to six seconds.

See the Pen
React Simple Loading Icon Body Styles set
by Bryan Rasmussen (@bryanrasmussen)
on CodePen.

Height and width, if they are not set, will default to 100 percent, thus filling the area available. In most cases, I suppose that these won’t need setting, but you might have a reason to. Of course, you can also do something silly like set the width to 50 percent, as seen here:

See the Pen
React Simple Loading Icon Body 50%
by Bryan Rasmussen (@bryanrasmussen)
on CodePen.

Finally, the repeatCount prop just says how often we want the animation to repeat, as per the SVG attribute.

You might consider changing this to just pass in a class as a prop instead of the styles, as seen in this CodePen:

See the Pen
React Simple Loading Icon Body with css class
by Bryan Rasmussen (@bryanrasmussen)
on CodePen.

I haven’t included the minor code changes here to save space, and obviously in real life you would probably choose a color scheme more pleasing to the eye, but the usage is clear. At any rate, there is a large number of properties that can be set by CSS in SVG; they are listed in the specification, and this list of properties will increase with the implementation of SVG 2.

This use of a CSS class allows us to remove the fill and stroke properties and not have a style object anymore — we’d just pass in the flood property to define what color our paths get filled with. Obviously, this opens up the possibility to control what classes get set via our usual React state management methods.

Conclusion

So with this, we have a generic method for turning simple SVG graphics like icons into loading animations, plus an example React component that we can use with any collection of SVG paths as children. I hope you find it useful and it spurs you to experiment with SVG animations in your own applications.

Full visibility into production React apps

Debugging React applications can be difficult, especially when users experience issues that are difficult to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket is like a DVR for web apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your React apps — .

Bryan Rasmussen Nowadays, I typically focus on JavaScript-intensive projects, React frontend work, accessibility, Node.js backends, ElasticSearch, and automation. I'm also proficient in documentation and technical writing.

Leave a Reply