RailsConf 2008

Timeout your Mouseovers

Jul 05, 2006 65 comments

This is a fun effect I built for Chowhound that does one better for mouseovers. The problem with most menu systems is that they’re really touchy whether you’re too fast or slow with the mouse.

The trick is to use a timeout with the effect, so it will wait a fraction of a second to pop-up, and a fraction of a second to go away…just enough to make the effect feel solid and not finicky.

I won’t take credit for the idea, it was inspired from Amazon’s website when you roll over the “See All 34 Product Categories” tab at the top.

Here’s the code to it (you will also need prototype.js and effects.js):


<script type="text/javascript">
var RollIt = {
    timeout : null,
    showPopup : function(){
        clearTimeout(this.timeout);
        if($('rollit').style.display == 'none'){
            this.timeout = setTimeout(function(){new Effect.BlindDown('rollit', {duration:.3, fps:40})},400);
        }
    },
    hidePopup : function(){
        if($('rollit').style.display == 'none'){
            clearTimeout(this.timeout);
        }else{
            this.timeout = setTimeout(function(){new Effect.BlindUp('rollit', {duration:.3, fps:40})},300);
        }
    }    
}
</script>

<div style="padding: 10px; background: #fff999;">
<a href="#" onclick="RollIt.hidePopup('rollit')" onmouseout="RollIt.hidePopup('rollit')" onmouseover="RollIt.showPopup('rollit')">Roll over Me</a>
</div>

<div id="rollit" style="display:none; background: #ff9999;" onmouseout="RollIt.hidePopup('rollit')" onmouseover="RollIt.showPopup('rollit')"> 

<h3 style="margin: 0;">Roll It</h3>
By keeping your mouse hovering over this div or over the link that made it appear, it will stay open.

<br/><br/>
But, if you roll away from this div, it will roll back up.
</div>

<div style="background: #fff999; padding: 10px;">
  Here's a bunch of other content
</div>

This can be adapted countless ways with any effect you’d like, have fun!


65 comments


jaxzin said about 9 hours later:

Found a bug: Roll over. Roll away, and roll back over before the div finishes closing. The div will not reopen. Tested in IE6 SP2. Great idea otherwise.

Tim said about 9 hours later:

Cool! Good usage of the two libraries – thanks for the tips!

Jim said about 9 hours later:

I could not duplicate the bug stated by jaxzin. Tested WinXP IE6 SP2

Ash said about 9 hours later:

“bug” happens in Firefox 1.5.x as well.

Caleb said about 9 hours later:

I can duplicate jaxzin’s bug in firefox and IE6 on xp. Are you sure you got it when the div was closing, jim?

Barry said about 9 hours later:

I can repeat the bug. WinXp Pro – IE6 SP2. Stupid work computers.

zen said about 9 hours later:

Jaxzin is right… if you rollout then over again before the timout.. the div hangs and stays open. Give it a try… Tested on Mac 0SX : opera 9, camino 1.02 and firefox 1.5.4

sorry! Otherwise great work.

ps. you may have to create an event for rollback that resets the sequence!

Jon said about 9 hours later:

I confirm jaxzin’s bug on XP IE6 SP2. Can’t see an easy way round it though.

adz said about 9 hours later:

neither could i, xp/firefox/sp2

Nachoes said about 9 hours later:

Bug also happens with FF 1.5.0.4 on WinXP SP2 Otherwise great idea

Juan said about 9 hours later:

Well, I can sort of repeat the bug. If you move the mouse over the link it won’t open again until you move if off and over again, and then it works fine. I wouldn’t call it a dealbreaker.

Terc said about 9 hours later:

jaxzin’s bug confirmed in firefox 1.5.0.4. I actually noticed before reading the comments. Still, not a serious problem, just mouse over again and it will work.

Andy said about 9 hours later:

I replicated with Firefox 1.5.0.4 OS X. I think it is a pretty good execution that will survive even with this “bug”. Although I think the delay is a little long, especially if you use this for menus. I find quick transfer between sliding menus to cumbersome for this long delay. Great effort though.

Kordless said about 9 hours later:

Same “bug” in Safari, and your div is covering up the last line of the text too.

ID34 said about 9 hours later:

This is really smart!

soundmage said about 9 hours later:

not really a bug…insofar as that the code doesn’t contain errors that cause this. It’s not perfect, but I do like the idea. I feel like the delay is sliiightly too long, myself, but again…I really like the idea.

Jack Stevens said about 9 hours later:

Your little javascript is like a asshole. It tastes and looks like it. I could actually taste how bad this was.

Derrick said about 9 hours later:

This is a little different from the Amazon version. It doesn’t move the other content, but is rather an relatively positioned div.

Bara said about 9 hours later:

You should make the “Roll Over Me” clickable and open the box without having to wait, for those of us who are impatient or “on the go”.

Martin said about 9 hours later:

The proper way is “no delay before showing it, delay before closing it”. No annoying wait-time to see the roll-out and still protection against the shaky hand.

Dean said about 9 hours later:

I was able to duplicate it as follows, Jim:

Roll over the text and then roll off of the text. You have to be quick now… as soon as the div begins to close roll over the text again. It does not re-open the div.

Tested using XP Pro w/ IE6 SP2.

BKDotCom said about 9 hours later:

I challenge someone to use this similar technique to create a nested vertical “explorer like” menu system. (ie mouseover/ not onclick). I got close, but always had a minor bug that made it unuseable.

PJ Hyett said about 10 hours later:

Derrick, as I mentioned in the post, you can adapt this script to work with any sort of div you chose. Making a popup div appear instead of a drop down is a matter of changing the stylesheet.

Thanks for pointing out the bug guys. I think part of the issue may be how scriptaculous queues up its effects, so it could be stacking up and getting confused if you time your mouse movements just right. The fix may be as simple as just extending or shortening the timeouts.

Also, if people don’t like the bloat of scriptaculous, this code works fine with the moo.fx script as well. setTimeout is a built-in Javascript function, so you can use it with anything.

Shade said about 10 hours later:

I was able to get another “bug” to happen, in so far as it’s really just a lack of consideration in the design of the code. probably all these are not “bugs” per se but just unexpected (only to user, not developer) behavior that can be changed.

anyway, what i found was rollover the text, then right before it starts to show it, roll off text (upward)... div shows up, but since you’re already off both text and div, no “rollout” event occurs, and div stays open.

IE6 SP1.

NotQuiteJack said about 10 hours later:

Awesome – this would be really useful for tooltips / previews.

Replication Guide said about 10 hours later:

This is very cool. I could see this being very useful for mobile devices where the screen size is very limited. Good work!

z said about 11 hours later:

this is a great idea, and is exactly what i needed for one of my projects. thank you!!

CoreDumped said about 12 hours later:

nice effect.

I cannot reproduce the “bug”

WinXP SP2 FF1.5

SoulFrost said about 13 hours later:

This code is very cool. The above bugs dont occur at all when I am trying. I’m using firefox 1.5.0.4.

James said about 13 hours later:

Who cares about a simple bug? This is a simple idea, cleanly implemented, that definitely deals with some of the issues current Javascript rollovers have (especially menus).

Se[BBB]e said about 13 hours later:

The bug also happens on Firefox 1.5.0.4 on Ubuntu 6.06

Eric said about 13 hours later:

Very cool, BUT there’s no way I can figure out how to make more than one per page, and it must be called rollit, not rollit2 or something else…. What gives? How can we get a few of these puppies on a page…?

BJ said about 14 hours later:

How can you do this with an onclick instead of Rollit

BJ said about 14 hours later:

I guess like this. Roll over Me

BJ said about 14 hours later:

{a href=”#” onclick=”RollIt.hidePopup(‘rollit’)” onmouseout=”RollIt.hidePopup(‘rollit’)” onmouseover=”RollIt.showPopup(‘rollit’)”>Roll over Me

Ed Knittel said about 15 hours later: @Eric (and PJ) Re: multiple instances per page The function needs to be updated as follows -
var RollIt = {
    timeout : null,
    showPopup : function(e){
        clearTimeout(this.timeout);
        if($(e).style.display == 'none'){
            this.timeout = setTimeout(function(){new Effect.BlindDown(e, {duration:.3, fps:40})},400);
        }
    },
    hidePopup : function(e){
        if($(e).style.display == 'none'){
            clearTimeout(this.timeout);
        }else{
            this.timeout = setTimeout(function(){new Effect.BlindUp(e, {duration:.3, fps:40})},300);
        }
    }    
}
Then just make sure your divs have different IDs. Example: rollit, rollit2, applesauce, etc.
Martin Bialasinski said about 15 hours later:

The effect everyone sees is easily explained.

mouse over -> blind down mouse out -> blind up Now, while it is moving up, move the mouse over the link again -> mouse over

In the mouse over handler, we have

if($(‘rollit’).style.display == ‘none’){ // do the timeout

But during the blind up, this condition is false. Therefore, the timeout is not scheduled.

Result: the blind up completes, the mouse moved onto the link without triggering a new blind down.

You now have to move the mouse out of the link and onto the link again to trigger another mouse over event.

A possible fix is to set a flag on mouse over and delete it on mouse out. When the blind up finishes, check if the flag is set and if it is, trigger a blind down.

Eric said about 16 hours later:

Ed Knittel- Thanks! That worked perfectly….. Love this….

RMM said about 17 hours later:

the address: tooltip gets in the way

Alex said about 17 hours later:

Is it possible to create an image for the backgrounds? I’m sure it is, but would you have to mess with the javascript much?

Uncool said about 21 hours later:

Yeah, gotta say, nice scripting effect, and shut up nick ;)

Joe said about 22 hours later:

WORKS GREAT… the problem to moving back before closes.. then won’t re-open ... WORKS FINENOT A PROBLEM ON A REAL BROWSERWORKS PERFECTLY WITH FIREFOX!!!! What do you expect from Internet EXPLODER!!

Paul said about 23 hours later:

The 2.0 bubble is about to blow. Are you kidding me?

Picky said about 23 hours later:

Joe, it makes sense that the OnMouseOver event will not be raised again once it already has been, without a good reason (the user freshly moving their mouse OVER the element, again, which does work by the way). There is nothing crazy about it and /shockingly/ works as expected.

B83s said 1 day later:

@Jaxin “Found a bug: ” the only bug i can spot is that you are using IE ;)

Nice feature!!!!

web design uk said 1 day later:

This rules! I had been doing a bulky workaround in the past. Long live prototype (and effects frameworks)!

Srđan Prodanović said 1 day later:

Nice, but you should probably use scriptaculous queues to get rid of the IE bug. Also, code stays pretty with the prototype function Element.visible(e):Boolean. To remain unobtrusive, use Behaviour or the new wrapper in Prototype. As a usability claim, I’d advise to have the timeout on RollUp only. The requested content should appear as fast as possible. Only once the user doesn’t care about it, you can play with the presentation. Keep it up.

bob said 1 day later:

this is so cool, I will use is on my website

Chris said 1 day later:

So, ‘no worky’ with IE 7 Beta 3. But, I can’t get it to work in FF 1.5.0.4 or Opera 9 either. I could be implementing the script incorrect, but I have prototype.js & effects.js referenced and I copied all of the script into the page… I’m at a loss. What am I doing wrong?

Neil said 1 day later:

So it doesn’t appear to work with anything but inline styles, is that just me?

James said 1 day later:

It’s crap, this could be made without mixing XHTML and JavaScript together. Rookie :p

Kiara said 1 day later:

The new Yahoo already implemented it.

John "Z-Bo" Zabroski said 2 days later:

This effect looks terrible in my web browser (Firefox 1.0.8) and it’s not exactly a good UI feature, either. Rule of thumb: Do the least suprising thing.

Thumbs down.

Drakas said 3 days later:

Works smoothly in Opera 9! Where do prototype.js and effects.js come from?

Mail me ( i-am @ wykis . com )

Derrick Liu said 3 days later:

I am trying to implement this on my website. It doesn’t work. I put the code into the specified area, and then fiddled around a bit changing the color and what not.

Derrick Liu said 3 days later: This is my code.
<script type="text/javascript">
var RollIt = {
    timeout : null,
    showPopup : function(){
        clearTimeout(this.timeout);
        if($('rollit').style.display == 'none'){
            this.timeout = setTimeout(function(){new Effect.BlindDown('rollit', {duration:.3, fps:40})},400);
        }
    },
    hidePopup : function(){
        if($('rollit').style.display == 'none'){
            clearTimeout(this.timeout);
        }else{
            this.timeout = setTimeout(function(){new Effect.BlindUp('rollit', {duration:.3, fps:40})},300);
        }
    }    
}
</script>

<div style="padding: 10px; background: #a0a0a0;">
<a href="#" onclick="RollIt.hidePopup('rollit')" onmouseout="RollIt.hidePopup('rollit')" onmouseover="RollIt.showPopup('rollit')">Technical Information</a>
</div>

<div id="rollit" style="display:none; background: #707070;" onmouseout="RollIt.hidePopup('rollit')" onmouseover="RollIt.showPopup('rollit')"> 

<h3 style="margin: 0;">Bugs</h3>
If your perl installation is not located at /usr/bin/perl, this script may need some tweaking for you. Simply edit the first line of the cgi to your perl path.

Please contact me at <a href="&#120;&#101;&#110;&#116;&#114;&#105;&#111;&#110;&#064;&#103;&#109;&#097;&#105;&#108;&#046;&#099;&#111;&#109;">&#120;&#101;&#110;&#116;&#114;&#105;&#111;&#110;&#064;&#103;&#109;&#097;&#105;&#108;&#046;&#099;&#111;&#109;</a>
</div>

<div style="background: #b0b0b0; padding: 10px;"></div>
Derrick Liu said 3 days later:

Never mind. I fixed it.

JDF said 8 days later:

I tried setting this script up and couldn’t get it to work at all. Copied the js files over and added the Script tag to reference.. Very weird..

the_JinX said 12 days later:

Cool effect.. might use it in one of my websites..

JX said 14 days later:

Cool but looks like no-one has a fix for the stuck rollover problem.

M said 15 days later:

Right, this is well cool – not too much but cool enough to make a site look extra slick. however I have implemented it but notice that in IE my site when the blindup is finished there is a strange snapping effect where the div appear in full before being turned off very quickly. This doesn’t appear in other browsers, and IE doesn’t do it on the one at the top of the site. I am using prototype.js and effects.js can anyone suggest what i’ve missed. Also if I copy this pages code and place it next to the js files and remove all the cookie stuff it does it to. Any Ideas I’d love to hear.

fueg0 said 16 days later:

I was thinking… and if the user doesn’t have javascript? If this effect has been used on a menu and the user as not javascript… What happens? Well, nothing happens. And this way the user that doesn’t have javascript enabled can’t see the menu. So, for me, this is not a good solution if doesn’t have a “lighter” version for old browsers or people with special needs.

sjs said 9 months later:

Is there a means to dynamically bring contents while mouse over?

Harel Seligmann said 11 months later:

Managed to fix the bug for when one div is open and then you move to a second div straight away and first one doesn’t close.

I have called each of my divs rollit_(unique name) e.g. rollit_RM, rollit_cm

What I then do is when one is mousing over a div, it closes any other ones which may be open. So the code is as follows: var RollIt = { timeout : null, showPopup : function(e){ var all=document.getElementsByTagName(”*”); for(var i=0; i<all.length; i++) { rollit_array=all[i].id.split(”_”); if (rollit_array0 == “rollit”) { }

if (rollit_array[0] + "_" + rollit_array[1] != e)
                    {
                    Effect.BlindUp(rollit_array[0] + "_" + rollit_array[1], {duration:.3, fps:40});                        
                    }
                }
            }
clearTimeout(this.timeout);         
if($(e).style.display == 'none')
{
   this.timeout = setTimeout(function(){new Effect.BlindDown(e, {duration:.2, fps:40})},100);
}
},     
hidePopup : function(e){         
    if($(e).style.display == 'none'){       
          clearTimeout(this.timeout);         
    }else{          
          this.timeout = setTimeout(function(){new Effect.BlindUp(e, {duration:.2, fps:40})},200);         
    }     
}
Bill said about 1 year later:

Is there a way to do this with a click on, and click off? I like the mouseover, but in showing this feature used in a smaller table, I noticed that some users didn’t keep their mouse in the right area and the drop down faded. I was then asked if it’d be possible to just make it a click on and click off, which sounds like a good idea for my particular situation.

Would anyone be able to point me to code that accomplishes this?

Name
Url