I originally wanted to create a tooltips effect that combines floating positioning and mouse following, but I found that positioning and mouse following are still different in some key places, so we should do it separately.
This effect itself is not very difficult. We mainly put some effort into the program structure and expansion to make it more convenient to use and can be used in more places.
Program features
Complete example download (click to download)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" " http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd ">
<html xmlns=" http://www.w3.org/1999/xhtml ">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312" />
<title>JavaScript floating positioning prompt effect</title>
<script>
var $$ = function (id) {
return "string" == typeof id ? document.getElementById(id) : id;
};
var isIE = navigator.userAgent.indexOf('MSIE') != -1;
var isIE6 = isIE && ([/MSIE (d).0/i.exec(navigator.userAgent)][0][1] == "6");
var isChrome = navigator.userAgent.indexOf('Chrome') != -1;
var isSafari = navigator.userAgent.indexOf('AppleWebKit') != -1;
// with input from Tino Zijdel, Matthias Miller, Diego Perini
// http://dean.edwards.name/weblog/2005/10/add-event/
function addEvent(element, type, handler) {
if (element.addEventListener) {
element.addEventListener(type, handler, false);
} else {
if (!handler.$$guid) handler.$$guid = addEvent.guid++;
if (!element.events) element.events = {};
var handlers = element.events[type];
if (!handlers) {
handlers = element.events[type] = {};
if (element["on" + type]) {
handlers[0] = element["on" + type];
}
}
handlers[handler.$$guid] = handler;
element["on" + type] = handleEvent;
}
};
addEvent.guid = 1;
function removeEvent(element, type, handler) {
if (element.removeEventListener) {
element.removeEventListener(type, handler, false);
} else {
if (element.events && element.events[type]) {
delete element.events[type][handler.$$guid];
}
}
};
function handleEvent(event) {
var returnValue = true;
event = event || fixEvent(((this.ownerDocument || this.document || this).parentWindow || window).event);
var handlers = this.events[event.type];
for (var i in handlers) {
this.$$handleEvent = handlers[i];
if (this.$$handleEvent(event) === false) {
returnValue = false;
}
}
return returnValue;
};
function fixEvent(event) {
event.target = event.srcElement;
if(event.type == "mouseout") {
event.relatedTarget = event.toElement;
}else if(event.type == "mouseover") {
event.relatedTarget = event.fromElement;
}
return event;
};
var Extend = function(destination, source) {
for (var property in source) {
destination[property] = source[property];
}
return destination;
}
var Contains = function(a, b){
return a.contains ? a != b && a.contains(b) : !!(a.compareDocumentPosition(b) & 16);
}
var Bind = function(object, fun) {
var args = Array.prototype.slice.call(arguments, 2);
return function() {
return fun.apply(object, args.concat(Array.prototype.slice.call(arguments)));
}
}
var BindAsEventListener = function(object, fun) {
var args = Array.prototype.slice.call(arguments, 2);
return function(event) {
return fun.apply(object, [event].concat(args));
}
}
var FixedTips = function(tip, options){
this.Tip = $$(tip);//Prompt box
this._trigger = null;//Trigger object
this._timer = null;//Timer
this._cssTip = this.Tip.style;//Simplified code
this._onshow = false;//Record the current display status
this.SetOptions(options);
//Process Tip object
this._cssTip.margin = 0;//Avoid positioning problems
this._cssTip.position = "absolute";
this._cssTip.visibility = "hidden";
this._cssTip.display = "block";
this._cssTip.zIndex = 99;
this._cssTip.left = this._cssTip.top = "-9999px";//Avoid scroll bars in the placeholder
//offset correction parameters
var iLeft = iTop = 0, p = this.Tip;
while (p.offsetParent) {
p = p.offsetParent; iLeft += p.offsetLeft; iTop += p.offsetTop;
};
this._offsetleft = iLeft;
this._offsettop = iTop;
//Keep displaying when moving into Tip object
addEvent(this.Tip, "mouseover", BindAsEventListener(this, function(e){
//If an external element enters, it means that it is currently in the hiding delay stage, then clear the timer to cancel the hiding.
this.Check(e.relatedTarget) && clearTimeout(this._timer);
}));
//ie6 handles select
if (isIE6) {
this._iframe = document.createElement("<iframe style='position:absolute;filter:alpha(opacity=0);display:none;'>");
document.body.insertBefore(this._iframe, document.body.childNodes[0]);
};
//Used to hide by clicking
this._fCH = BindAsEventListener(this, function(e) {
if (this.Check(e.target) && this.CheckHide()) {
this.ReadyHide(this._trigger.ClickHideDelay);
};
});
//Used to trigger the method to hide
this._fTH = BindAsEventListener(this, function(e) {
if (this.Check(e.relatedTarget) && this.CheckHide()) {
this.ReadyHide(this._trigger.TouchHideDelay);
};
});
};
FixedTips.prototype = {
_doc: document.documentElement,//Simplify the code
//Set default properties
SetOptions: function(options) {
this.options = {//Default value
ClickShow: true,//Whether to display in click mode
ClickShowDelay: false,//Whether the click display delay
ClickHide: true,//Whether the click mode is hidden
ClickHideDelay: false,//Whether to click to hide the delay
TouchShow: true,//Whether the trigger mode is displayed
TouchShowDelay: true,//Whether to trigger the display delay
TouchHide: true,//Whether the trigger mode is hidden
TouchHideDelay: true,//Whether to trigger the hide delay
ShowDelay: 300,//Display delay time
HideDelay: 300,//Hide delay time
Align: "clientleft",//Horizontal positioning
vAlign: "clienttop",//vertical positioning
Custom: { left: 0, top: 0 },//Custom positioning
Percent: { left: 0, top: 0 },//Customized percentage positioning
Adaptive: false,//Whether to adaptive positioning
Reset: false,//Whether to reposition during adaptive positioning
onShow: function(){},//Execute when displaying
onHide: function(){}//Executed when hidden
};
Extend(this.options, options || {});
},
//Check the trigger element
Check: function(elem) {
//Return whether it is an external element (that is, an element object other than the trigger element and the Tip object itself and its internal elements)
return !this._trigger ||
!(
this.Tip === elem || this._trigger.Elem === elem ||
Contains(this.Tip, elem) || Contains(this._trigger.Elem, elem)
);
},
//Ready to display
ReadyShow: function(delay) {
clearTimeout(this._timer);
var trigger = this._trigger;
//Click to hide
trigger.ClickHide && addEvent(document, "click", this._fCH);
//Trigger mode hidden
trigger.TouchHide && addEvent(this._trigger.Elem, "mouseout", this._fTH);
//Whether to delay triggering
if (delay) {
this._timer = setTimeout(Bind(this, this.Show), trigger.ShowDelay);
} else { this.Show(); };
},
//show
Show: function() {
clearTimeout(this._timer);
this._trigger.onShow();//Put it in front for easy modification of attributes
//Calculate left and top based on preset positioning and custom positioning
var trigger = this._trigger, rect = trigger.Elem.getBoundingClientRect(),
scrolldoc = isChrome || isSafari ? document.body : this._doc,
scrollLeft = scrolldoc.scrollLeft, scrollTop = scrolldoc.scrollTop,
customLeft = trigger.Custom.left, customTop = trigger.Custom.top,
iLeft = this.GetLeft(rect, trigger.Align) + customLeft,
iTop = this.GetTop(rect, trigger.vAlign) + customTop;
//Custom percentage positioning
if (trigger.Percent.left) { iLeft += .01 * trigger.Percent.left * trigger.Elem.offsetWidth; };
if (trigger.Percent.top) { iTop += .01 * trigger.Percent.top * trigger.Elem.offsetHeight; };
//Adaptive window positioning
if (trigger.Adaptive) {
//Correct positioning parameters
var maxLeft = this._doc.clientWidth - this.Tip.offsetWidth,
maxTop = this._doc.clientHeight - this.Tip.offsetHeight;
if (trigger.Reset) {
//Automatic repositioning
if (iLeft > maxLeft || iLeft < 0) {
iLeft = this.GetLeft(rect, 2 * iLeft > maxLeft ? "left" : "right") + customLeft;
};
if (iTop > maxTop || iTop < 0) {
iTop = this.GetTop(rect, 2 * iTop > maxTop ? "top" : "bottom") + customTop;
};
} else {
//Correct to appropriate position
iLeft = Math.max(Math.min(iLeft, maxLeft), 0);
iTop = Math.max(Math.min(iTop, maxTop), 0);
};
};
//Set position and display
this._cssTip.left = iLeft + scrollLeft - this._offsetleft + "px";
this._cssTip.top = iTop + scrollTop - this._offsettop + "px";
this._cssTip.visibility = "visible";
//ie6 handles select
if (isIE6) {
this._iframe.style.left = iLeft + scrollLeft + "px";
this._iframe.style.top = iTop + scrollTop + "px";
this._iframe.style.width = this.Tip.offsetWidth + "px";
this._iframe.style.height = this.Tip.offsetHeight + "px";
this._iframe.style.display = "";
};
//Trigger mode hidden
trigger.TouchHide && addEvent(this.Tip, "mouseout", this._fTH);
},
//Get the left of the relative trigger element
GetLeft: function(rect, align) {
switch (align.toLowerCase()) {
case "left" :
return rect.left - this.Tip.offsetWidth;
case "clientleft" :
return rect.left;
case "center" :
return (rect.left + rect.right - this.Tip.offsetWidth)/2;
case "clientright" :
return rect.right - this.Tip.offsetWidth;
case "right" :
default :
return rect.right;
};
},
//Get the top relative to the trigger element
GetTop: function(rect, valign) {
switch (valign.toLowerCase()) {
case "top" :
return rect.top - this.Tip.offsetHeight;
case "clienttop" :
return rect.top;
case "center" :
return (rect.top + rect.bottom - this.Tip.offsetHeight)/2;
case "clientbottom" :
return rect.bottom - this.Tip.offsetHeight;
case "bottom" :
default :
return rect.bottom;
};
},
//Prepare to hide
ReadyHide: function(delay) {
clearTimeout(this._timer);
if (delay) {
this._timer = setTimeout(Bind(this, this.Hide), this._trigger.HideDelay);
} else { this.Hide(); };
},
//hide
Hide: function() {
clearTimeout(this._timer);
//Set up to hide
this._cssTip.visibility = "hidden";
this._cssTip.left = this._cssTip.top = "-9999px";
//ie6 handles select
if (isIE6) { this._iframe.style.display = "none"; };
//Process trigger object
if (!!this._trigger) {
this._trigger.onHide();
removeEvent(this._trigger.Elem, "mouseout", this._fTH);
}
this._trigger = null;
//remove event
removeEvent(this.Tip, "mouseout", this._fTH);
removeEvent(document, "click", this._fCH);
},
//Add trigger object
Add: function(elem, options) {
//Create a trigger object
var elem = $$(elem), trigger = Extend(Extend({ Elem: elem }, this.options), options || {});
//Click to display
addEvent(elem, "click", BindAsEventListener(this, function(e){
if (trigger.ClickShow) {
if (this.CheckShow(trigger)) {
this.ReadyShow(trigger.ClickShowDelay);
} else {
clearTimeout(this._timer);
};
};
}));
//Trigger mode display
addEvent(elem, "mouseover", BindAsEventListener(this, function(e){
if (trigger.TouchShow) {
if (this.CheckShow(trigger)) {
this.ReadyShow(trigger.TouchShowDelay);
} else if (this.Check(e.relatedTarget)) {
clearTimeout(this._timer);
};
};
}));
//return trigger object
return trigger;
},
//Show check
CheckShow: function(trigger) {
if (trigger !== this._trigger) {
//If it is not the same trigger object, execute Hide first to prevent conflicts.
this.Hide(); this._trigger = trigger; return true;
} else { return false; };
},
//hide check
CheckHide: function() {
if (this._cssTip.visibility === "hidden") {
//It is originally a hidden state, no need to execute Hide anymore
clearTimeout(this._timer);
removeEvent(this._trigger.Elem, "mouseout", this._fTH);
this._trigger = null;
removeEvent(document, "click", this._fCH);
return false;
} else { return true; };
}
};
</script>
</head>
<body>
<style>
.trigger{border:1px solid #003099; color:#003099; background:#e2e7ff; padding:10px; width:200px; height:100px; margin-left:150px;}
.tip{border:1px solid #c00000; color:#c00000; background:#ffcccc; padding:5px; line-height:20px;}
</style>
<div style="padding:50px;">
<div id="idTip" class="tip"></div>
<div id="idTrigger1" class="trigger">
<select>
<option>test</option>
</select>
</div>
<br>
horizontal position:
<label>
<input name="nAlign" type="radio" value="left" />
left </label>
<label>
<input name="nAlign" type="radio" value="clientleft" />
clientleft </label>
<label>
<input name="nAlign" type="radio" value="center" />
center </label>
<label>
<input name="nAlign" type="radio" value="clientright" />
clientright </label>
<label>
<input name="nAlign" type="radio" value="right" checked="checked" />
right </label>
<br>
Vertical position:
<label>
<input name="nVAlign" type="radio" value="top" />
top </label>
<label>
<input name="nVAlign" type="radio" value="clienttop" />
clienttop </label>
<label>
<input name="nVAlign" type="radio" value="center" />
center </label>
<label>
<input name="nVAlign" type="radio" value="clientbottom" />
clientbottom </label>
<label>
<input name="nVAlign" type="radio" value="bottom" checked="checked" />
bottom </label>
<br>
<br>
Custom targeting:
left:
<input id="idLeft" type="text" size="5" value="0" maxlength="3" />
top:
<input id="idTop" type="text" size="5" value="0" maxlength="3"/>
<br>
<br>
<input id="idClick" type="checkbox" checked="checked" />
<label for="idClick">Click method</label>
<input id="idTouch" type="checkbox" checked="checked" />
<label for="idTouch">Trigger method</label>
<br>
<br>
Delay time:
<input id="idDelayTime" type="text" size="5" value="0" maxlength="4"/>
<input id="idDelay" type="button" value="Cancel delay" />
<br>
<br>
Other application examples: <br>
<br>
<div id="idTest1"> Use title: <a href=" http://www.cnblogs.com/cloudgamer/archive/2008/11/17/1334778.html " title="Drag and drop effect"> Drag and drop Effect</a> <a href=" http://www.cnblogs.com/cloudgamer/archive/2008/12/03/1346386.html " title="Drag and Zoom Effect"> Drag and Zoom Effect</a > <a href=" http://www.cnblogs.com/cloudgamer/archive/2008/07/21/1247267.html " title="Picture cutting effect"> Picture cutting effect</a> </div >
<br>
<br>
Popular avatar display effects: <br>
<br>
<div id="idTest2"> <a href=" http://www.cnblogs.com/cloudgamer/archive/2008/07/06/1236770.html " title="Picture sliding switching effect"> <img src= "/articleimg/2009/07/6852/r_mx1.jpg" border="0"/></a> <a href=" http://www.cnblogs.com/cloudgamer/archive/2008/05/ 23/1205642.html " title="Picture transformation effect"><img src="/articleimg/2009/07/6852/r_mx2.jpg" border="0"/></a> <a href=" http://www.cnblogs.com/cloudgamer/archive/2008/05/13/1194272.html " title="Picture sliding display effect"><img src="/articleimg/2009/07/6852/r_mx3.jpg " border="0"/></a> </div>
<br>
<br>
Close button: <a id="idTest3" href=" http://www.cnblogs.com/cloudgamer/archive/2009/05/18/TableFixed.html">Table row positioning effect</a>
</div>
<script>
var forEach = function(array, callback, thisObject){
if(array.forEach){
array.forEach(callback, thisObject);
}else{
for (var i = 0, len = array.length; i < len; i++) { callback.call(thisObject, array[i], i, array); }
}
}
//////////////////////////////////////////
var ft = new FixedTips("idTip");
/////////////////////////////////////////
var trigger1 = ft.Add("idTrigger1", {
onShow: function(){
//positioning test
var sAlign = this.Align, sVAlign = this.vAlign;
forEach(document.getElementsByName("nAlign"), function(o){ if(o.checked){ sAlign = o.value; } });
forEach(document.getElementsByName("nVAlign"), function(o){ if(o.checked){ sVAlign = o.value; } });
this.Align = sAlign;
this.vAlign = sVAlign;
this.Custom.left = $$("idLeft").value | 0;
this.Custom.top = $$("idTop").value | 0;
trigger1.ShowDelay = trigger1.HideDelay = $$("idDelayTime").value | 0 || 300;
ft.Tip.innerHTML = sAlign + "<br>" + sVAlign + "<br>" + "left: " + this.Custom.left + ", top: " + this.Custom.top;
}
});
//delay test
$$("idDelayTime").value = trigger1.ShowDelay;
$$("idDelay").onclick = function(){
if(trigger1.TouchShowDelay){
trigger1.ClickShowDelay = trigger1.ClickHideDelay =
trigger1.TouchShowDelay = trigger1.TouchHideDelay = false;
$$("idDelayTime").disabled = true;
this.value = "Set delay";
}else{
trigger1.ClickShowDelay = trigger1.ClickHideDelay =
trigger1.TouchShowDelay = trigger1.TouchHideDelay = true;
$$("idDelayTime").disabled = false;
this.value = "Cancel delay";
}
}
//method test
$$("idClick").onclick = function(){
trigger1.ClickShow = trigger1.ClickHide = this.checked;
}
$$("idTouch").onclick = function(){
trigger1.TouchShow = trigger1.TouchHide = this.checked;
}
//////////////////////////////////////////
forEach($$("idTest1").getElementsByTagName("a"), function(o){
var title = o.title; o.title = "";
ft.Add(o, { vAlign: "bottom", Percent: { left: 50, top: 0 }, onShow: function(){ ft.Tip.innerHTML = title; } });
})
//////////////////////////////////////////
forEach($$("idTest2").getElementsByTagName("a"), function(o){
var img = o.getElementsByTagName("img")[0], title = o.title;
o.title = "";
ft.Add(img, { Custom: { left: -6, top: -6 },
onShow: function(){
var str = '<a href="' + o.href + '"><img src="' + img.src + '" style="padding-bottom:5px;" border="0"/></ a>';
str += '<br /><a href="' + o.href + '">' + title + '</a>';
ft.Tip.innerHTML = str;
}
});
})
//////////////////////////////////////////
ft.Add("idTest3", { ClickHide: false, TouchHide: false, Align: "right",
onShow: function(){
var str = ' <a href=" http://www.cnblogs.com/cloudgamer/archive/2009/03/11/1408333.html "> Color gradient and gradient effects</a><br />';
str += ' <a href=" http://www.cnblogs.com/cloudgamer/archive/2008/10/20/1314766.html "> Imitation 163 network disk no refresh file upload system</a><br / >';
str += '<input type="button" onclick="ft.Hide();" value="Click to close" />';
ft.Tip.innerHTML = str;
}
});
</script>
</body>
</html>
JavaScript, positioning, floating, tips, tooltips, FixedTips, Tip
Program Description
Tip object :
The Tip object is a container used to display prompt information, and the program is represented by the Tip attribute. There are no requirements for this, some settings will be made to it when the program is initialized.
First make the following settings:
this._cssTip.margin = 0;
this._cssTip.position = "absolute";
this._cssTip.visibility = "hidden";
this._cssTip.display = "block";
this._cssTip.zIndex = 99;
this._cssTip.left = this._cssTip.top = "-9999px";
The margin is set to 0 to avoid some positioning problems. Visibility is used to hide instead of display because the program needs to obtain the offsetWidth and offsetHeight of the Tip. It also needs to set left and top to avoid the scroll bar appearing due to the Tip occupying the space.
Because Tip may be inside other positioned elements, two offset correction parameters must be set:
var iLeft = iTop = 0, p = this.Tip.offsetParent;
while (!(p === document.body || p === document.documentElement)) {
iLeft += p.offsetLeft; iTop += p.offsetTop; p = p.offsetParent;
};
this._offsetleft = iLeft;
this._offsettop = iTop;
Finally, add an event to Tip's mouseover, which will be explained later.