Ecommerce Developer
 
 

Code

Meet the Mouseover Shield Hack

 

Just about every developer who has ever worked with a hover effect in JavaScript has encountered the mouseover-mouseout problem. It seems to randomly fire mouse events when you don’t want them, only to seemingly refuse mouseover and mouseout events when you do.

This common problem derives from event bubbling, which—along with event capturing—is one of two ways that web browsers monitor events (such as clicks, hovers, and keystrokes) within the page.

In event bubbling, the hover event (whether mouseover or mouseout) is first fired at the lowest level element and then bubbles up to parent elements. Take a look at the code below. It describes a simple unordered list.

<ul>
	<li><a href="#">link one</a></li>
	<li><a href="#">link two</a></li>
	<li><a href="#">link three</a></li>
	<li><a href="#">link four</a></li>
</ul>

shows basic list

In this example, the unordered list is the parent element, and it has four child elements. Each child element also has a child element. When a user hovers over one of the links, the mouseover event fires first at the link and next at the list item, then at the unordered list.

This may not seem like a big deal, but what if you want an event to trigger some action when a user hovers over (mouseover) the unordered list and take an opposite action when the user moves away (mouseout)? You could have an issue, since moving from one child element to the next will register mouseout events for the unordered list.

Depending on how your script is written, the event might not fire when you expect it or might fire when you don’t. Perhaps the worst possible outcome would be flickering.

Here is another example. The image below shows a simple HTML page with a header and three images. I will be using this page for my example below.

shows example page

Let’s say that I have written some JavaScript to display a description of the image when a user hovers over it.

shows hover effect on example page

As long as the user hovers over the top of the image—above the description—as shown with the arrow in the picture above, all is fine. But if the user tried to hover over the description, a mouseout event would fire for the picture, followed by a new mouseover for the description.

If I had written my code to hide the description when the user moused out or stopped hovering, that script would fire as soon as the mouse pointer moved over the description because my pointer is now over the description, not the picture. But once the description vanished, the browser would fire a new mouseover event for the picture, making the description reappear. The reappearing description would cause a mouseout event to fire for the picture, which would initiate the script that makes the description disappear. We would then have a flicker. This is a prime example of the mouseover-mouseout problem.

Solving the Mouseover-Mouseout Problem

There are many ways to address the mouseover-mouseout problem. Some of those methods are elegant; some are not so much. In this tutorial, I am going to demonstrate a solution that is sometimes called a mouseover shield. I'll let you decide if you believe it is elegant or not.

The mouseover shield works by positioning a transparent element (usually a div) over top of the various child, parent, or sibling elements you will be taking action on. It then captures mouse events on the transparent element rather than on the actual elements in question.

To demonstrate this technique, I am going to imagine that I am developing a site for a company that sells high school wrestling gear. The company wants to display three products on the home page, and provide a description when a user hovers over an image of one of the products.

Start with the HTML

As always, I am going to start with the HTML. My code includes a section of “shield” divs, the images, and the descriptions.

<!doctype html>
<html lang="en">
<head>
	<title>Mouseover-Mouseout</title>
	<meta charset="utf-8">
	<link type="text/css" rel="stylesheet" href="text.css">
	<link type="text/css" rel="stylesheet" href="960.css">
	<link type="text/css" rel="stylesheet" href="reset.css">
	<link type="text/css" rel="stylesheet" href="style.css">
	<link rel="icon" type="image/png" href="favicon.png">
</head>
<body>
	<div class="container_12" id="wrapper">
		<div class="grid_12" id="header"><h1>Awesome Wrestling Gear</h1></div><!--end header-->
		<div class="grid_12" id="content">

		<span class="shield grid_3" id="1"></span>

		<span class="shield grid_3" id="2"></span>

		<span class="shield grid_3" id="3"></span>

		<div id="promo-content">
	
		<img src="product-promo-1.png" alt="image of wrestling gear" class="grid_3 promo-image" id="1-img" />

		<img src="product-promo-2.png" alt="image of wrestling gear" class="grid_3 promo-image" id="2-img" />

		<img src="product-promo-3.png" alt="image of wrestling gear" class="grid_3 promo-image" id="3-img" />

		<span class="clear"></span>

		<span class="grid_3 promo" id="1-span"><h2>Promotional Message</h2><p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed ac libero sit amet leo malesuada hendrerit. Vestibulum ac urna ut arcu tincidunt suscipit et ac. </p></span>

		<span class="grid_3 promo" id="2-span"><h2>Promotional Message</h2><p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed ac libero sit amet leo malesuada hendrerit. Vestibulum ac urna ut arcu tincidunt suscipit et ac. </p></span>

		<span class="grid_3 promo" id="3-span"><h2>Promotional Message</h2><p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed ac libero sit amet leo malesuada hendrerit. Vestibulum ac urna ut arcu tincidunt suscipit et ac. </p></span>

		</div>



		</div><!--end content-->
		<div class="grid_12" id="footer"></div><!--end footer-->				
		
	</div><!--end wrapper-->
	<script src="js.js"></script>
</body>
</html>

Next Add CSS

I also wrote a bit of CSS to stack the various sections in layers. To this end, it was easier to have the “shield” divs come earlier in the HTML. Note the z-index for my mouseover shield elements.

h1 {margin: 15px 0 15px 10px;}

#wrapper {margin-top: 100px;}

#promo-content {position: relative; top:-310px;}

.promo {background: #3c3c3c; position: relative; top: -100px; visibility: visible;}
.promo h2, .promo p {padding: 10px; color:#fff;}

.shield {position: relative; height:310px; z-index: 3;}

I am also using the 960 Grid System, which we profiled earlier here on Ecommerce Developer, to help with the layout. The grid system is in no way required, but it makes it a lot faster to lay out my example page.

With the Shield in Place

With the HTML and CSS done, my mouseover shield is now in place. In fact, if I give the shield a background color and set its opacity to, say, 85 percent, you will be able to see it on page.

.shield {background:pink; opacity: .85; position: relative; height:310px; z-index: 3;}

shows an example shield

So now I need to start my JavaScript. My first task is to hide the descriptions, since they have a visibility set to “visible” in my CSS.

I am going to write a function that (1) collects all of the span elements on the page; (2) uses a regular expression to learn if the element’s id includes “span”; and (3) toggle the visibility for those elements. This will work because only the description elements have “span” in their ids.

function firstHide()
{
	elSpan = document.getElementsByTagName('span');

	for(var i = 0; i < elSpan.length; i++)
	{
		if(/span/.test(elSpan[i].id))
		{
			elSpan[i].style.visibility = 'hidden';
		}
	}
}

Let me explain this function in more detail. The variable “elSpan” is established to represent the array of all span elements on the page. Next, I initiate a loop to cycle through those span elements. Inside of the loop, I place a conditional statement.

if(/span/.test(elSpan[i].id))

The conditional statement checks to learn if the id value of the span element currently in view contains “span.” Basically this is a simple regular expression.

Finally, if the current span does, in fact, include “span” in its id value, the elements visibility is set to “hidden.”

I will call this function at the top of my JavaScript, and I can move on.

Event Listeners

Next, I add two event listeners. One will handle mouseovers. One will handle mouseouts. They join the invocation for my firstHide() function at the top of the JavaScript file.

document.onmouseover = showPromo;
document.onmouseout = hidePromo;
firstHide();

Showing the Descriptions

Now, I need a function to show the descriptions when a user mouses over the shield.

function showPromo(e)
{
	if(!e) var e = window.event;
	var target = e.target || e.srcElement;
	if(target.className == 'shield grid_3')
	{
		var elNum = parseInt(target.id);
		var elSpan = document.getElementsByTagName('span');
		var elId = elNum + '-span';

		for(var i =0; i < elSpan.length; i++)
		{
			if(elSpan[i].id == elId)
			{
				elSpan[i].style.visibility = 'visible';
				break;
			}
		}
	}


}

The element that initiated the mouseover event is passed to this function from the event handler and is represented by the “e” in function showPromo(e).

Since World Wide Web Consortium (W3C) compliant browsers and Microsoft’s Internet Explorer (IE) manage events differently, I need to include some code to handle both kinds of browsers.

	if(!e) var e = window.event;
	var target = e.target || e.srcElement;

In this way, “target” means the same thing to both IE and W3C-compliant browsers.

Next, I want to learn if the mouseover event started at my mouseover shield. So I write a conditional statement. If the event did come from the mouseover shield, I can take further action. If not, the browser won’t do anything.

	if(target.className == 'shield grid_3')

Of course, if the event did start at the mouseover shield, I need to do more. So I establish three variables.

		var elNum = parseInt(target.id);
		var elSpan = document.getElementsByTagName('span');
		var elId = elNum + '-span';

Because my mouseover shield is separate from the elements I will be manipulating, I need to play sort of a numbers game, associating the individual mouseover shields with individual pictures, and individual descriptions. There are a few ways to manage this, but I used numbers in the id value for each. Basically, I want shield “1” to match up with “1-img” and “1-span” from the markup.

To achieve this, I first get the number value for the shield.

var elNum = parseInt(target.id);

Next, I create a variable to represent every span element on the page. (Note that I use the same variable name as the one I used in fristHide().)

var elSpan = document.getElementsByTagName('span');

Now, I need to create a variable to manage the “span” part of the id values for the descriptions. I could have used a regular expression as above, but I wanted to mix it up.

var elId = elNum + '-span';

With this variable set, I establish a loop to check each span element on the page. When the loop finds the span element that corresponds to the shield the user is hovering over, it sets that description’s visibility to “visible,” and stops or breaks the loop.

			if(elSpan[i].id == elId)
			{
				elSpan[i].style.visibility = 'visible';
				break;
			}

Hiding the Descriptions

Hiding the descriptions when the user mouses out becomes a mirror image of showing the descriptions.


function hidePromo(e)
{
	if(!e) var e = window.event;
	var target = e.target || e.srcElement;
	if(target.className == 'shield grid_3')
	{
		var elNum = parseInt(target.id);
		var elSpan = document.getElementsByTagName('span');
		var elId = elNum + '-span';
		for(var i =0; i < elSpan.length; i++)
		{
			
			if(elSpan[i].id == elId)
			{
				elSpan[i].style.visibility = 'hidden';
				break;
			}
		}
	}


}

The script is almost identical. In fact, I could even combine both functions into one, if I were writing this for a production site rather than as a demonstration.

Summing Up

The mouseover shield is a technique that seeks to solve the mouseover-mouseout problem by interposing a transparent element over the elements that will be manipulated. In this tutorial, I have demonstrated how to make a mouseover shield. Now it is up to you to decide how you will use it.

shows finished example with first description showing

shows finished example with second description in view

shows finished example with third description showing

Related Articles

0 Comments

Rss-sm