当前位置:首页 > JavaScript浮动定位提示效果[附源码]

JavaScript浮动定位提示效果[附源码]

点击次数:1911  更新日期:2011-01-05

本来想做一个集合浮动定位和鼠标跟随的tooltips效果,但发现定位和鼠标跟随在一些关键的地方还是不同的,还是分开来吧。 
这个效果本身难度不大,主要在程序结构和扩展中下了些功夫,务求用起来更方便,能用在更多的地方。


程序特点




同一个提示框用在多个触发元素时,只需一个实例;
显示和隐藏分别有点击方式和触发方式选择;
能设置延时显示和隐藏;
有25种预设定位位置;
可在预设定位基础上,再自定义定位;
可设置自适应窗口定位;


代码:


<!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 浮动定位提示效果</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);//提示框


this._trigger = null;//触发对象
this._timer = null;//定时器
this._cssTip = this.Tip.style;//简化代码
this._onshow = false;//记录当前显示状态


this.SetOptions(options);
//处理Tip对象
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”;//避免占位出现滚动条
//offset修正参数
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;
//移入Tip对象时保持显示状态
addEvent(this.Tip, “mouseover”, BindAsEventListener(this, function(e){
//如果是外部元素进入,说明当前是隐藏延时阶段,那么清除定时器取消隐藏
this.Check(e.relatedTarget) && clearTimeout(this._timer);
}));
//ie6处理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]);
};
//用于点击方式隐藏
this._fCH = BindAsEventListener(this, function(e) {
if (this.Check(e.target) && this.CheckHide()) {
this.ReadyHide(this._trigger.ClickHideDelay);
};
});
//用于触发方式隐藏
this._fTH = BindAsEventListener(this, function(e) {
if (this.Check(e.relatedTarget) && this.CheckHide()) {
this.ReadyHide(this._trigger.TouchHideDelay);
};
});
};
FixedTips.prototype = {
_doc: document.documentElement,//简化代码
//设置默认属性
SetOptions: function(options) {
this.options = {//默认值
ClickShow: true,//是否点击方式显示
ClickShowDelay: false,//是否点击显示延时
ClickHide: true,//是否点击方式隐藏
ClickHideDelay: false,//是否点击隐藏延时
TouchShow: true,//是否触发方式显示
TouchShowDelay: true,//是否触发显示延时
TouchHide: true,//是否触发方式隐藏
TouchHideDelay: true,//是否触发隐藏延时
ShowDelay: 300,//显示延时时间
HideDelay: 300,//隐藏延时时间
Align: “clientleft”,//水平方向定位
vAlign: “clienttop”,//垂直方向定位
Custom: { left: 0, top: 0 },//自定义定位
Percent: { left: 0, top: 0 },//自定义百分比定位
Adaptive: false,//是否自适应定位
Reset: false,//自适应定位时是否重新定位
onShow: function(){},//显示时执行
onHide: function(){}//隐藏时执行
};
Extend(this.options, options || {});
},
//检查触发元素
Check: function(elem) {
//返回是否外部元素(即触发元素和Tip对象本身及其内部元素以外的元素对象)
return !this._trigger ||
!(
this.Tip === elem || this._trigger.Elem === elem ||
Contains(this.Tip, elem) || Contains(this._trigger.Elem, elem)
);
},
//准备显示
ReadyShow: function(delay) {
clearTimeout(this._timer);
var trigger = this._trigger;
//点击方式隐藏
trigger.ClickHide && addEvent(document, “click”, this._fCH);
//触发方式隐藏
trigger.TouchHide && addEvent(this._trigger.Elem, “mouseout”, this._fTH);
//是否延迟触发
if (delay) {
this._timer = setTimeout(Bind(this, this.Show), trigger.ShowDelay);
} else { this.Show(); };
},
//显示
Show: function() {
clearTimeout(this._timer);
this._trigger.onShow();//放在前面方便修改属性
//根据预设定位和自定义定位计算left和top
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;
//自定义百分比定位
if (trigger.Percent.left) { iLeft += .01 * trigger.Percent.left * trigger.Elem.offsetWidth; };
if (trigger.Percent.top) { iTop += .01 * trigger.Percent.top * trigger.Elem.offsetHeight; };
//自适应视窗定位
if (trigger.Adaptive) {
//修正定位参数
var maxLeft = this._doc.clientWidth – this.Tip.offsetWidth,
maxTop = this._doc.clientHeight – this.Tip.offsetHeight;
if (trigger.Reset) {
//自动重新定位
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 {
//修正到适合位置
iLeft = Math.max(Math.min(iLeft, maxLeft), 0);
iTop = Math.max(Math.min(iTop, maxTop), 0);
};
};
//设置位置并显示
this._cssTip.left = iLeft + scrollLeft – this._offsetleft + “px”;
this._cssTip.top = iTop + scrollTop – this._offsettop + “px”;
this._cssTip.visibility = “visible”;
//ie6处理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.TouchHide && addEvent(this.Tip, “mouseout”, this._fTH);
},
//获取相对触发元素的left
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;
};
},
//获取相对触发元素的top
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;
};
},
//准备隐藏
ReadyHide: function(delay) {
clearTimeout(this._timer);
if (delay) {
this._timer = setTimeout(Bind(this, this.Hide), this._trigger.HideDelay);
} else { this.Hide(); };
},
//隐藏
Hide: function() {
clearTimeout(this._timer);
//设置隐藏
this._cssTip.visibility = “hidden”;
this._cssTip.left = this._cssTip.top = “-9999px”;
//ie6处理select
if (isIE6) { this._iframe.style.display = “none”; };
//处理触发对象
if (!!this._trigger) {
this._trigger.onHide();
removeEvent(this._trigger.Elem, “mouseout”, this._fTH);
}
this._trigger = null;
//移除事件
removeEvent(this.Tip, “mouseout”, this._fTH);
removeEvent(document, “click”, this._fCH);
},
//添加触发对象
Add: function(elem, options) {
//创建一个触发对象
var elem = (elem), trigger = Extend(Extend({ Elem: elem }, this.options), options || {});
//点击方式显示
addEvent(elem, “click”, BindAsEventListener(this, function(e){
if (trigger.ClickShow) {
if (this.CheckShow(trigger)) {
this.ReadyShow(trigger.ClickShowDelay);
} else {
clearTimeout(this._timer);
};
};
}));
//触发方式显示
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;
},
//显示检查
CheckShow: function(trigger) {
if (trigger !== this._trigger) {
//不是同一个触发对象就先执行Hide防止冲突
this.Hide(); this._trigger = trigger; return true;
} else { return false; };
},
//隐藏检查
CheckHide: function() {
if (this._cssTip.visibility === “hidden”) {
//本来就是隐藏状态,不需要再执行Hide
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>
水平位置:
<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>
垂直位置:
<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>
自定义定位:
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”>点击方式</label>
<input id=”idTouch” type=”checkbox” checked=”checked” />
<label for=”idTouch”>触发方式</label>
<br>
<br>
延时时间:
<input id=”idDelayTime” type=”text” size=”5″ value=”0″ maxlength=”4″/>
<input id=”idDelay” type=”button” value=” 取消延时 ” />
<br>
<br>
其他应用范例: <br>
<br>
<div id=”idTest1″> 利用title: <a href=”http://www.cnblogs.com/cloudgamer/archive/2008/11/17/1334778.html” title=”拖放效果”>拖放效果</a> &nbsp; <a href=”http://www.cnblogs.com/cloudgamer/archive/2008/12/03/1346386.html” title=”拖拉缩放效果”>拖拉缩放效果</a> &nbsp; <a href=”http://www.cnblogs.com/cloudgamer/archive/2008/07/21/1247267.html” title=”图片切割效果”> 图片切割效果</a> </div>
<br>
<br>
流行的头像显示效果: <br>
<br>
<div id=”idTest2″> <a href=”http://www.cnblogs.com/cloudgamer/archive/2008/07/06/1236770.html” title=”图片滑动切换效果”><img src=”/upfiles/2009-7/2009772016942832.jpg” border=”0″/></a> &nbsp; <a href=”http://www.cnblogs.com/cloudgamer/archive/2008/05/23/1205642.html” title=”图片变换效果”><img src=”/upfiles/2009-7/2009772016963417.jpg” border=”0″/></a> &nbsp; <a href=”http://www.cnblogs.com/cloudgamer/archive/2008/05/13/1194272.html” title=”图片滑动展示效果”><img src=”/upfiles/2009-7/2009772016980181.jpg” border=”0″/></a> </div>
<br>
<br>
关闭按钮:<a id=”idTest3″ href=”http://www.cnblogs.com/cloudgamer/archive/2009/05/18/TableFixed.html”>Table行定位效果</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(){
//定位测试
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;
}
});
//延时测试
(“idDelayTime”).value = trigger1.ShowDelay;
(“idDelay”).onclick = function(){
if(trigger1.TouchShowDelay){
trigger1.ClickShowDelay = trigger1.ClickHideDelay =
trigger1.TouchShowDelay = trigger1.TouchHideDelay = false;
(“idDelayTime”).disabled = true;
this.value = ” 设置延时 “;
}else{
trigger1.ClickShowDelay = trigger1.ClickHideDelay =
trigger1.TouchShowDelay = trigger1.TouchHideDelay = true;
(“idDelayTime”).disabled = false;
this.value = ” 取消延时 “;
}
}
//方式测试
(“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”>颜色梯度和渐变效果</a><br />’;
str += ‘<a href=”http://www.cnblogs.com/cloudgamer/archive/2008/10/20/1314766.html”>仿163网盘无刷新文件上传系统</a><br />’;
str += ‘<input type=”button” onclick=”ft.Hide();” value=”点击关闭” />’;
ft.Tip.innerHTML = str;
}
});
</script>
</body>
</html>


JavaScript,定位,浮动,提示,tooltips,FixedTips,Tip
程序说明


Tip对象:


Tip对象就是用来显示提示信息的容器,程序用Tip属性表示。这个没什么要求,程序初始化时会对它进行一些设置。 
首先进行下面设置:


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”;


其中margin设为0是为了避免一些定位问题,用visibility来隐藏而不是display是因为程序需要获取Tip的offsetWidth、offsetHeight,还需要设置left和top是避免因Tip占位出现的滚动条。 
因为Tip可能会在其他定位元素里面,所以还要设两个offset修正参数:


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;


最后给Tip的mouseover加一个事件,具体后面再说明。


触发对象:


由于很多情况下都是一个Tip对应多个地方的提示,所以程序参考了 Table排序 的方式,添加了一个Add方法。 
一个Tip实例化后,再用Add方法就可以对多个触发元素分别添加触发对象,程序中用_trigger属性表示当前的触发对象。 
Add方法的一个必要参数是触发元素,就是触发显示Tip的元素。 
需要的话还可以用options参数,来自定义触发对象的属性,包括:


属性: 默认值//说明 
ClickShow: true,//是否点击方式显示 
ClickShowDelay: false,//是否点击显示延时 
ClickHide: true,//是否点击方式隐藏 
ClickHideDelay: false,//是否点击隐藏延时 
TouchShow: true,//是否触发方式显示 
TouchShowDelay: true,//是否触发显示延时 
TouchHide: true,//是否触发方式隐藏 
TouchHideDelay: true,//是否触发隐藏延时 
ShowDelay: 300,//显示延时时间 
HideDelay: 300,//隐藏延时时间 
vAlign: “clienttop”,//垂直方向定位 
Align: “left”,//水平方向定位 
Custom: { left: 0, top: 0 },//自定义定位 
Percent: { left: 0, top: 0 },//自定义百分比定位 
Adaptive: true,//是否自适应定位 
onShow: function(){},//显示时执行 
onHide: function(){}//隐藏时执行


具体作用后面再说明,可以在程序初始化时修改这些默认值。 
一个经典应用是在onShow中把Tip修改为各个触发对象对应的内容。 
此外还有Elem属性保存触发元素。


显示和隐藏:


提示效果的一个重点就是显示和隐藏提示信息。程序是通过设置Tip的visibility是否hidden来显示和隐藏Tip的。 
具体的显示和隐藏程序分别在Show和Hide程序中,还有ReadyShow和ReadyHide程序,主要用来处理延时。


这种提示效果的一个特点是鼠标移动到Tip上时,会保持显示状态。 
为了实现这个效果,给Tip的mouseover写入程序:


this.Check(e.relatedTarget) && clearTimeout(this._timer);


其中Check程序是用来判断relatedTarget是不外部元素,即鼠标离开的元素是不是外部元素。 
如果是外部元素,就说明当前是隐藏延时阶段,那么只要清除定时器来取消隐藏就可以了。


这里的外部元素是指触发元素和Tip对象本身及其内部元素以外的元素。 
这个有点拗口,那再看看Check程序是怎么判断的就明白了:


return !this._trigger || 
!( 
this.Tip === elem || this._trigger.Elem === elem || 
Contains(this.Tip, elem) || Contains(this._trigger.Elem, elem) 
);


首先判断_trigger是否存在,不存在的话说明是刚开始触发,也看成是外部触发。 
存在的话再判断传递过来的元素是不是Tip或触发元素本身,最后再用Contains判断判断是不是在Tip或触发元素内部。 
ps:关于Contains请参考这里的比较文档位置。 
这样得到的是判断是否内部元素,最后取反就是判断是否外部元素了。


点击方式:


点击方式显示是指点击触发元素的时候显示Tip。 
在Add程序中会给触发元素的click事件绑定以下程序:


addEvent(elem, “click”, BindAsEventListener(this, function(e){ 
if (trigger.ClickShow) { 
if (this.CheckShow(trigger)) { 
this.ReadyShow(trigger.ClickShowDelay); 
} else { 
clearTimeout(this._timer); 
}; 
}; 
}));


首先根据ClickShow判断是否进行点击显示,再用CheckShow检测是否同一个触发对象。


CheckShow程序是这样的:


if (trigger !== this._trigger) { 
this.Hide(); this._trigger = trigger; return true; 
} else { return false; };


如果不是同一个触发对象,就先执行Hide清理前一个触发对象,防止冲突,再执行ReadyShow来显示。 
如果是同一个触发对象,就说明当前是延时隐藏阶段,清除定时器保持显示状态就行了。


对应的,点击方式隐藏是指点击外部元素的时候隐藏Tip。 
在ReadyShow里,当ClickHide为true时,就会把_fCH绑定到document的click事件里:


trigger.ClickHide && addEvent(document, “click”, this._fCH);


注意这里要把隐藏绑定事件放到ReadyShow,而不是Show里面,因为延时的时候有可能还没有显示就触发了隐藏事件。


其中_fCH是在初始化时定义的一个属性,用于添加和移除点击隐藏事件:


this._fCH = BindAsEventListener(this, function(e) { 
if (this.Check(e.target) && this.CheckHide()) { 
this.ReadyHide(this._trigger.ClickHideDelay); 

});


注意不同于点击显示,由于绑定的是document,隐藏前要先确定e.target是不是外部元素。


其中CheckHide是作用是检查Tip当前是不是隐藏状态:


if (this._cssTip.visibility === “hidden”) { 
clearTimeout(this._timer); 
removeEvent(this._trigger.Elem, “mouseout”, this._fTH); 
this._trigger = null; 
removeEvent(document, “click”, this._fCH); 
return false; 
} else { return true; };


如果本来就是隐藏状态,清除定时器移除事件就行,不需要再执行Hide了。


触发方式:


触发方式针对的是mouseover和mouseout,它的流程跟点击方式是差不多的。


触发方式显示是指鼠标从外部元素进入触发元素(触发mouseover)的时候显示Tip。


在Add程序中会给触发元素的mouseover事件绑定以下程序:


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); 
}; 
}; 
}));


跟点击方式类似,也需要执行一次CheckShow,但不同的是,还会用Check判断e.relatedTarget是不是外部对象。 
这是因为mouseover可能是从触发元素的内部元素(包括Tip)进入或内部元素冒泡触发的,而这些情况不需要任何操作。


对应的,触发方式隐藏是指鼠标从触发元素或Tip离开时隐藏Tip。 
当TouchHide为true时,在ReadyShow的时候会把_fTH绑定到触发元素的mouseout事件里:


trigger.TouchHide && addEvent(this._trigger.Elem, “mouseout”, this._fTH);


在Show的时候,再绑定到Tip的mouseout:


trigger.TouchHide && addEvent(this.Tip, “mouseout”, this._fTH);


在ReadyShow绑定的原因同上,而Tip只需显示时绑定。


其中_fTH跟_fCH类似,也是在初始化时定义的一个属性,用于添加和移除触发隐藏事件:


this._fTH = BindAsEventListener(this, function(e) { 
if (this.Check(e.relatedTarget) && this.CheckHide()) { 
this.ReadyHide(this._trigger.TouchHideDelay); 

});


不同的是mouseout在Check的时候是用e.relatedTarget。


触发原理:


上面是从程序的角度说明了触发显示和隐藏的过程,但要真正理解的话还需要做一次细致的分析。 
下面是以触发方式的显示隐藏为例做的流程图:






下面是文字说明:




等待触发显示;
进入触发元素,如果设置延时,跳到3,如果没有设置延时,跳到4;
延时时间内,离开到外部元素,清除定时器,返回1,超过延时时间,跳到4;
执行显示程序;
显示Tip状态;
离开触发元素,如果是进入到Tip,跳到7,如果是离开到外部元素,跳到9;
保持显示状态;
离开Tip,如果是进入触发元素,返回5,如果是离开到外部元素,跳到9;
如果设置延时,跳到10,如果没有设置延时,跳到11;
延时时间内,如果进入Tip,清除定时器,返回7,如果进入触发元素,清除定时器,返回5,超过延时时间,跳到11;
执行隐藏程序,返回1;


再对照程序,应该就能理解整个流程了,当然可能还不是那么好理解。 
这个流程也只是单例的情况,多例的时候还要多加一些判断。 
可以说这个流程看似不难,但如果想做一个最优化的流程,那要考虑的细节地方可能会让人受不了。 
点击方式跟触发方式的流程是差不多的,而且更简单,这里就不重复了。


元素定位:


完成了显示隐藏,就到本程序另一个重点,元素定位。 
程序包括这几个定位:预设定位,自定义定位,自适应定位。 
而定位的最终效果是结合了这几个定位设置的效果,下面再一一分析。


预设定位和自定义定位:


预设定位的意思是使用程序25个预设位置来定位。 
25个位置是怎么来的呢?看下面的演示


.test{ padding:50px;}
.test .t{border:1px solid #000; width:150px; height:150px; position:relative;}
.test .t div{border:1px solid #F00; position:absolute; width:38px; height:38px; background:#FFF; line-height:38px; text-align:center;}
.test .l1{ left:-41px;}
.test .l2{ left:-1px;}
.test .l3{ left:50%;margin-left:-21px;}
.test .l4{ right:-1px;}
.test .l5{ right:-41px;}
.test .t1{ top:-41px;}
.test .t2{ top:-1px;}
.test .t3{ top:50%;margin-top:-21px;}
.test .t4{ bottom:-1px;}
.test .t5{ bottom:-41px;}