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:
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 relative
ly 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!