A stylesheet talk at 2013 ThoughtWorks North America Awayday #2013naad. We’ll look at how to use some of the powerful features of SASS to choreograph modular, maintainable and highly-flexible css keyframe animations.
Speakers at TW Awayday are asked to create a 30-second commercial to promo their talk. The talk is about the video. The video is about the talk. So meta.
We’ll be using Bourbon, a lightweight SASS pattern-library, to support CSS3 vendor prefixes. This will clean-up our code a bit and make it more readable/maintainable.
An important note: Including bourbon doesn’t write any code. It is merely a library of mixins that we can use if we so choose. aka not bootstrappopotamus.
Apply a set of keyframes to an element and tell it how long the animation will last.
1234
.foo{animation-name:pulse;animation-duration:3s;}
123
.foo{animation:pulse3s;// shorthand syntax}
Time to start building
Setup the #milkyway and bodybackground-color
Set the body’s background-color using a variable from our color palette and darken() function.
The darken and lighten functions allow you to move through the HSL color-space. This can be quite useful when need to create some contrast and already have a color-palette.
_milkyway.scss
123
body{background:darken($blackblue,10);}
Create a #milkyway container
We’ll create a reusable @mixin to ‘fill’ the screen with the #milkyway. This will helpful later as we’ll want to perform a zoom animation on everything inside the #milkyway later.
Note that this didn’t change the appearance of the screen.
The Sun
Variable-driven size and position
Define the $sun-size.
The $sun-size will come in handy when we need to align elements with our sun and/or base other elements on the $sun-size. We’ll use this variable for height, width.
Make a reusable @mixin for a circle
To create a circle, we’ll need an object that is of equal height and width and has a 50% border-radius.
We also need to create a mixin to center the sun in the window. Since we have defined the size of the sun, we can set its position, top, left, bottom and right and margin properties.
We’ll make 3 @mixins for this so that we can reuse pieces of it (that’ll come in later).
We want the sun to pulse, but we know that our use won’t be able to see the animation until after the #milkyway is finished zooming in. So, we’ll apply an animation-delay and make it equal to the $zoomin-animation-duration.
Before we can make a planet, we need to make an orbit. Then we’ll put the planet inside the orbit. This will let us animate the orbit and the planet will naturally move with its orbit.
In order to create a complete 360-degree rotation, we have to guide the rotation through 3-steps. Usually, you would have to write this out, but here we’ll use a @for loop.
Define the value of your full $rotation.
Define the number of steps to complete your rotation ($rotation-keyframe-count).
/* css output of 3-keyframe */@keyframesorbit{33.33333%{transform:rotate3d(0,0,1,120deg);}66.66667%{transform:rotate3d(0,0,1,240deg);}100%{transform:rotate3d(0,0,1,360deg);}}
I don’t really like the fact that my percentages don’t come out clean. This may cause some hiccups in easing-calculations. Fortunately we used a loop, so we can easily change the $rotation-keyframe-count from 3 to 4.
/* css output from 4-step rotation */@keyframesorbit{25%{transform:rotate3d(0,0,1,90deg);}50%{transform:rotate3d(0,0,1,180deg);}75%{transform:rotate3d(0,0,1,270deg);}100%{transform:rotate3d(0,0,1,360deg);}50%{opacity:.8;}}
Ah. Much cleaner.
Apply orbit keyframes to .orbit
We don’t want the orbits to start until after the zoomin animation and a couple pulses of the sun.
So we’ll set $orbit-animation-delay to reflect that.
The current animation works, but it doesn’t do much for me. I want to vary the speeds of the orbits. So we’ll add some logic to multiply the animation-duration in the .orbit@for loop.
We want to zoom the scene out at the end. So let’s go ahead and time that up to be after 4-orbit-animations. We’re going to hook up the zoom-out to the #milkyway.
#milkyway{@include transform(scale(1));@include animation(zoomin$zoomin-animation-duration$zoomin-animation-delaylinear,// commas separate shorthand-animationszoomout$zoomout-animation-duration$zoomout-animation-delaylinear);}
Subtitles
Loop over content-arrays with haml
First let’s get the content onto the page. When it comes to content, I often find it nice to make a quick array and loop over it. This makes tweaking/refacting markup much easier/cleaner.
_helpers.rb
12345678910111213
defwhatif"What if I told you.."enddefsubtitles[{copy1:whatif,copy2:"stylesheets don't have to be hard-coded.",},{copy1:whatif,copy2:"choreographing css animations is easy."},{copy1:whatif,copy2:"this video is only css and a few divs."},{copy1:"Want to see how flexible it is?",copy2:""},{copy1:"Orbits.",copy2:"These aren't your grandma's stylesheets."},]end
Whenever creating typography styles, I like to put them into a typography partial. In a larger web-app, this helps us to see any/all type styles that we’re using.
Position the subtitles on the page. Vertically centering an unknown text-string with display: table; and display: table-cell; is nice.
Make the subtitle container a table and set the width.
_subtitles.scss
1234567891011
#subtitles{display:table;width:100%;// tables aren't blocks. so you have to set the width.height:100%;padding:020px;// give it some breathing room on the edges.}.subtitle{display:table-cell;vertical-align:middle;}
CSS :frist-of-type
We want to break each sentence into 2-pieces for readability. So let’s add some space between the first and second halves by setting the spans to display: block and adding margin-bottom: 1em; to the first half.
We want to set the 2nd-half of the subtitles to text-align: right;. Well, the second-half, except for the last subtitle. We can select ‘all the but the frist nth-elements’ using css nth-of-type. For details on how this works, see Chris Coyier’s useful :nth-child recipes.
.subtitle{&:nth-of-type(n+#{$subtitle-half-count}){text-align:right;// right align the 2nd half of the subtitles.}}
Add the subtitle animation.
Setup subtitle keyframes
Create the subtitle keyframes. For now we’ll just fade them in and out with opacity.
Set the orbit’s initial opacity to 0. .orbit { opacity: 0; }
_keyframe_animations.scss
123
@include keyframes(fadein){to{opacity:1;}}
_subtitles.scss
123
.subtitle{opacity:0;}
Subtitle animation timing
Calculate the sum of the total animation time.
Set the animation-duration for each subtitle.
Divide the total-time by the number of messages to get the animation-duration for each subtitle.
Delay each subtitle animation.
We want the subtitle animation to last the entire-duration of all other animations combined. So first we’ll calculate the $total-animation-time, then divide that total by the $subtitle-count to determine the animation-duration for each subtitle.
Loop over the count of subtitles and delay each subtitle-animation by product of $subtitle-animation-duration*($i - 1). ($i - 1) is the count of previous subtitles.
Set the display to fixed to take the last-element out of the flow of its siblings.
Center it in the screen.
Apply a new table/table-cell relationship.
CSS :last-of-type
We want to center the last subtitle in the window and center align the text. We can target the last subtitle with .subtitle:last-of-type.
Change the display property to fixed. Note that as soon as we change the display property of the element, the rest of the table-cells fall into place.. beautiful.
We’ll create a display: table; within a display: table; to vertically center text in the last subtitle.
Now that our animations are all lined up. It’s time to have some fun. Change variables, mess with the orbit sizing function, add a planet-sizing function, etc.