Fancy animated menu buttons, but faster and more fun!

Recently i bumped across at least four unrelated sites that had the same idea for the sidebar menu: make the item names have a funky font and have the background pattern light up when the user hovers over. While the idea is cool and artsy, the implementation often makes the menu annoyingly slow and not instantly reactive. The core problem lies in not adhering to one of the faster web rules: if it can be done with css and not burned into an image, it's usually better to do it with css. And doing this exact idea using CSS is not only lighter on your visitors' bandwidths, but also opens up a bunch of new creative possibilities!

The naive method

Let's start with the simple idea. I want three buttons that say 'home', 'about' and 'blog'. I want them to be written in this font and have one of these background patterns. I also want this background to brighten up when i hover over a button.

My first obvious thought is to grab my (cracked) photoshop (cs6) and get to work. I make six png files titled home.png, home_hover.png, about.png, about_hover.png, blog.png and blog_hover.png - all six rendered as png24. Then i display the three non-hover ones using the <img> tag. To make them change on hover, i will set the onmouseover attribute of each tag to change its src to the other image, and the onmouseleave to revert it back to the original one.



The result looks like this - i omit the vertical layout styling for simplicity's sake - so i used break-return tags instead. Another simplification is the actual menu action of taking you somewhere upon click: this is done either by wrapping each <img> in an <a>nchor, or doing this via JS in an onclick attribute (gah, one more!). Try hovering and noting the time it takes to show you the hover variants!



Although this works, knowing Neocities, you probably experienced some lag while the alternative images were loading. Plus... look at the abhorrent amount of boilerplate attribute coding and the tediousness of creating each button, in double! Let's do the same thing, but with css. We start off with a very simple HTML structure, and i promise you - it will be left untouched until the very end of this tutorial, keeping the markup insanely clean. We're going with an unordered list full of list items instead of a bunch of <div>s; this allows for better compatibility with reader mode, screen readers, and prevents the HTML markup from becoming a div-only hell. Of course, the href attributes should be set to wherever the button should be taking you. Note: clicking on a link with href="#" takes you to the page top, so... don't click those, i guess.

CSS: engage!

First things first, let's create a baseline feasible prototype.

I assigned the buttons ID to the <ul>; now i can style its insides without spending time on assigning them any classes. Before the list items, i put the <style> tag that allows me to write CSS right in the middle of HTML. On your pages, please, put your CSS in a dedicated file and hook it up using <link> instead!

Now, i style the list items that are inside #buttons and give them some colours. Then i style #buttons li:hover to some other colors. :hover is a pseudoselector that matches when something happens to the element and overrides its styles with something else, e.g. get different colours if the cursor is hovered over. The HTML layout already feels so much fresher. Now, let's add the spice by styling it!

Since every <li> contains an <a>, whose color is independent from its parent's, we have to explicitly force it to refer to the parent's color all the time with inhertit. This way, all color changes from hovring over <li> will propagate over to its child <a>.

Below is the result:

Buttonizing it

It still looks like a list of links, just with fancy backgrounds and colourful hover effects. Let's make things look more like a bunch of buttons. First things first, let's set <li>s' display to block, instead of the default list-item - this will make the list bullets go away and make the <li>s behave more like <div>s. Also, any <ul> comes with a pre-defined <padding-left>, so, let's zero that out, too. The pattern image that i saved as pattern.gif is 40 by 40 pixels, so the button dimensions have to be a multiple of that in order to not cut in the middle of the pattern. The pre-rendered ones are 240x80, so let's just stick with that. The anchor's annoying underline is removed with text-decoration: none. The new style looks like this:

Positioning the text

The biggest issue now is that the <a>nchor is precisely of its text's size and stuck to the top-left of the listitem (<li>). This looks bad, but worst of all, it means that the button's link only work by clicking precisely the text, and not anywhere on the button.

First, the anchor's position is set to absolute. This means that it can "dangle" inside its parent element like a free bird, not affected/not affecting any other elements. Then we can stretch it to perfectly fit its parent. However, this doesn't work if the parent's position is static - which is what most HTML elements' default position is! Let's change <li>s' position to relative - this is basically like static, but allows you to use properties like left or top on an element.

Now that our <a> is absolutely positioned inside the list item, let's make it fill up the entire area: width: 100% and height: 100%. Since our parent, the list item, has its width and height explicitly defined in pixels, its children will treat that as "100%". By setting the anchor's width and height to 100% we simply make them equal to the parent listitem's. dimensions. The text is easily alightned horizontally with text-align: center. Unfortunately, we can't use vertical-align: middle with same success - it works on inline elements inside the same parent element, not on the text rendered inside one single element. A workaround is to set the anchor's line-height to match the button height; this way, the text line's vertical center will be precisely in the middle. 100% will not work, since values in % for line-height are relative to the font, not to the parent (i luv css!) - 100% will mean 100% of the font, not of the parent listitem. Absolute values in pixels are still accepted, though, and since we know the button is 80px, we can just shove that value right into line-height.

Another issue is the background image is now blocking off the background color, so we no longer see the color change on hover. background-color may even be deleted altogether, since now it's redundant. The pattern has to be faded-out when the button is not hovered over, but currently it's bright-green regardless. This is done by setting the using the filter property to grayscale(100%) for all <li>s, negating this filter with filter: none in the li:hover bit. There's a whole bunch of filters like hue and saturation, so you're not limited to just black-whiting.

Do note that the entire <li> is receiving the filter, though; the workaround for that is described as an addendum to this article.

Styling it up

The font is still not fancy enough, so let's add our font!

Doing so is simple, assuming we have the font file handy, which we do - it's called perfume.ttf, and we import it using the @font-face at-rule. Note that this directive has to go at the very top of your CSS file! While we're at it, let's match the font size to the one we have in the original images, which was 60px.

After changing the font, one may notice that the rendered labels are shifted to the right slightly, and are not exacly in the middle anymore. That's .ttf magic, and it's ok to fix it by adjusting line-height and width for #buttons li a to push the text back in place vertically and horizontally. After slight adjustments, everything looks wonderful.

Since the font and the puny background pattern are shared by all buttons, you can spawn dozens of them, and they will be loading as fast as one, without you having to create new images for every single one of them and without having to write tedious inline attribute code for each. But why stop there? Since now everything is CSS-based, we can apply some animations on it to make it stand out even more!

I used the transition rule to make the button not instantly colorjump, but nicely fade between the states. I spelled out every property that has to be animated with transition - and you should too, otherwise you will run out into stuff drifting unexpectedly when the page is freshly loaded. Setting an offset of -40px, 40px to background-position (one pattern's width to the left, height to the bottom) so that it does a cool slide when hovered and unhovered. Finally, i also used an inset box-shadow to make the button feel like a cutout when the background does the not-so-parallax shift.

Conclusion

Now you know how to make a pretty-looking button with a changing background without making two separate images for each button! I hope you agree that this method is much easier on bandwidth, saves you from doing boring stuff all over again, and even allows effects outside of a sharp image-to-image transition! Note how during this article, the HTML markup never changed and was as simple as

- as markup should be. Adding or changing buttons will now feel like a breeze. I hope this article convinces some webmxters out there to consider using fast and fluid CSS instead of tedious and bulky multiimaging!

Addendum: separating the background

As mentioned earlier, applying filter on an element applies it on its entirety, with its children. This means that our <li> greyscales the <a> along with it. One workaround is to simply separate the background from the list item and only apply the filtering on it - and no, we're not making a <div> inside of it! No clutter in the markup, all clutter in CSS! Let's take the last example from the main article as our starting point and change color for #buttons li to, say, green. Below is a tesout with a single button.

You can see the text stopped being black, because it's no longer set to black... but it's not green either. That's cause it got greyscaled along with its parent. Oh no! Let's fix this.

We make a new declaration for #buttons li::before. The ::before part is another pseudoselector - a very, very useful one! It creates a pseudoelement inside every element it matches, before its inner HTML. In our case, something will go before every <a> for each <li> inside our <ul id="buttons">. This something has to have some content - otherwise it doesn't get rendered, so we set content to an empty string. Then, we use the positioning trick again. The list item is already relatively positioned, so we can lightheartedly absolute our ::before pseudoelement and max-out its height/width to that of its parent. Boom, we have a pseudo-div automatically rendered in each list item, without any clutter in the markup! This pseudo-div is, however, a rectangle - and i styled listitems to have smooth corners via border-radius in the previous step. Let's fix this by setting the ::before's border-radius to inherit: this way, it will just copy whatever its parent does, copying its rounded edges.

Now it's as simple as moving everything about the background to this ::before element - so, the background image, the filter, the box shadow (which will now get covered by the ::before otherwise). It's ok to simply cut-n-paste the needed rules from #buttons li to #buttons li::before for usual rules and from #buttons li:hover to #buttons li:hover::before for the on-hover ones. Matching with #buttons li:hover::before works like a charm, although looks clunky. Think about it this way: it translates to "put this ::before after a hovered li somewhere inside #buttons".

Although the CSS is a bit complex by now, the result is the preserved text colour with grayscaled background - and an open field for further customizations with 100% instant repeatability from button to button!

← Back to the web tutorial index