Back

Advanced MouseOver Effects

This article is intended to give an example of an advanced mouseover effect whereby the fade takes place slowly.. This article assumes that you are comfortable in CSS and JavaScript and have some base knowledge of the jQuery library.

The base premise of creating the effect is the manipulation of alpha channel opacity. Sadly, as seen on other articles on this site, opacity can be a little trickier than one would expect. With that in mind, we are going to ignore cross browser compatibility for now and assume that jQuery has a handle on it.

At this time of writing, jQuery has a full handle on Mozilla compatible browsers (Firefox, Iceweasel, Konqueror, Safari) and IE7-IE8. IE6 and below do not seem to function correctly with opacity blends, but not a big deal. All that happens in those cases is that the effect doesn't occur, though 'old school' mouseover presentation occurs (ie: straight image replace on mouse over as opposed to the alpha blend that this article describes). I have not yet tested these effects against IE9.

Let's start by examining a sample 'hover' link. We start with two images: One for 'active' and one for 'inactive'.

Active Image Inactive Image

Please note that these are .png images. Both items have a solid background and no transparency (alpha channel) due to IE's inability to properly handle PNG transparency. Of course, GIF transparency is supported, but limits us in how clean the image will appear.

So.. how do we need to approach this? There are several approaches, but we will look at is to combine the 'background-image' css element with an img tag. To illustrate:

<div id="web_button_back" class="button_back"> <a href="/web"> <img id="web_button_fore" src="/images/web_button_inactive.png" border="0" class="button_fore" /> </a> </div>

To explain, the framework I used is to assume the following:

  • Each button will be wrapped in a div with an id of 'buttonname_button_back' with a CSS class of 'button_back'
  • The 'a' tag is irrelevant here and simply the means by which the links navigate.
  • The 'img' tag should start life displaying the 'inactive' image and have an id of 'buttonname_button_fore' with class of 'button_fore'
  • The images themselves will follow the naming convention:
    • buttonname_button_active.png
    • buttonname_button_inactive.png
In order for these elements to display themselves properly layered, we will need to set them up with some CSS styles.

.button_fore { display: inline; } .button_back { background-repeat: no-repeat; width: 140px; height: 45px; }

  • 'display: inline' for the foreground ensures that the image will be layered on top of the background properly.
  • 'background-repeat: no-repeat' ensures that the background image will not attempt to tile for cases where the image is smaller than the allocated size.
  • width and height match the width and height of the largest image in my set that I am using.
So, now we have it set to go.. but.. all we are seeing is:

Why? Because we haven't set up any mouseover effects yet!

Instead of getting all worked up and create a bunch of javascript to try and manipulate the individual elements and actions, we are going to use the jQuery library instead. Your page will of course need to include the jquery.js script to gain access to the functions we want. Once that is in place, we are going to add some additional javascript.

We will start with creating a function that will handle swapping the images out. Please remember that this is dependent on the naming scheme listed above.

function swapImages(objName,fadeDirection,speed) { // Sanity check of the inbound arguments if (!objName.length || isNaN(speed) || (fadeDirection != "in" && fadeDirection != "out")) { return; } // get the root 'name' part of the object we're dealing with objName = objName.substring(0,objName.length-4); // from which we can then get the foreground and background objects var backObj = $("#" + objName + "back"); var foreObj = $("#" + objName + "fore"); // and of course, another sanity check if (!backObj || !foreObj) { return; } // based on if we are fading 'in' or 'out', we can then determine the image paths var backImage = "images/" + objName; var foreImage = "images/" + objName; if (fadeDirection == "in") { backImage += "inactive.png"; foreImage += "active.png"; } if (fadeDirection == "out") { backImage += "active.png"; foreImage += "inactive.png"; } // set the background image backObj.css('background-image',"url(" + backImage + ")"); // and queue up the animation foreObj.queue(function() { // render the foreground transparent foreObj.fadeTo(0,0); // then set the new foreground image foreObj.attr('src',foreImage); // and fade it back in. foreObj.fadeTo(speed,1.0); foreObj.dequeue(); }); }

What the heck does it all mean you ask? Let's step through it.

The function assumes three arguments:

  1. object name - in this case it assumes that the name will be the buttonname_button_fore name. We'll get to that in a bit.
  2. fadeDirection - either "in" or "out". "in" means fade to 'active'. "out" means fade to 'inactive'.
  3. speed - the speed of the effect in milliseconds

The first 'if' section is simply a safety check to ensure that the given arguments are valid.

From there, we are taking the object name and stripping off the 'fore' portion at the end. This is so we can further manipulate it get variables pointing at:

  • backObj (#web_button_back)
  • foreObj (#web_button_fore)
As well, we use that modified name to generate the file paths to our images. What we are doing from there is the old shell game:
  • Setting the background image of the 'backObj' div to the same image as is presently being displayed by the img tag
  • Hiding the img tag by setting the opacity to 0
  • setting the img src image to the new foreground image
  • slowly fading the img tag back in
You'll note that the actions themselves, excluding setting the background image which is already hidden by the img tag, is done within a 'queue'. This is to ensure that the actions happen one after another, as opposed to all at once which can cause issues. You'll also note the use of the function 'fadeTo()'. I could have used the jQuery standard 'fadeIn' and 'fadeOut' but what I found was that fadeOut' actually set the visibility attribute to hidden - which in turn generated a new 'mouseout' event when it should not have. So instead I have chosen functions that strictly operate on the opacity attributes.

So.. now we have a shiny new function that can 'generically' handle the swapping of images that cause the effect. What we need to do now is tell our page that the function should be called on certain events. In this case, we want to 'fade in' when the mouse point hovers over the image. We want to fade out when it is stops hovering. Once again, jQuery can handle it. In our javascript, we will want to call the following code:

$(document).ready(function() { $('.button_fore').hover( function() { // get the object name var objName = $(this).attr("id"); // trim it back to the root. swapImages(objName,"in",500); },function() { // mouseleave... var objName = $(this).attr("id"); swapImages(objName,"out",500); }); });

What this does is:

  • states that an inline function should run as soon as the document has finished loading
  • finds all elements with the class 'button_fore' set
  • for those elements, registers with the special 'hover' event - similar to onmouseover but more reliable
  • grabs the 'id' attribute of the image triggered and...
  • passes it to swapImages
You will probably want to check out jQuery's documentation to get a better understanding of how the event registration works, but this is a quick an elegant method for registering the 'on hover' and 'on hover stop' events for all of the buttons in a page.

So what you end up with is a neat slow blur between images.

Full 'sample' source:

<html> <head> <title>Test</title> <style> .button_fore { display: inline; } .button_back { background-repeat: no-repeat; width: 140px; height: 45px; } </style> <script type="text/javascript" src="/js/jquery.js"></script> <script> function swapImages(objName,fadeDirection,speed) { if (!objName.length || isNaN(speed) || (fadeDirection != "in" && fadeDirection != "out")) { return; } objName = objName.substring(0,objName.length-4); var backObj = $("#" + objName + "back"); var foreObj = $("#" + objName + "fore"); if (!backObj || !foreObj) { return; } var backImage = "images/" + objName; var foreImage = "images/" + objName; if (fadeDirection == "in") { backImage += "inactive.png"; foreImage += "active.png"; } if (fadeDirection == "out") { backImage += "active.png"; foreImage += "inactive.png"; } // set the background image backObj.css('background-image',"url(" + backImage + ")"); foreObj.queue(function() { // render the foreground transparent foreObj.fadeTo(0,0); foreObj.attr('src',foreImage); foreObj.fadeTo(speed,1.0); foreObj.dequeue(); }); } $(document).ready(function() { $('.button_fore').hover( function() { // get the object name var objName = $(this).attr("id"); // trim it back to the root. swapImages(objName,"in",500); },function() { // mouseleave... var objName = $(this).attr("id"); swapImages(objName,"out",500); }); }); </script> </head> <body> <div id="web_button_back" class="button_back"> <a href="#"> <img id="web_button_fore" src="/images/web_button_inactive.png" border="0" class="button_fore" /> </a> </div> </body> </html>

Enjoy!

Back