实现漂亮的链接提示效果,仿http://www.live.com/
最近在学Ajax,想起老师给我们演示的http://www.live.com/里面的一些效果,其中很喜欢这个链接提示效果,不禁思索着怎么实现这种效果。怀着好奇心开始查看它的源代码,可是看了半天还是找不到哪一段代码是实现这个效果的,只好自己尝试着做一下。

一、 思路分析
首先要把文章标题列表显示出来,其中包括了“更多”这个链接以及ID这个参数。
接下来通过javascript检查Dom的操作把几个“更多”找出来,并对这几个“更多”增加鼠标经过和鼠标移开事件。在事件处理里就是对 提示框 的操作。其中这个提示框的外观应该是先用CSS定义好的。我们所要处理的,就是这个提示框的可视化、位置以及内容。对于位置,需要先获取当前“更多”的位置,然后再改变提示框的位置。而内容,首先要接收一个ID,然后根据这个ID发送到服务器并返回文章正文。
二、 功能实现
1、 提示框的外观
箭头可以用图像处理软件做出来,作为背景图片,调整一下位置就行了。
而阴影的效果是用相对定位来实现,在内容层以上增加一个层,作为,阴影层。所以阴影层是做为父元素的,可以这样理解,内容层本来是刚刚好填充满阴影层的,加上top:-3px;left:-3px(也就是向上和左移动3个像素),就露出了一部分阴影层,加上阴影层的背景颜色,就出现了阴影的效果。下面是HTML以及CSS
代码如下:
<style type="text/css">
#LinkPanel{position:absolute;z-index:100; font-size:12px; visibility:hidden; }
#LinkPanel .arrowhead{ position:relative; top:30px; left:0px; width:20px; height:39px; background:url(../images/arrowhead_black.gif) left top no-repeat; float:left;padding:0px;margin:0px; }
#LinkPanel .shadow{ background:#ccc;float:left; }
#LinkPanel .maincontent{ width:400px;position:relative; top:-3px; left:-3px; background:#fff;border:1px solid #000;padding:5px; overflow:hidden;margin:0px;}
#LinkPanel .title{ font-weight:bold; border-bottom:1px dashed #d3d3d3; padding-bottom:10px;}
#LinkPanel .content{width:390px; overflow:auto;max-height:420px;height:420px;}
#LinkPanel .links{ background:#EDF2FB; padding:10px; width:390px;}
#LinkPanel .links a{ display:block; color:#0066A7;}
#linkPanel .links a:hover{color:#0088E4; }</style>
<div id="LinkPanel">
<div class='arrowhead'></div>
<div class='shadow'>
<div class='maincontent'><div class='title'></div>
<div class='content'>正在读取数据中
</div>
<div class='links'>
<a target='_blank' href=''>查看完整内容</a>
</div>
</div>
</div>
</div>
数据库用Access,建一个article表,里面有id,title,content,posttime,categoryId等字段。
在HTML页写上一个要显示内容的层,ID为ListPanel,然后就可以在javascript中用var List=document.getElementById("ListPanel")来获取要显示内容的层,用List.innerHTML来插入元素的内容。内容来自数据库,所以需要用到Ajax,先创建一个xmlhttprequest对象,然后发送一个请求到aspx页面,并返回一个JSON文本。在客户端用Eval(xmlhttp.responseText)就可以得到相应的对象,也就是article对象,有id和title属性。
接下来可以用循环语句来遍历article里的每一条记录,并储存起来。最后赋值给List.innerHTML。
//返回创建的XMLHttpRequest对象
function createxmlHttp()
{
var xmlHttp= false;
if(window.ActiveXObject) 
{
xmlHttp= new ActiveXObject("Microsoft.XMLHTTP");
}
else if(window.XMLHttpRequest)
{
xmlHttp=new XMLHttpRequest();
}
return xmlHttp;
}
//全局变量,获取文章列表的xmlhttprequest对象
var List_xmlhttp;
//获取文章列表数据函数,categoryId为分类ID,不填时获取全部文章
function GetList(categoryId)
{
List_xmlhttp=createxmlHttp();//创建XMLHttpRequest对象
var url;
if(categoryId==null)
url="Handler.aspx";
else
url="Handler.aspx?categoryId="+categoryId;
List_xmlhttp.open("POST",url,true);
List_xmlhttp.onreadystatechange=ReturnList;
List_xmlhttp.send(null);
}
//readyState属性改变时的处理函数
function ReturnList()
{
var img="<ul><li class='li_img'><img src='images/ajax_loader.gif'/>请稍等,数据正在读取中
</li></ul>";
var List=document.getElementById("ListPanel");
if(List_xmlhttp.readyState==1)//判断对象状态-初始化
{ 
List.innerHTML=img;
} 
else if(List_xmlhttp.readyState==4)//判断对象状态-完成
{
if(List_xmlhttp.status==200)//信息成功返回,开始处理信息
{
eval(List_xmlhttp.responseText);//利用JSON获取article对象
var i;
var HTML;
HTML="<ul>";
//遍历article对象
for(i=0;i<article.length;i++)
HTML+="<li><a class='title' href='#'>"+article[i].title+"</a> <a class='more'>[更多]</a></li>";
HTML+="</ul>";
List.innerHTML=HTML;//把数据填冲到文章列表区块
InitLinkPanle();//初始化提示框效果


}
else
{
List.innerHTML="对不起,操作超时";
}
}

}
Handler.aspx.cs
using System;
using System.Web;
using System.Data.OleDb;
using System.Text;
public partial class Handler : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(Request.QueryString["type"]))
GetList();
else if(Request.QueryString["type"]=="article")
GetArticle();
}


private void GetList()
{

OleDbConnection conn = new OleDbConnection("data source=" + HttpContext.Current.Server.MapPath("~/App_Data/db.mdb").ToString() + ";provider=microsoft.jet.oledb.4.0");
conn.Open();
string sqlstr="";
if(string.IsNullOrEmpty(Request.QueryString["categoryId"]))
sqlstr="select top 10 id,title,posttime from article";
else
sqlstr = "select top 10 id,title from article where categoryId=" + Request.QueryString["categoryId"];
OleDbCommand cmd = new OleDbCommand(sqlstr, conn);
OleDbDataReader dr = cmd.ExecuteReader();
string JSONText = GetJSON(dr, "article");
Response.Write(JSONText);
conn.Close();
Response.End();
}
private void GetArticle()
{
OleDbConnection conn = new OleDbConnection("data source=" + HttpContext.Current.Server.MapPath("~/App_Data/db.mdb").ToString() + ";provider=microsoft.jet.oledb.4.0");
conn.Open();
OleDbCommand cmd = new OleDbCommand("select content from article where id="+Request.QueryString["id"], conn);

Response.Write(cmd.ExecuteScalar().ToString());
conn.Close();
Response.End();
}

private static string GetJSON(OleDbDataReader drValue, string objName)
{
StringBuilder sb = new StringBuilder();
sb.AppendLine("{");
sb.Append(objName);
sb.AppendLine("=[");
try
{
while (drValue.Read())
{
sb.Append("{");
for (int i = 0; i < drValue.FieldCount; i++)
{
sb.AppendFormat("\"{0}\":\"{1}\",", drValue.GetName(i), drValue.GetValue(i));
}
sb.Remove(sb.ToString().LastIndexOf(','), 1);
sb.AppendLine("},");
}
sb.Remove(sb.ToString().LastIndexOf(','), 1);

}
catch
{
}
finally
{
drValue.Close();
}
sb.AppendLine(" ]");
sb.AppendLine(" }");
return sb.ToString();
}
}

给ListPanel填冲完内容后,ListPanel就可以得给下面这样的代码
<div id="ListPanel">
<ul>
<li><a class='title' href='#'> ArticleTitle</a> <a class='more'>[更多]</a></li>


</ul>
</div>
这个时候如果想获取“更多”,可以先获取列表<ul>的列表项<li>的集合,接着遍历这个列表项集合,并从各个列表项中找到第二个链接,也就是“更多”这个链接,对其添加鼠标经过和鼠标移开事
//提示框效果初始化
function InitLinkPanle()
{ 
var i;
//获取列表的集合
var liList=document.getElementById("ListPanel").getElementsByTagName("ul")[0].getElementsByTagName("li"); 
for(i=0;i<liList.length;i++)//遍历列表
{
var link=liList[i].getElementsByTagName("a")[1];//获取第2个链接,也就是"更多"链接
link.id=article[i].id;//利用JSON获取article对象的id并赋值给link的id属性,用于后面的使用
link.title=article[i].title;//利用JSON获取article对象的title并赋值给link的title属性,用于后面的使用
//为"更多"增加一个鼠标经过事件
link.onmouseover=function(){ //这里是对提示框的处理语句 };
}
}

4、提示框位置、可视化
要设置提示框的位置首先要获取触发对象,也就是所在“更多”链接的位置。用getElementPos()函数
function getElementPos(elementId) {
var ua = navigator.userAgent.toLowerCase();
var isOpera = (ua.indexOf('opera') != -1);
var isIE = (ua.indexOf('msie') != -1 && !isOpera); // not opera spoof
// var el = document.getElementById(elementId);
var el = elementId;
if(el.parentNode === null || el.style.display == 'none') {
return false;
} 
var parent = null;
var pos = []; 
var box; 
if(el.getBoundingClientRect) //IE
{ 
box = el.getBoundingClientRect();
var scrollTop = Math.max(document.documentElement.scrollTop, document.body.scrollTop);
var scrollLeft = Math.max(document.documentElement.scrollLeft, document.body.scrollLeft);
return {x:box.left + scrollLeft, y:box.top + scrollTop};
}else if(document.getBoxObjectFor) // gecko 
{
box = document.getBoxObjectFor(el); 
var borderLeft = (el.style.borderLeftWidth)?parseInt(el.style.borderLeftWidth):0; 
var borderTop = (el.style.borderTopWidth)?parseInt(el.style.borderTopWidth):0; 
pos = [box.x - borderLeft, box.y - borderTop];
} else // safari & opera 
{
pos = [el.offsetLeft, el.offsetTop]; 
parent = el.offsetParent; 
if (parent != el) { 
while (parent) { 
pos[0] += parent.offsetLeft; 
pos[1] += parent.offsetTop; 
parent = parent.offsetParent;
} 
} 
if (ua.indexOf('opera') != -1 || ( ua.indexOf('safari') != -1 && el.style.position == 'absolute' )) { 
pos[0] -= document.body.offsetLeft;
pos[1] -= document.body.offsetTop; 
} 
} 
if (el.parentNode) { 
parent = el.parentNode;
} else {
parent = null;
}
while (parent && parent.tagName != 'BODY' && parent.tagName != 'HTML') { // account for any scrolled ancestors
pos[0] -= parent.scrollLeft;
pos[1] -= parent.scrollTop;
if (parent.parentNode) {
parent = parent.parentNode;
} else {
parent = null;
}
}
return {x:pos[0], y:pos[1]};
}

如果使用了jquery,可以用里面的offset()方法。
获取了“更多”的位置后设置一下提示框的相对偏移就行了。
link.onmouseover=function()
{

var width = this.offsetWidth;//获取当前元素(也就是"更多")的宽度
var height=this.offsetHeight;//获取当前元素的高度
var pos=getElementPos(this);//获取当前对象的坐标
pos.x=pos.x+width;
pos.y=pos.y+height/2;
var LinkPanel=document.getElementById("LinkPanel");
LinkPanel.style.top = pos.y-50+"px";//设置提示框的Y坐标
LinkPanel.style.left = pos.x-2+"px";//设置提示框的X坐标
GetArticleById(this.id,this.title); //提示框内容
LinkPanel.style.visibility="visible";//提示框可视化
};
这里也需要用到Ajax,因为只是返回某一篇文章的Content字段,所以没必要用到JSON。
在link.onmouseover=function(){}里面增加一条语句:GetArticleById(this.id,this.title); //获取提示框内容
function GetArticleById(id,title)
{
var art_xmlhttp=createxmlHttp();//创建xmlhttprequest对象
var url="Handler.aspx?type=article&id="+id;
art_xmlhttp.open("POST",url,true);
art_xmlhttp.onreadystatechange=function(){
if(art_xmlhttp.readyState==1)
document.getElementById("LinkPanel").innerHTML="<div class='arrowhead'></div><div class='shadow'><div class='maincontent'><div class='title'>"+title+"</div><div class='content'>正在读取数据中
</div><div class='links'><a href='view.aspx?id="+id+"'>查看完整内容</a></div></div></div>"; 
else if(art_xmlhttp.readyState==4)
{
if(art_xmlhttp.status==200)
{
document.getElementById("LinkPanel").innerHTML="<div class='arrowhead'></div><div class='shadow'><div class='maincontent'><div class='title'>"+title+"</div><div class='content'>"+art_xmlhttp.responseText+"</div><div class='links'><a href='view.aspx?id="+id+"'>查看完整内容</a></div></div></div>";
}
else
document.getElementById("LinkPanel").innerHTML="<div class='arrowhead'></div><div class='shadow'><div class='maincontent'><div class='title'>"+title+"</div><div class='content'>对不起,操作超时</div><div class='links'><a href='view.aspx?id='"+id+"'>查看完整内容</a></div></div></div>";
}

};
art_xmlhttp.send(null);
}

上面的做好了,接下来文章分类就很容易实现
Default.htm
<html>
<head>
<title>实现漂亮的链接提示效果,仿www.live.com</title>
<link href="style/css.css" rel="stylesheet" type="text/css" />
</head>
<body>
<div id="LinkPanel"></div>
<br /><br />
<div id="MainPanel">
<div id="ListPanel">
<ul>
<li><a class='title' href='#'>article[i].title</a> <a class='more' name='more'>[更多]</a></li>
</ul>
</div>
<div id="category"> 文章分类:<a>全部</a> | <a>分类一</a> | <a>分类二</a> </div>
</div>
<script language="JavaScript" type="text/javascript" src="js/js.js"></script>
</body>
</html>
//文章分类初始化
function InitCategory()
{

var categorylinks=document.getElementById("category").getElementsByTagName("a");
categorylinks[0].onclick=function(){GetList();this.style.color="#0093BB";categorylinks[1].style.color="#333333";categorylinks[2].style.color="#333333";};//全部
categorylinks[1].onclick=function(){GetList(1);this.style.color="#0093BB";categorylinks[2].style.color="#333333";categorylinks[0].style.color="#333333";};//分类一
categorylinks[2].onclick=function(){GetList(2);this.style.color="#0093BB";categorylinks[0].style.color="#333333";categorylinks[1].style.color="#333333";};//分类二
}
//初始化页面
function InitPage()
{
GetList();//获取全部文章列表
InitCategory();//文章分类初始化
}
InitPage();
演示地址:http://www.leoxu.cn/demo/linktooptip/default.htm
源码下载:http://user.qzone.qq.com/175932810/blog/1210213256

#LinkPanel
浙公网安备 33010602011771号