Dive into a Liquid SVG Logo Reveal Animation
Introduction
Today we're going to make an animated loader/spinner image. Loaders and spinners tell the user to sit tight while the the browser or server does some work. We could just look up some generic spinning icons or animated gifs, but why do that when we can roll our own, learn a lot and personalize it for our company all in one fell swoop?
The Idea and the Source SVG
Once we knew we wanted some kind of animated image to indicate our loading/waiting states on our pages, I asked our designer, Patrick, what kind of image he was thinking of. Patrick suggested we make our logo look like it was filling up with liquid, but instead of liquid it was just the colors of the logo. I think I knew what he meant. Actually, I never asked. He just liked what I ended up making. I should probably find out how close to his vision I had gotten...
First off, this is the logo we're starting with . . .
I asked Patrick to get me an SVG of one of our logos, but I also asked him to include a long waveform type shape. Remember those sine-wave borders they had on the cork boards in elementary school?
So basically I was asking for at least one full wavelength (preferably a couple wavelengths) of that green border in the image above.
I took the SVG he made me and I made a copy of this wavelength object/path and mirrored it vertically. I did this in Inkscape manually and fairly imprecisely. I tried calculating out the coordinates and it didn't line up so I just zoomed in a lot and made sure the two waveforms enmeshed as closely as I could. Once I zoomed out I could only see a solid rectangle made up of the two objects, but I knew I had a waveform separating the two.
In the above image I just changed the color of one of the waveforms for demonstration. The colors do not matter at all at this point as I will be controlling fill color with CSS.
Great! I think I have all the SVG building blocks I need. Next step is to reformat the SVG document so it is more human-readable. Tools like Inkscape or Adobe's Illustrator will create fairly bloated SVG markup. I like to trim this down until I have a minimal number of objects/paths or groups and I still get the desired image to appear. I do this in the following steps:
-
Most objects will have id attributes or class attributes identifying them for the original editor, I rename these so they correspond with the actual paths/objects. In my case I have a left curly brace, a letter 'e' and a right curly brace. I gave them ids that correspond to these objects.
-
If you have any objects that you will style similarly or will be animated together put them in a group (which is the
<g>
tag. NOTE: group tags are a lot like divs. They're just containers that can be used for structure). I know I'll want my left and right curly braces to be the same colors all the time, so I put them inside<g>
tags and also put a class on the group tag so they could be easily styled. To keep the structure of the document consistent, I also put the letter 'e' all by itself in a group. I also put both of my waveform objects/paths into a group for the time being. -
Strip out any unneeded groups. Sometimes there is a lot of redundant grouping that these editors do. In my document, inside of the
<svg>
tags I have 3 top-level<g>
tags. One for the braces, one for the letter 'e' and one for the waveforms. -
Strip out any
<style>
tags. You probably put colors on objects/paths inside your SVG editor (or the SVG came that way). SVG accomplishes this with<style>
tags, and the ids they refer to have probably been wiped out in step #1. Just remove these entirely as we'll be styling this from an external stylesheet. -
Removing anything outside of the
<svg>
tags. We just want the svg element and all of its guts.
Now we should have a simpler SVG document. Something like this . . .
<svg id="logo-svg" version="1.1" ...xml namespace stuff here...>
<g class="curly-braces"> ...curly brace <path> tags and stuff... </g>
<g class="letter-e"> ...letter 'e' <path> tags and stuff... </g>
<g class="waveforms"> ...waveform <path> tags and stuff... </g>
</svg>
Here's a codepen at this stage. I also added a small amount of CSS to color the SVG and hide the waveforms at this stage.
Now that we have some good source material to work with, lets start playing with some of the fun parts of SVG in the browser!
SVG Mask
I'm going to use those waveforms not as normal objects/paths, but as two independent Mask objects. Masks are neat things. They can be used to control if pixels are rendered in a specific location depending on the shade of the pixel in the mask at the same corresponding spot.
Lets imagine I have an SVG of a red square that is 5rem on a side and I also have a circular mask that has a 5rem radius and shares the same center as the red square. If the circular mask's fill is white, then we should see a red circle render on the screen. That is because a white fill on a mask let's the masked object/path's pixels show through. If the circular mask's fill was black or transparent, we wouldn't see anything. The reason we don't see a red square is because the mask only specifies a circle because only those pixels inside the circle have a white fill. If the fill was 50% gray we will see a 50% opacity version of the masked object/path's pixels show through! Very cool, but why is this important?
My idea here is to initially show a grayscale/fully-desaturated version of the logo. Next use the waveforms to unmask waves moving from right to left across the image. What will they unmask? Well, a full-color version of the logo! But wait, there are two waveforms. What are they both doing? Both will translate from right to left, but one will mask a color version of the logo and another will mask a grayscale version of the logo. So I'll make two new groups in my SVG and copy both the curly and letter 'e' groups into both new groups. I'll give each group a class or id indicating whether it should be styled grayscale or full-color. Finally I'll translate both masks upward as well to "fill" the logo with color (masking more and more of the grayscale logo, and unmasking more and more of the color logo). Both of these translations combined should create a pleasing effect, if only after some time tweaking the speeds.
Great, we've got that idea but how do we actually get our waveforms into our SVG as masks, and how do we tie them to each version (grayscale/full-color) of our logo?
We need to move our waveform objects/paths into separate <mask>
tags. Create two <mask>
tags, give them different ids to identify them, and slap your waveform objects/paths inside their corresponding <mask>
tags. Finally for both <g>
tags each containing either grayscale or color logos add the following attribute and make the id reference the appropriate mask. mask="url(#grayscale-mask)"
.
Now we should have an SVG document along these lines . . .
<svg id="logo-svg" version="1.1" ...xml namespace stuff here...> <mask id="grayscale-mask"> ...grayscale waveform <path> tags and stuff... </mask> <mask id="color-mask"> ...color waveform <path> tags and stuff... </mask> <g id="grayscale-logo" mask="url(#grayscale-mask)"> ...grayscale logo groups and paths and stuff... </g> <g id="color-logo" mask="url(#color-mask)"> ...color logo groups and paths and stuff... </g> </svg>
Note how each version of our logo points to the corresponding mask. Now that we have all the parts matched up, let's get them rendering and moving!
Styling the groups
This part is dead simple. Just set fill
CSS properties for each part of the logo. In my case, it is two classes for each logo.
Remember we need the masks to be white to allow the image behind to shine through.
#grayscale-logo .curly-braces { fill: #9f9f9f; // Light-Gray } #grayscale-logo .letter-e { fill: #6c6c6c; // Gray } #color-logo .curly-braces { fill: #F26522; // Orange } #color-logo .letter-e { fill: #2c3144; // Navy-Blue } #grayscale-mask, #color-mask { fill: white; }
This styling will make braces in the grayscale version slightly ligher in color than the letter 'e'. The color version will have orange braces and a navy-blue letter 'e'.
Here's a codepen at this stage.
Easy stuff, let's move on to the animations!
Horizontal "Wave" Animation
We can do either the horizontal wave animation OR the vertical filling animation first. I'm just picking the wave animation first.
For the wave, I'll make both masks move from right to left (or you could also choose left to right if you wish!). I'll use the transform CSS property and the translateX transformation. This part is a bit tricky, but if you use relative units like rem you can just trial and error until it looks right. Let's try and get it so we see some whole number wavelength of right to left translation every 0.66 seconds.
#grayscale-mask path, #color-mask path { animation: waves 0.66s infinite linear; } @keyframes waves { from { transform: translateX(17rem); // Expect these values to take some hunting for } to { transform: translateX(-17rem); // Expect these values to take some hunting for } }
Now in the case of my source SVG, I had the waveforms centered more or less in the frame. So the keyframe starts with the waveforms shifted 17rem to the right, and finishes 34rem to the left, or 17rem left of its origin (a negative value on the X coordinate). These values are pretty arbitrary. I just played with making them bigger or smaller until the animation was smooth and didn't jump. You can probably math this out too, but I zeroed in on a good enough value in a few seconds.
But wait, what is that path
thing doing in your selector? It seems that putting an animation CSS property on a <mask>
element itself does nothing. You need to target something inside the <mask>
element. In my case, the waveforms are each a single <path>
element, so I target that instead of the parent <mask>
tag. If you have multiple elements making up your mask, you can nest all of the elements in a <g>
tag and write your CSS selector against that parent tag.
If all has gone well, you should have smooth wavy animation. If you have jumps, adjust your translateX values in large steps and then small steps as the jumps seem to get smaller. Don't worry, you'll get there! This is the "measure never, cut 10 times" method of creating things.
If you figure out how to calculate what this value should be, let me know. I didn't trust pixel values in a browser because I may want to resize this entire svg via CSS and have the animations still work. So I just stuck with finding the correct rem value through trial and error.
Here's a codepen at this stage.
For the vertical animation, I realized I couldn't place another animation CSS property on the elements. There can only be one. However, in the case of my waveform objects/paths, each one is just a single <path>
element. I can simply nest my waveform <path>
tags inside of <g>
tags and apply the vertical animation to that different element (immediate child of the <mask>
element now) and it'll work. Let's write that second animation out, and have it take 4 seconds to "fill up".
#grayscale-mask g, #color-mask g { animation: raise 4s infinite ease-in-out; animation-direction: alternate; } @keyframes raise { from { transform: translateY(7rem); // Expect these values to take some hunting for } to { transform: translateY(-15rem); // Expect these values to take some hunting for } }
Note that I gave it an animation-direction
of alternate. This makes the animation play in reverse once it has finished. If you only want your animation to play once you should change the infinite
part to 1
and remove the animation-direction
property entirely. I chose to make mine alternate as it is difficult and sometimes impossible to scale the animation for all situations. I usually don't know how long I need to keep the user entertained so I just assume an indefinite amount of time.
In my example I start the waveforms 7rem below their origin and move them up to 15rem above their origin. These values provide the illusion that the logo starts "empty" and ends "full". I also used the ease-in-out
easing function instead of a simple linear
easing as it looked better when alternating back and forth.
That's it, we've got a neato looking loader/spinner animation now. Time to put it up on the site!
Here's a codepen of the finished animation.
This turned out to be a great learning experience. I've always been intrigued with SVG and knew this type of masking was possible. Getting this chance to ship something using SVG was great.
If you notice that your animation is making the color "fill" from the top down, just swap your mask ids.
As a final step I wrapped the entire SVG object in another wrapper div and just set a rem width on that to scale the entire SVG. Since I used rem to animate with all those values I figured out how to make the translations look right scaled with the image nicely.
I'm not even 80% sure I went about this using best practices, but it came together so elegantly and quickly that I didn't care at the time. The animation seems to perform very well even on my ancient smart phone, and, as you can see, the entirety of the markup is limited to HTML and CSS.
Let us know if you enjoyed this walk through of our thought process when implementing a portion of our site by contacting us at info@ethode.com.
Also feel free to email, tweet or share with us on facebook if you want to show off your animated SVGs, we'd love to see them!