002 — Menu Icon

Suck Less at CSS November Challenge

P1xt
P1xt’s Blog
10 min readNov 8, 2017

--

This November, though ridiculously busy, I’ve set myself (and others) the challenge of practicing CSS, challenging CSS, with the plan of — in general — sucking less at it by the end of the month. If you’d like to participate, feel free to sign up here.

The Second challenge is replicating this image:

This looks deceptively simple, until you dive a bit deeper and realize that clicking on the “hamburger menu” causes it to animate and morph into an “X”. This one will be a bit of a challenge for me as I’ve never done much (any) animation in pure CSS so to start I’m keeping with what worked best for me on the last challenge and breaking everything down into concrete pieces of the task at hand so I can tackle each separately.

The tasks:

  1. Create a 400x400px square, green in color, with a slight box shadow
  2. Create three thin lines in the center of the container, each white, with lightly rounded edges and a slight box shadow.
  3. Animate the top line so that when clicked on, it sinks down to be at the same position as the middle line.
  4. Animate the bottom line so that when clicked on, it rises up to be at the same position as the middle line.
  5. When all three lines are at the same position, remove the middle line.
  6. When all three lines are at the same position, rotate the top line so that it’s at a 45 degree angle.
  7. When all three lines are at the same position, rotate the bottom line so it’s at a -45 degree angle.
  8. If clicked again, “de-animate” the lines, causing them to reverse their animation and transition back to their original position.

Steps 1 and 2 — basic initial layout

This was the easy part: Used ColorZilla to snag the background color, reused my container class from challenge 001, and used the same principles I used for making the “1” on challenge 001 to build out three divs for the three lines. Mainly using flexbox, element width and height, and box shadows to draw the three lines exactly how and where I want them.

At this point, deceptively, my project looks like:

Initial layout

This is exactly how the project will end up looking in it’s final state — however, I’ve only done the easy part (the layout) the hard part (the animations) is yet to come.

Steps 3–5 — Basic animation

At this point, I stared blankly at the screen for a moment or two, I’m no expert at CSS animations and I vaguely have a sense that I need to setup keyframes or somesuch, but I’m a bit at a loss as to how to start. And then: I decide to get a cup of tea and let the Net Ninja give me a bit of a review — so I watched:

Net Ninja’s YouTube playlist for CSS animations

He really does put out a great playlist for getting a quick overview. After a quick watchthrough of the playlist I was ready to start tackling the animation.

Notes / highlights from the playlist:

Transforms: alter an element by moving, scaling, or rotating it.

transform: translate(x, y); moves an element x amount in the left-right direction and y amount in the up-down direction.

transform: scale(x, y); grows/shrinks an element x amount in the left-right direction and y amount in the up-down direction. You use a positive number over 1 to grow, a decimal under 1 (like 0.25) to shrink. 0.5 would shrink the element to half it’s size, 2 would cause it’s size to double.

transform: rotateX(degrees); rotates an element degrees amount around the x axis (there are corresponding rotateY and rotateZ transforms which rotate around the y and z axis respectively). Positive degrees will cause clockwise rotation, negative will result in counter clockwise rotation. Some quick numbers off the top of my head for those less math inclined, 90 degrees is a quarter of the way through a full rotation, 180 degrees is halfway, 45 degrees is that magic number that will change a line from horizontal to a “bottom left to top right” angle like I’ll want for the transform on this challenge.

You can chain transforms like transform: translate(x, y) scale(x, y) to do multiple transforms in one line. The transforms are cumulative, each run in the order they appear.

Transitions: transition an element from one state, to another state, over a set amount of time.

Set the transition on the main element. For instance, if you set transition: 1s on an element, any effects you apply to that element (such as a transform on hover) will take 1 second for full effect.

You can chain transitions for various properties and effects. For instance, if you set transition: background 1s, transform 2s, any change to the background of the element will take 1 second to transition in, but if you set a transform (like rotate(180deg)) it will take 2 seconds to rotate the element.

You can provide a second number to a transition to specify the delay before the transition will begin. transition: background 1s 0.5s would cause any change to the background to start in half a second, then take a full second to execute.

In addition to the duration, and delay, you can also set a third parameter which controls whether the change speeds up, slows down, or is consistent throughout the transition. Valid values include: ease-in, ease-out, ease, linear.

Keyframes: control the intermediate steps in a CSS animation sequence by defining styles for keyframes (or waypoints) along the animation sequence.

Create one keyframe block per animation.

Syntax:

.myElement {
animation-name: myKeyframe;
animation-duration: 3s;
animation-fill-mode: forwards;
animation-delay: 5s;
animation-iteration-count: 4;
animation-timing-function: ease;
}
@keyframe myKeyframe{
from { /* some transform here representing begin state */ }
to { /* some transform here representing end state */ }
}

This will cause the myKeyframe animation to start, apply the transition between the from state to the to state over 3 seconds. Instead of from and to you can use percentages between 0% and 100%, which allows for additional control as you can set many steps in your keyframe function to control your element’s state every step of the way.

animation-fill-modecontrols whether, after the animation returns to it’s original state, maintains it’s final state, or something in between — valid values: backwards, forwards, and both.

animation-delay causes a delay between page load and when the animation begins.

animation-iteration-count causes the animation to repeat the number of times specified. You can use infinite instead of a number for the parameter to cause the animation to continue cycling “forever”.

animation-direction changes the direction of the animation. normal plays like normal, going from from to to. reverse reverses that and plays from to to from. alternate and alternate-reverse alternate between normal and reverse.

animation-timing-function controls the “ramp up and ramp down time” for an animation. The default is ease which will cause an animation to begin slowly, ramp up, ramp down, then end slowly. Other options: ease-in, ease-out, ease-in-out, linear, step-end, step-start, cubic-bezier. Note: this awesome site will let you drag a curve to setup your own values for a cubic-bezier function which lets you have much greater control over the slow down / speed up timing on your animation.

animation: myFunction 5s ease infinite alternate — shorthand allows you to combine all the various specific controls into one (kind of like how border 1px solid black combines the various things you can set on a border.) This shorthand also makes it extremely convenient to chain animations (by comma separating them on the animation:.

The Net Ninja went on to show how to handle an animation on click by using jQuery to handle the click event and add and remove classes (which contain animations) appropriately. I won’t be using jQuery (I prefer straight JavaScript) so I skipped that part.

The first thing I tackled for getting animations up and running was setting up a basic click handler to respond to clicks on the element. (Note: I later refactored this to use only one class, and to apply it to the menu-icon instead of the individual lines because, I agree, this is messy.)

const toggleMenuIcon = () => {  const line1Classes = document.getElementById('line1').classList;
const line2Classes = document.getElementById('line2').classList;
const line3Classes = document.getElementById('line3').classList;
const isNormal = ! line1Classes.contains('toggle');

if (isNormal) {
line1Classes.remove('normal');
line1Classes.add('toggle');
line2Classes.remove('normal');
line2Classes.add('toggle');
line3Classes.remove('normal');
line3Classes.add('toggle');
} else {
line1Classes.remove('toggle');
line1Classes.add('normal');
line2Classes.remove('toggle');
line2Classes.add('normal');
line3Classes.remove('toggle');
line3Classes.add('normal');
}
}

The important things to note here are document.getElementById().classList which gives you access to the list of classes currently applied to an element. Once you have access to an element’s class list, you can use contains to see if it already has a particular class, and add and remove to add and remove whatever class you like. Here, I checked for the absence of the toggle class by using ! contains (the ! means not in JavaScript) and if the classlist doesn’t contain “toggle” I remove the “normal” class and add the “toggle” class, otherwise, I do the reverse.

To test that the JavaScript portion was in place, I added some basic (gaudy) css classes to correspond to “normal” and “toggle” to ensure that clicking the menu item did, indeed, toggle the classes on and off. (I used SCSS which is why you see classes nested in classes here.)

.line1 {
&.normal {
background: green;
}
&.toggle {
background: grey;
}
}
.line2 {
&.normal {
background: blue;
}
&.toggle {
background: grey;
}
}
.line3 {
&.normal {
background: red;
}
&.toggle {
background: grey;
}
}

Tip: if you’re just starting with SCSS, note the & before the .normal and .toggle classes. If you have a class (like .line1) and you want to check whether an element with that class ALSO has another class (like .normal), you can nest the additional class inside the initial one and prepend it with & to indicate that it should be fired when both the first and the second classes are present.

That got me to this point (same place I was before, but with a functioning click handler that toggles the appropriate classes on click):

Intermediate step — shows JavaScript toggling classes.

The animations for morphing to the X proved to be quite simple after the quick review of the Net Ninja’s CSS Animations playlist.

First I created a keyframe for each line:

@keyframes line1 {
0% {transform: translateY(0) rotate(0deg); }
50% {transform: translateY(25px) rotate(0deg); }
100% {transform: translateY(25px) rotate(45deg);}
}

Then, I applied it to the line:

.line1 { animation: line1 1s ease forwards;  }

It was so easy that once I got the gist of keyframes, I ended up not just finishing steps 3–5 of my unofficial todo list, I finished 6–7 as well, all in one fell swoop, because it ended up being just another line in the keyframe.

Step 8 — If clicked again, “de-animate” the lines, causing them to reverse their animation and transition back to their original position.

Reversing proved to be a simple matter of creating new keyframes that reversed the ones I already had.

So for:

@keyframes line1 {
0% {transform: translateY(0) rotate(0deg); }
50% {transform: translateY(25px) rotate(0deg); }
100% {transform: translateY(25px) rotate(45deg);}
}

I created the reverse:

@keyframes line1-reverse {
0% {transform: translateY(25px) rotate(45deg); }
50% {transform: translateY(25px) rotate(0deg); }
100% {transform: translateY(0) rotate(0);}
}

Which pretty much just flips the transforms at 0 and 100%.

I DID start this part with the (not) brilliant idea to just use the same keyframes and use forwards and backwards on the animation — it doesn’t work that way. Let me save you some time — don’t try it, it will not work.

Final Cleanup

Once I had everything working, I did a quick review of the code I had and decided to refactor a bit.

I decided to just use one “toggle” class on the menu-icon instead of toggling two classes on and off the lines. This let me reduce a bunch of spaghetti CSS down to:

.line1 { animation: line1-reverse 1s ease forwards; }
.line2 { animation: line2-reverse 1s ease forwards; }
.line3 { animation: line3-reverse 1s ease forwards; }
&.toggle {
.line1 { animation: line1 1s ease forwards; }
.line2 { animation: line2 1s ease forwards; }
.line3 { animation: line3 1s ease forwards; }
}

But, it also caused the “reverse” animations to fire on page load, which prompted me to add a class preventing animations which gets removed on first click of the menu-icon.

&.disableAnimations {
-webkit-animation: none !important;
animation: none !important;
}

Like before, I used JavaScript to add and remove classes as appropriate.

const toggleMenuIcon = () => {  if (!hasAnimations) allowAnimations();

const menuIconClasses =
document.getElementById('menu-icon').classList;
const isToggled = menuIconClasses.contains('toggle');

if (isToggled) {
menuIconClasses.remove('toggle');
} else {
menuIconClasses.add('toggle');
}
}

You can see my code [here on CodePen].

Retrospective / notes on this challenge:

I think that breaking the challenge up into steps and just whittling away, one step at a time really helped me on this challenge.

Don’t think that you can use forwards/backwards on consecutive clicks instead of making separate animations. It won’t work.

The Net Ninja’s CSS Animation playlist really is enough about animation to tackle this challenge.

Concepts that I found myself leaning on:

  • keyframes
  • transitions
  • transforms
  • JavaScript click handlers
  • JavaScript getElement functions, the classList property of elements, and thee add() and remove() properties of classList for changing the classes applied to an element.

Onward and upward to the next challenge. I thought this one was going to be super tough. But, in the end, once you wrap your mind around the basic concepts, it wasn’t really that bad.

--

--