Resolving mouseover/mouseout multi-trigger problem
Background
While I was developing my blog's comments function, I hope reader can write down their Gravatar email address, and my preferable UI was an "hint icon" dispolayed desides the Email textbox, so that while reader mouse overs it an overlay box will show the reason why Email is required, in additional, I want to add a little bit fancier animation:
- When user's mouse hovers on the icon, show the div box from small to large, once reaches predefined size, fades in the hint sentente.
- When mouse out the icon, fades out the hint sentence and then hides the div from large to small.
My requirement seems easy, however, I did spend two whole days in making it perfect, so I write down my investigation path here.
The problems I faced
My first thoughts was definitely simple, subscribe the onmouseover/onmouseout event for the hint icon, and setinterval the size animation, once finished, fades in the texts.
That's fine! But I soon encountered one problem which is easy to trigger: mouse keeps on overing/outing the icon quickly (as long as the interval is shorter than the sizing animation), it will cause the two event handlers (mouseover/mouseout) multi-triggered so that a infinite loop could be easily caused.
The multi-trigger problem I faced is not the one related with DOM event bubbling, which is well described in jQuery mouseover API.
Majority functions on my simple blog didn't use any third party JS library including jQuery, instead, I was trying to be a "rambo", just for improving my coding skills - by implementing various kinds of features and resolving various kinds of problems.
Overcoming this issue cost me one whole day:) I tried strategies listed below:
- Put global variables indicating the showing/hiding process - isShowing & isHiding.
- Clearing oppositeinterval handle as soon as over/out event was triggered.
- Inspired by #2, unsubscribing oppotite event handler as soon as mouseover/mouseout was captured.
#1 does not solve the problem, I tried while multi-triggered, the interval cannot be cleared (not very sure the reason), #2 absolutely not work because it will stop unfinished animation. Finally I resolved the problem using #3, which I think it is the best one.
Implementation
The HTML
<div id="hintContainer">
<div id="hintIcon">
<img src="../Images/Hint.gif" alt="Email Hint">
</div>
<div id="CommentHint">
<div id="hintText">
Required (not shown), used only for displaying <strong>Gravatar</strong> and receiving
future notification when new comment(s) posted on this blog.
</div>
</div>
</div>
The CSS
<style type="text/css">
#hintContainer
{
width: 184px;
height: 80px;
}
#hintIcon
{
width: 16px;
height: 16px;
float: left;
cursor: pointer;
}
#CommentHint
{
position:absolute;
color: #fff;
width: 0;
height: 0;
background-color: black; /*#adc7ed*/
border: 1px dotted #666;
padding: 2px;
display: none;
}
#hintText
{
opacity: 0;
filter: Alpha(opacity=0);
}
</style>
The JavaScript
Foundation methods in my "rambo" library:
var $id = function (id) { return document.getElementById(id); }
function bindEvent(domObj, eventName, evtHandler, useCapture) {
if (domObj.addEventListener) {
domObj.addEventListener(eventName, evtHandler, useCapture ? useCapture : false);
}
else if (domObj.attachEvent) { // IE 8 and below
evtHandler.wrapper = function () {
evtHandler.call(domObj)
};
domObj.attachEvent("on" + eventName, evtHandler.wrapper);
}
}
function setSize(ele, width, height) {
ele.style.width = width + 'px';
ele.style.height = height + 'px';
}
function setPosition(ele, left, top) {
ele.style.left = left + 'px';
ele.style.top = top + 'px';
}
Animation methods:
var hintIcon = $id('hintIcon'), commentHint = $id('CommentHint'), hintText = $id('hintText');
bindEvent(hintIcon, "mouseover", showHint);
var totalFrames = 20, curFrame = 0;
var sliceWidth = 180 / totalFrames, sliceHeight = 80 / totalFrames;
var showHinthandler = hideHinthandler = undefined;
var offset = 10;
function showHint(evt) {
if (!evt)
evt = window.event;
// Unbind mouseover immediately to prevent multi-trigger
unbindEvent(this, "mouseover", showHint);
setPosition(commentHint, evt.clientX + offset, evt.clientY);
showElement(commentHint);
showElement(hintText);
showHinthandler = setInterval(function () {
//console.log("show: " + curFrame);
if (++curFrame <= totalFrames)
setSize(commentHint, sliceWidth * curFrame, sliceHeight * curFrame);
else {
clearInterval(showHinthandler);
curFrame = totalFrames;
// Fade in hint text
divFadeIn(hintText, 2, 10);
// Bind mouseout event back to the icon UNTIL finished showing
bindEvent(hintIcon, "mouseout", hideHint);
}
}, 10);
}
function hideHint(evt) {
if (!evt)
evt = window.event;
// Unbind mouseover immediately to prevent multi-trigger
unbindEvent(this, "mouseout", hideHint);
divFadeOut(hintText, 2, 10);
setTimeout(function () {
hideHinthandler = setInterval(function () {
hideElement(hintText);
console.log("hide: " + curFrame);
if (--curFrame >= 0) {
setSize(commentHint, sliceWidth * curFrame, sliceHeight * curFrame);
}
else {
clearInterval(hideHinthandler);
curFrame = 0;
hideElement(commentHint);
// Bind mouseover event back to the icon UNTIL finished hiding
bindEvent(hintIcon, "mouseover", showHint);
}
}, 10);
}, 800);
}
Conclusion
I guess this is a pretty good problem for interview, I am fool so I cannot resolve it in short period, however, I think good fronteers should solve it greatly:)
Leave a comment