转载:
JavaScript和CSS的交互是现代JavaScript程序设计的支柱。事实上对于所有的现代web应用程序来说,至少使用某些形式的动态交互是必须的。那么做过之后,用户可以更快地操作而更少地把时间浪费在等待页面加载上。将动态技术与第六章提出的事件方面的观念相结合,对于实现无缝而强大的用户体验是非常重要的。
层叠式样式表是用来对易用的、有吸引力的网页进行修饰和布局的事实标准,它在给用户提供最少的困难的同时为开发者提供最多的能力。当你将那一能力与JavaScript相结合时,你将能够构造强健的用户界面,包括动画、窗口部件(widgets),或动态显示等。
访问样式信息
JavaScript与CSS的结合全部是以表现作为结果的交互。理解什么对你是可用的,对于精确地达到你想要的交互非常重要。
用来设置和获取元素的CSS属性的主要工具是其style属性。比如说,如果想要取得一个元素的高度,你可以编写如下代码:elem.style.height。如果你想要设置元素的高度为某个特定值,你可以执行如下代码:ele.style.height="100px"。
当处理DOM元素的CSS属性时,有两个会碰到的问题,它们并不像一般人所期望的那样运作。首先,JavaScript要求你在设置任何空间尺度时指明单位(就像前面设置高度时所做的那样)。同时,任何空间属性也将返回一个代表元素属性的字符串而非数字(如"100px"而非100)。第二,如果一个元素高为100像素,而你试图获取它的当前高度,你期望从style属性里取得那个"100px",情况却未必会如你所愿。这是因为任何样式表文件或内联CSS预设的样式信息并不能可靠地反映到style属性上。
这一状况将我们引向JavaScript中处理CSS的一个重要函数:获取一个元素真正的当前样式属性的方法,给你一个预期的精确值。存在一组(来源于W3C和IE特有的变种)相当可靠的方法可以用来得到DOM元素的真正的计算后的样式属性。它们能顾及所有相关的样式表、元素特定属性以及JavaScript所作的修改。当需要得到你正操作的元素的精确视图信息时这些方法将会是极其有用的。
获取元素的计算后样式值时应该考虑到存在于不同的浏览器间的大量的差异。跟在大多数的情形一样,IE有它自己的方法,而其它所有的浏览器都使用W3C定义的方式来实现。
程序7-1给出了一个用来找出元素的计算后样式属性值的一个函数,7-2则给出了使用此函数的一个示例。
程序7-1. 用来得到元素的计算后的实际CSS样式值的一个函数
代码:
//获取一个特定元素(elem)的样式属性(name)
function getStyle( elem, name ) {
//如果该属性存在于style[]中,则它最近被设置过(且就是当前的)
if (elem.style[name])
return elem.style[name];
//否则,尝试IE的方式
else if (elem.currentStyle)
return elem.currentStyle[name];
//或者W3C的方法,如果存在的话
else if (document.defaultView && document.defaultView.getComputedStyle) {
//它使用传统的"text-Align"风格的规则书写方式,而不是"textAlign"
name = name.replace(/([A-Z])/g,"-$1");
name = name.toLowerCase();
//获取style对象并取得属性的值(如果存在的话)
var s = document.defaultView.getComputedStyle(elem,"");
return s && s.getPropertyValue(name);
//否则,就是在使用其它的浏览器
} else
return null;
}
程序7-2. 元素的计算后的CSS样式值未必是style对象里可用值的一种情况
代码:
<html>
<head>
<style>p { height: 100px; }</style>
<script>
window.onload = function(){
//找到欲检查高度的段落对象
var p = document.getElementsByTagName("p")[0];
//使用传统方式检查其高度
alert( p.style.height + " should be null" );
//检查计算后的高度值
alert( getStyle( p, "height" ) + " should be 100px" );
};
</script>
</head>
<body>
<p>I should be 100 pixels tall.</p>
</body>
</html>
程序7-2说明了怎样得到一个DOM元素的实际的CSS属性值。在这种情形里你得到的是元素的实际的像素高度,即使其高度是通过头部的CSS来设定的。应该注意的是,你的函数将会忽略度量的单位(比如使用的是百分比)。尽管这一解决方案并不是绝对安全的,它的确是一个良好的出发点。
动画
现在你已经掌握了执行基本的DHTML操作的基本技能,我们再来看web应用程序中流行的视觉效果之一:动画。如果使用得宜,动画将能够为用户提供有用的反馈,比如将注意力引向屏幕上的新创建的元素。
我们将先看两个流行的动画效果,再在研究广泛使用的DHTML库时重访它们。
滑入
在第一个动画里将处理一个(display属性为"none"的)隐藏元素,你将通过在一秒之内逐渐增加其高度来渐渐地显示它,以取代粗糙的show()函数。程序7-13所示的函数可以用作show()函数的合适的替代,为用户提供更加平滑的视觉体验。
程序7-13. 通过一在秒之内递增其高度来慢慢显示隐藏元素的函数
代码:
function slideDown( elem ) {
//从0开始扩张
elem.style.height = '0px';
//显示元素(但你看不到它,因为高度为零)
show( elem );
//得到元素的完整的潜在高度
var h = fullHeight( elem );
//我们将做一个在一秒钟内播放的20"帧"的动画
for ( var i = 0; i <= 100; i += 5 ) {
//保证我们有一个正确的i的闭包
(function(){
var pos = i;
//设置未来特定时间的定时器
setTimeout(function(){
//设置元素的新高度
elem.style.height = ( pos / 100 ) * h + "px";
}, ( pos + 1 ) * 10 );
})();
}
}
淡入
你们将看到的下一个动画与第一个非常相似,不过用先前所创建的setOpacity()函数代替了修改高度。这一函数(如程序7-14)显示一个隐藏的元素,并将其透明度从0(完全透明)逐渐变化到100%(完全不透明)。与7-13的函数一样,它为用户提供更加平滑的视觉体验。
程序7-13. 通过一在秒之内递增其透明度来慢慢显示隐藏的元素的函数
代码:
function fadeIn( elem ) {
//透明度从零开始
setOpacity(elem,0);
//显示元素(但是你看不到它,因为透明度为零)
show( elem );
//我们将做一个在一秒钟内播放的20"帧"的动画
for ( var i = 0; i <= 100; i += 5 ) {
//保证我们有一个正确的i的闭包
(function(){
var pos = i;
//设置未来特定时间的定时器
setTimeout(function(){
//设置元素的新的透明度
setOpacity(elem,pos);
}, ( pos + 1 ) * 10 );
})();
}
}
动态元素的隐含的意思也就是使用JavaScript和CSS维护或创建的非静态的元素。简单的例子是指示你对时事通讯感兴趣的复选框和弹出式的e-mail输入域。
在最基本的层面上,有三个关键的属性可用来构造动态效果:位置、尺寸、可见性。使用这三个属性你可以在现代浏览器上模拟大多数常见的用户交互效果。
元素的位置
操作元素的位置是在页面中开发动态元素的一个重要构成部分。访问和修改CSS位置属性让你有效地模拟许多流行的动画和交互效果(比如拖放)。
知道CSS的定位系统是怎样工作是操作元素位置的一个重要步骤。在CSS中,元素使用偏移来定位,使用的度量是相对父元素的左上角的偏移量。图7-1是CSS中使用的坐标系统的一个例子。
图7-1. 使用CSS的网页里坐标系统示例
页面上的所有元素都有着某种形式的top(垂直坐标)和left(水平坐标)偏移。大体来说,多数元素简单地根据其周围的元素静态定位。依照CSS标准的提议,一个元素可以有几种不同的定位方案。为了正好地理解这一点,我们来看看程序7-3所示的一个简单的网页。
程序7-3. 演示使用不同的定位方案的一个网页
代码:
<html>
<head>
<style>
p {
border: 3px solid red;
padding: 10px;
width: 400px;
background: #FFF;
}
p.odd {
/* Positioning information goes in here */
position: static;
top: 0px;
left: 0px;
}
</style>
</head>
<body>
<p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Etiam …p>
<p class='odd'>Phasellus dictum dignissim justo. Duis nec risus id nunc…p>
<p>Sed vel leo. Nulla iaculis, tortor non laoreet dictum, turpis diam …</p>
</body>
</html>
静态定位: 这是元素定位的默认方式;它简单地遵从文档的自然流向。当一个元素静态定位时,top和left属性将不起作用。静态定位的一个例子见图7-2,其中用于定位的css为:position:static;top:0px;left:0px。
图7-2. 页面正常(static)布局流里的段落
相对定位: 这一定位方式与静态定位非常相似,因为元素仍然会遵循正常的文档流直到得到其它指示。但是,设置top或left属性将会导致元素相对它的原来的(静态的)位置发生偏移。相对定位的一个例子如图7-3所示,其中的CSS定位为position:relative;top:-50px;left:50px。
图7-3. 相对定位,元素被移位到了前一个元素上面,而不再遵循正常的文档流
绝对定位: 将一个元素完全从正常的页面布局流中抓出来。被绝对定位的元素将相对于其第一个非静态定位的父级元素来显示。如果不存在父元素,它将相对于整个文档被定位。绝对定位的一个例子见图7-4,其中用于定位的css为:position:absolute;top:20px;left:0px。
图7-4. 绝对定位,元素的位置与页面的左上角相关,显示在已经存在的元素之上
固定定位: 固定定位将一个元素相对于浏览器的窗口定位。设置一个元素的top和left为0相素,将会使得该元素显示在浏览器的左上角(只要用户还在那个页面上),完全忽略浏览器滚动条的任何动作。固定定位的一个例子见图7-5,其中用于定位的css为:position:fixed;top:20px;right:0px。
图7-5. 固定定位,元素被定位到页面的右上角,尽管浏览器窗口被向下滚动了。
取得位置
元素被定位在何处依赖于它的css参数以及与其邻接的内容而不同。访问CSS属性或计算后的实际值都没有提供的一个能力是,获取元素在页面中或者仅在其它元素中的确切位置。
首先,我们来看如何获取元素在页面中的位置。你拥有几个可支配的元素属性可用来找到这一信息。所有的现代浏览器都支持以下三个属性;当然它们各自是怎么处理的,又是另外一回事了:
offsetParent: 理论上,这是元素在其中定位的父级元素。但是在实际情况下,offsetParent引用的元素取决于浏览器(比如说,在FireFox中,它引用根节点,而在Opera中,则是直接父元素。
offsetLeft和offsetTop: 这些参数是元素在其offsetParent上下文中的水平和垂直偏移。在现代浏览器上,它总是精确的。
现在,问题在于寻求一种可以跨浏览器工作的用来判定方式元素位置的一致的办法。实现这一点的最一致的办法如程序7-4所示:使用offsetParent属性沿着DOM树上行,一路累加偏移值。
程序7-4. 计算元素相对于文档的x和y坐标的辅助函数
代码:
//计算元素的X(水平,左)位置
function pageX(elem) {
//检查我们是否已经到了根元素
return elem.offsetParent ?
//如果我们还能往上,则将当前偏移与向上递归的值相加
elem.offsetLeft + pageX( elem.offsetParent ) :
//否则,取当前偏移
elem.offsetLeft;
}
//计算元素的Y(垂直,顶)位置
function pageY(elem) {
//检查我们是否已经到了根元素
return elem.offsetParent ?
//如果我们还能往上,则将当前偏移与向上递归的值相加
elem.offsetTop + pageY( elem.offsetParent ) :
//否则,取当前偏移
elem.offsetTop;
}
使用元素相对于其父元素的位置,你可以向DOM添加额外的相对该父元素定位的元素。比如,这个值用来建造上下文相关的工具提示是非常理想的。
为了找到元素相对于其父元素的位置,你必须再一次求助于offsetParent属性。因为该属性并不能保证返回特定元素的实际的父元素,你不得不使用你的pageX和pageY函数来找到父元素与子元素之间的位置差异。在程序7-5所示的两个函数中,我试图首先使用offsetParent,如果它是当前元素的实际的父元素;否则,我将继续使用pageX和pageY方法沿DOM上行,以确定它的实际位置。
程序7-5. 用来确定元素相对于其父元素位置两个函数
代码:
//查找元素在其父元素中的垂直位置
function parentX(elem) {
//如果offsetParent就是元素的parent,则提前返回
return elem.parentNode == elem.offsetParent ?
elem.offsetLeft :
//否则,我们需要找出两个元素相对整个页面的位置,计算差值
pageX( elem ) - pageX( elem.parentNode );
}
//查找元素在其父元素中的垂直位置
function parentY(elem) {
//如果offsetParent就是元素的parent,则提早返回
return elem.parentNode == elem.offsetParent ?
elem.offsetTop :
//否则,我们需要找出两个元素相对整个页面的位置,计算差值
pageY( elem ) - pageY( elem.parentNode );
}
有两个可用的简单的包装函数可以处理这一点,如程序7-6所示。它们都只是简单调用getStyle函数,但同时也删除任何多余的(除非你不是使用基于像素的布局,它才是有用的)单位信息(比如说,100px将变成100)。
程序7-6. 找出元素的CSS定位的辅助函数
代码:
//得到元素的left位置
function posX(elem) {
//取得计算后样式并从中提取数字
return parseInt( getStyle( elem, "left" ) );
}
//得到元素的top位置
function posY(elem) {
//取得计算后的样式并从中提取数字
return parseInt( getStyle( elem, "top" ) );
}
不同于取得元素的的位置,设置位置要少许多变数。但是联合使用各种方式的布局(absolut,relative,fixed)时,你将能得到相当的、可用的结果。
目前,调整元素位置的唯一的办法是通过修改它的CSS属性。为了保持方法上的一致性,你应该仅修改left和top属性,尽管存在着其它的属性(如bottom和top)。作为开端,你可以轻松地创建一对函数,如程序7-7所示,用来设置一个元素的位置,而不考虑其当前位置。
程序7-7. 不考虑其当前位置,设置元素的x和y位置的一对函数
代码:
//设置元素垂直位置的一个函数
function setX(elem, pos) {
//使用像素为单位,设置CSS属性'left'
elem.style.left = pos + "px";
}
//设置元素水平位置的一个函数
function setY(elem, pos) {
//使用像素为单位,设置CSS属性'top'
elem.style.top = pos + "px";
}
程序7-8. 用来调整元素相对于基原来的位置的一对函数
代码:
//用来把元素的水平位置增加几个像素的一个函数
function addX(elem,pos) {
//取得当前的水平位置并向其加入偏移
setX( posX(elem) + pos );
}
//用来把元素的垂直位置增加几个像素的一个函数
function addY(elem,pos) {
//取得当前的垂直位置并向其加入偏移
setY( posY(elem) + pos );
}
元素的尺寸
计算元素的高度和宽度可能是既无比简单又痛苦的一件事,这取决于具体的情况和你需要它做什么。许多情况下,你只需要使用getStyle函数的一个修改版本来得到元素的当前宽度和高度,如程序7-9所示。
程序7-9. 检索DOM元素的当前高度和宽度的两个函数
代码:
//获取元素的实际高度(使用计算后的CSS)
function getHeight( elem ) {
//取得计算后的CSS值并解析出一个可用的数字
return parseInt( getStyle( elem, 'height' ) );
}
//获取元素的实际宽度(使用计算后的CSS)
function getWidth( elem ) {
//取得计算后的CSS值并解析出一个可用的数字
return parseInt( getStyle( elem, 'width' ) );
}
程序7-10里给出的两个函数说明了怎样找出元素潜在的完整高度和宽度,不论它当前的高度是多少。这是通过访问clientWidth和clientHeight属性实现的,它们提供元素能够展开到的可能的总的区域。
程序7-10. 用来找出元素的潜在高度和宽度的两个函数,即使元素是隐藏的
代码:
//找出元素完整的、可能的高度(不是实际的、当前的高度)
function fullHeight( elem ) {
//如果元素当前是显示的,那么offsetHeight应该可以成功,即使失败,getHeight也可以生效
if ( getStyle( elem, 'display' ) != 'none' )
return elem.offsetHeight || getHeight( elem );
//否则,我们必须处理display为'none'的元素,
//这时我们需要重置它的CSS属性以得到更精确的读数
var old = resetCSS( elem, {
display: '',
visibility: 'hidden',
position: 'absolute'
});
//计算元素的完整高度,如果clientHeight无效,则用getHeight()
var h = elem.clientHeight || getHeight( elem );
//最后,我来来恢复元素本来的CSS属性
restoreCSS( elem, old );
//并返回元素的完整高度
return h;
}
//找出元素完整的、可能的宽度(不是实际的、当前的宽度)
function fullWidth( elem ) {
//如果元素当前是显示的,那么offsetWidth应该可以成功,即使失败,getWidth也可以生效
if ( getStyle( elem, 'display' ) != 'none' )
return elem.offsetWidth || getWidth( elem );
//否则,我们必须处理display为'none'的元素,
//这时我们需要重置它的CSS属性以得到更精确的读数
var old = resetCSS( elem, {
display: '',
visibility: 'hidden',
position: 'absolute'
});
//计算元素的完整宽度,如果clientWidth无效,则用getWidth()
var w = elem.clientWidth || getWidth( elem );
//最后,我来来恢复元素本来的CSS属性
restoreCSS( elem, old );
//并返回元素的完整宽度
return w;
}
//用来设置一系列的CSS属性的一个函数,这些属性稍后可以恢复
function resetCSS( elem, prop ) {
var old = {};
//遍历每一个属性
for ( var i in prop ) {
//记录原来的属性
old[ i ] = elem.style[ i ];
//并设置新的值
elem.style[ i ] = prop[i];
}
//返回改变的值的集合,以备restoreCSS使用
return old;
}
//恢复resetCSS函数引用的副作用的函数
function restoreCSS( elem, prop ) {
//将所有的属性重新设置为它们原来的值
for ( var i in prop )
elem.style[ i ] = prop[ i ];
}
元素的可见性
元素的可见性是可在JavaScript中用来创建从动画到快速模板效果的每一样东西的强大工具。然而,更重要的是,它也能用来从视图中快速地隐藏元素,提供一些基本的用户界面功能。
在CSS里有两种不同的方式来有效地从视图中隐藏元素;它们各自有其益处但也能产生无意的后果,这取决于你怎样使用它们:
◇ visibility属性决定一个元素是否可见,同时仍保持它在布局流中的正常占位。visibility属性有两个值:visible(缺省值)和hidden(使一个元素完全不可见)。比如说,如果你有一些<b>标签中换行的visibility属性设为hidden的文本,结果将简单地显示为文本中的一个空白块,尺寸与原始文本完全相同。例如,比较以下两行文本:
//正常文本:
Hello John, how are you today?
//对"John"应用了"visibility:hidden"的文本
Hello , how are you today?
◇ display属性为开发者提供了更多的选项来控制元素的而局。这些选项是inline(类似于<b>和<span>的标签是行内的,也就是说他们遵循正常的文本流布局),block(类似于<p>和<div>的标签是块级的,他们打破正常的文本流),和none(从文档中彻底隐藏元素)。将display属性设为none的结果表面上跟你把它从文档中删除完全一致;然而,实情并非那样,因为它可以在迟些时候快速地被切换回视图里。下面的两说明了display属性是怎样工作的:
//正常文本:
Hello John, how are you today?
//对"John"应用了"display:none"的文本
Hello , how are you today?
尽管visibility属性有它的特定的用途,display属性的重要性不容忽视。元素的visibility属性被设为hidden时它仍存在于正常的文本流中,这使用得在许多应用中visibility的可行性打了折扣。程序7-11中展示了使用display属性来切换元素可见性的两种方法。
程序7-11. 使用CSS Display属性来切换元素的可见性的一组函数
代码:
//用来(使用display)隐藏元素的一个函数
function hide( elem ) {
//找到元素的当前显示状态是什么
var curDisplay = getStyle( elem, 'display' );
//记录它的显示状态
if ( curDisplay != 'none' )
elem.$oldDisplay = curDisplay;
//将display设为none(隐藏元素)
elem.style.display = 'none';
}
//用来(使用display)显示元素的函数
function show( elem ) {
//将display属性设为它曾经的值,或者使用'',如果没有保存过先前的display的话
elem.style.display = elem.$oldDisplay || '';
}
程序7-12. 调整元素的透明度级别的一个函数
代码:
//设置元素的透明度级别(level是0-100的数字)
function setOpacity( elem, level ) {
//如果滤镜存在,这是IE,于是设置Alpha滤镜
if ( elem.filters )
elem.style.filter = 'alpha(opacity=' + level + ')';
//译注:此处原文为ele.style.filters='alpha(opacity=' + level + ')',有误
//多谢02062007同学的热心验证
//否则,使用W3C的opacity属性
else
elem.style.opacity = level / 100;
}
掌握了调整元素的位置、大小、及可见性的方法,是时候开始探索你联合使用这些能力做一些有意思的事情了。
浏览器
除了操作特定的DOM元素以外,懂得怎样修改或追踪浏览器及其部件,将大大提升站点与用户间的交互。与浏览器协同工作最重要的两个方面是判定鼠标的位置和及用户将页面滚动了多少。
鼠标位置
获取鼠标位置是为用户提供拖放操作和上下文菜单的基本要求,这两种效果都只能通过JavaScript与CSS的交互来实现。
你首先需要检测的两个变量是光标相对于整个页面的x和y坐标(如程序7-15所示)。因为当前的鼠标坐标只可能从鼠标事件中取得,你最终需要使用一个普通的鼠标事件来捕获它们,如MouseOver或MouseDown(这方面的更多例子见"拖放"小节)。
程序7-15. 用来在获取鼠标在整个页面中位置的两个通用函数
代码:
//取得鼠标的水平位置
function getX(e) {
//获取事件对象
e = e || window.event;
//先取非IE的位置,不成功则用IE的位置
return e.pageX || e.clientX + document.body.scrollLeft;
}
//取得鼠标的垂直位置
function getY(e) {
//获取事件对象
e = e || window.event;
//先取非IE的位置,不成功则用IE的位置
return e.pageY || e.clientY + document.body.scrollTop;
}
需要知道的第二组与鼠标相关的变量是鼠标光标相对于当前交互的元素的位置。可用于取得这些值的两个函数如程序7-16所示。
程序7-16. 取得鼠标相对于当前元素位置的两个函数
代码:
//取得鼠标相对于事件对象里e的目标元素(target)的x坐标
function getElementX( e ) {
//取合适的元素偏移
return ( e && e.layerX ) || window.event.offsetX;
}
//取得鼠标相对于事件对象里e的目标元素(target)的x坐标
function getElementY( e ) {
//取合适的元素偏移
return ( e && e.layerY ) || window.event.offsetY;
}
在学习本章的"拖放"这一节里浏览器中元素拖放的实现时,我们将重新回到鼠标交互问题。如需更多的鼠标事件的例子,可参见第六章和附录B。
视口
浏览器的视口可认为就是浏览器里被滚动条围住的区域。视口包含几个部件:视口窗口,页面,滚动条。正确计算它们的位置和尺寸,是在有大段内容的情况下(比如自动滚屏的聊天室)开发漂亮的交互效果的需要。
页面尺寸
你需要关注的第一组属性是当前页面的宽度和高度。一般来说,大多数的实际的页面都被视口所裁切(通过检查视口尺寸和滚动条位置来判定)。程序7-17所示的两个函数使用了前面提到过的scrollWidth和scrollHeight属性(它们表征了一个部件可能的总的宽度和高度,而不仅仅是当前的可见的那一部分)。
程序7-17. 判定当前页面的宽和高的两个函数
代码:
//返回网页的高度(如果新的内容被加入,这一值可能会改变)
function pageHeight() {
return document.body.scrollHeight;
}
//返回网页的宽度
function pageWidth() {
return document.body.scrollWidth;
}
滚动条位置
接下来,你们将看到怎样断定浏览器滚动条的位置(或者,在另一个意义上,视口在当前页面上的定位情况)。掌握这些数字(使用7-18所示的函数了得),对于超越浏览器提供的贫乏的滚动、建立自己更好的动态滚动,是必不可少的。
程序序7-18. 用来判定视口在文档上定位于何处和两个函数
代码:
//用来检测浏览器在水平方向滚动了多少的函数
function scrollX() {
//ie6 strict模式里的快捷方式
var de = document.documentElement;
//如果浏览器的pageXOffset可用,则使用之
return self.pageXOffset ||
//否则,尝试取得根节点的水平滚动量
( de && de.scrollLeft ) ||
//最后,尝试取得body元素的水平滚动量
document.body.scrollLeft;
}
//用来检测浏览器在垂直方向滚动了多少的函数
function scrollX() {
//ie6 strict模式里的快捷方式
var de = document.documentElement;
//如果浏览器的pageYOffset可用,则使用之
return self.pageYOffset ||
//否则,尝试取得根节点的垂直滚动量
( de && de.scrollTop ) ||
//最后,尝试取得body元素的垂直滚动量
document.body.scrollTop;
}
移动滚动条
拥有了页面中滚条的偏移量和页面本身的长度信息之后,就可以利用浏览器提供的scrollTo方法,调整页面上的视口的当前位置。
scrollTop方法作为window对象(以及任何其它包含可滚动内容元素的元素或者<iframe>)的一个属性存在,它接收两个参数,要将视口(或者元素或者<iframe>滚动到的x偏移和y偏移。程序7-19展示了两个使用scrollTo方法的例子。
程序7-19. 使用scrollTo方法调整浏览器视口位置的例子
代码:
//如果你想将视口滚动到浏览器的顶部,可以这么做:
window.scrollTo(0,0);
//如果你想将滚动到特定元素所在位置,可以这么做:
window.scrollTo( 0, pageY( document.getElementById("body") ) );
视口尺寸
有关视口的最后一个可能也是最明显的方面:视口本身的尺寸。知道视口的尺寸将能够洞悉用户当前可以看到多少内容,不管其屏幕分辨率和浏览器窗口是多大。可以使用程序7-20所示的两个函数来得到那些值。
程序7-20. 判定浏览器视口高度和宽度的两个函数
代码:
//取得视口高度
function windowHeight() {
//ie6 strict模式里的快捷方式
var de = document.documentElement;
//如果浏览器的innerHeight可用,则使用它
return self.innerHeight ||
//否则,尝试获得根节点的高度
( de && de.clientHeight ) ||
//最后,尝试获得body元素的高度
document.body.clientHeight;
}
//取得视口高度
function windowWidth() {
//ie6 strict模式里的快捷方式
var de = document.documentElement;
//如果浏览器的innerWidth可用,则使用它
return self.innerWidth ||
//否则,尝试获得根节点的宽度
( de && de.clientWidth ) ||
//最后,尝试获得body元素的宽度
document.body.clientWidth;
}
操作视口的重要性不容忽视。随便查看一个现代web应用程序,如Gmail或Campfire,都能找到操作视口以提供引人注目的结果的实例(Gmail提供了上下文相关的覆盖图,而Campfire提供了自动滚动的聊天室)。在第十一章中我将讨论在高交互性的web应用程序中视口可用来提供更好的体验的不同的方式。
拖放
浏览器上可实现的最流行的交互之一,是将一个元素在页面里拖动。使用你所学的技能(判定元素位置的能力,怎样调整位置,各种定位方式的不同),你现在已可以完全理解拖放系统是怎样工作的。
为了探索这一技术,我选择参照Aaron Boodman创建的DOM-Drag库(http://boring.youngpup.net/2001/domdrag)。他的库里提供了许多便捷的功能,包含以下几种:
拖动手柄: 你可以拥有一个真正被移动的父元素和另一个被拖动的子元素。这对于创建具有窗口外观的界面是很好的。
回调函数: 你可以监视指定事件,如用户何时开始拖动元素、正在拖动元素、停止拖动元素,并得到元素的当前位置信息。
最小/最大拖动区域: 你可以限定一个元素不能拖动到特定的区域之外(如屏幕之外)。这对于建造滚动条是很完美的。
自定义坐标系统: 你可以选择操作一套x/y坐标系统映射,如果对使用css坐标系统感觉不爽的话。
自定义x和y坐标系统转换: 你可以使你拖动的元素以非传统的方式移动(如上下摇动或波浪形运动)。
DOM-Drag系统的使用是相当简单的。程序7-12展示了一些使用DOM-Drag的例子。
程序7-21. 使用DOM-Drag模拟浏览器内的可拖动窗口
代码:
<html>
<head>
<title>DOM-Drag – Draggable Window Demo</title>
<script src="domdrag.js" type="text/javascript"></script>
<script type="text/javascript">
window.onload = function(){
//初始化DOM-Drag函数,使id为"window"的元素可拖动
Drag.init( document.getElementById("window") );
};
</script>
<style>
#window {
border: 1px solid #DDD;
border-top: 15px solid #DDD;
width: 250px;
height: 250px;
position:relative;//译者加的,原文漏了
}
</style>
</head>
<body>
<h1>Draggable Window Demo</h1>
<div id="window">I am a draggable window, feel free to move me around!</div>
</body>
</html>
7-22是DOM-Drag的一个带有完整文档的版本。代码作为单个的全局对象存在,该对象的方法可在页面元素上调用,以初始化拖放过程。
程序7-22. 带有完整文档的DOM-Drag库
代码:
var Drag = {
// 被拖动的当前元素
obj: null,
//拖动元素的初始化函数
// o = 做为拖动手柄的元素
// oRoot = 被拖动的元素,如未指明,拖动手柄本身将为被拖动元素
// minX, maxX, minY, maxY = 允许的元素最小和最大坐标
// bSwapHorzRef = 切换到水平坐标系统
// bSwapVertRef = 切换到垂直坐标系统
// fxMapper, fyMapper = 另行映射x和y坐标的函数
init: function(o, oRoot, minX, maxX, minY,
maxY, bSwapHorzRef, bSwapVertRef, fXMapper, fYMapper) {
//监视拖动事件的开始
o.onmousedown = Drag.start;
//确定使用哪个坐标系统
o.hmode = bSwapHorzRef ? false : true ;
o.vmode = bSwapVertRef ? false : true ;
//确定哪个元素做做为句柄
o.root = oRoot && oRoot != null ? oRoot : o ;
//初始化指定的坐标系统
if (o.hmode && isNaN(parseInt(o.root.style.left )))
o.root.style.left = "0px";
if (o.vmode && isNaN(parseInt(o.root.style.top )))
o.root.style.top = "0px";
if (!o.hmode && isNaN(parseInt(o.root.style.right )))
o.root.style.right = "0px";
if (!o.vmode && isNaN(parseInt(o.root.style.bottom)))
o.root.style.bottom = "0px";
//检查用户是否提供的最小/最大坐标限定
o.minX = typeof minX != 'undefined' ? minX : null;
o.minY = typeof minY != 'undefined' ? minY : null;
o.maxX = typeof maxX != 'undefined' ? maxX : null;
o.maxY = typeof maxY != 'undefined' ? maxY : null;
//检查指定的任何坐标映射函数
o.xMapper = fXMapper ? fXMapper : null;
o.yMapper = fYMapper ? fYMapper : null;
//为所有的用户定义的函数添加外壳(shells)
o.root.onDragStart = new Function();
o.root.onDragEnd = new Function();
o.root.onDrag = new Function();
},
start: function(e) {
//找到正被拖动的元素
var o = Drag.obj = this;
//规范化事件对象
e = Drag.fixE(e);
//取得当前x和y坐标
var y = parseInt(o.vmode ? o.root.style.top : o.root.style.bottom);
var x = parseInt(o.hmode ? o.root.style.left : o.root.style.right );
//以x和y坐标调用用户的函数
o.root.onDragStart(x, y);
//记录起始鼠标位置
o.lastMouseX = e.clientX;
o.lastMouseY = e.clientY;
//如果我们正使用CSS坐标系统
if (o.hmode) {
//设定可用的最小和最大坐标
if (o.minX != null) o.minMouseX = e.clientX - x + o.minX;
if (o.maxX != null) o.maxMouseX = o.minMouseX + o.maxX - o.minX;
//否则,我们正使用传统的数学坐标系统
} else {
if (o.minX != null) o.maxMouseX = -o.minX + e.clientX + x;
if (o.maxX != null) o.minMouseX = -o.maxX + e.clientX + x;
}
//监听拖动和拖动结束事件
document.onmousemove = Drag.drag;
document.onmouseup = Drag.end;
return false;
},
//在拖动事件中监视所有的鼠标运行的函数
drag: function(e) {
//规范化事件对象
e = Drag.fixE(e);
//得到正被拖动的对象的引用
var o = Drag.obj;
//取得鼠标在窗口中的位置
var ey = e.clientY;
var ex = e.clientX;
//得到当前坐标
var y = parseInt(o.vmode ? o.root.style.top : o.root.style.bottom);
var x = parseInt(o.hmode ? o.root.style.left : o.root.style.right );
var nx, ny;
//如果设定了最小x坐标,确保不会超过它
if (o.minX != null) ex = o.hmode ?
Math.max(ex, o.minMouseX) : Math.min(ex, o.maxMouseX);
//如果设定了最大x坐标,确保不会超过它
if (o.maxX != null) ex = o.hmode ?
Math.min(ex, o.maxMouseX) : Math.max(ex, o.minMouseX);
//如果设定了最小y坐标,确保不会超过它
if (o.minY != null) ey = o.vmode ?
Math.max(ey, o.minMouseY) : Math.min(ey, o.maxMouseY);
//如果设定了最小y坐标,确保不会超过它
if (o.maxY != null) ey = o.vmode ?
Math.min(ey, o.maxMouseY) : Math.max(ey, o.minMouseY);
//计算转换后的新的x和y坐标
nx = x + ((ex - o.lastMouseX) * (o.hmode ? 1 : -1));
ny = y + ((ey - o.lastMouseY) * (o.vmode ? 1 : -1));
//并再次使用x和y映射函数(如果提供了)转换它们
if (o.xMapper) nx = o.xMapper(y)
else if (o.yMapper) ny = o.yMapper(x)
//为元素设置新的x和y坐标
Drag.obj.root.style[o.hmode ? "left" : "right"] = nx + "px";
Drag.obj.root.style[o.vmode ? "top" : "bottom"] = ny + "px";
//并记录鼠标的最后位置
Drag.obj.lastMouseX = ex;
Drag.obj.lastMouseY = ey;
//使用当前的x和y坐标调用用户的onDrag函数
Drag.obj.root.onDrag(nx, ny);
return false;
},
//处理拖放结束的函数
end: function() {
//不在监视鼠标事件(因为拖放已经结束)
document.onmousemove = null;
document.onmouseup = null;
//在拖放事件的最后,用元素的x和y坐标调用我们的特别的onDragEnd函数
Drag.obj.root.onDragEnd(
parseInt(Drag.obj.root.style[Drag.obj.hmode ? "left" : "right"]),
parseInt(Drag.obj.root.style[Drag.obj.vmode ? "top" : "bottom"]));
//不再监视拖动的对象
Drag.obj = null;
},
//规范化事件对象的函数
fixE: function(e) {
//如果e不存在,则是IE浏览器,于是使用IE的event对象
if (typeof e == 'undefined') e = window.event;
//如果layer属性没有设置,则从等效的属性中取得
if (typeof e.layerX == 'undefined') e.layerX = e.offsetX;
if (typeof e.layerY == 'undefined') e.layerY = e.offsetY;
return e;
}
};
平心而论,DOM-Drag算是数以百计的JavaScript拖放库中比较简单的一个。但是我个人特别喜欢它,因其整洁的面向对象语法和相对的简单性。下一节中我将论述Scriptaculous库,它拥有一个出色而强大的拖放的实现,我强烈推荐你们去研究一下。
库
正如JavaScript里多数枯燥的任务一样,如果你想要开发出一个效果或者交互,极有可能它已经被创造出来了。下面将快速地浏览三种提供了不同的DHTML交互的库,你将知道作为开发者,有些什么拿来就可以用。
moo.fx和jQuery
有两种擅长于简单效果的轻量级的库:moo.fx和jQuery。这两个库都提供了一套基本的效果,可以用来组合以创建有效而简单的动画。关于它们的更多信息可以在其各自的相关网站上找到。程序7-23展示了这两个库的一些基本的例子。
程序7-23. 使用moo.fx和jQuery的动画的基本例子
代码:
//一个简单的动画:元素的隐藏部分被展开,完成以后,再次收缩
//这一动画的moo.fx实现
new fx.Height( "side", {
duration: 1000,
onComplete: function() {
new fx.Height( "side", { duration: 1000 } ).hide();
}
}).show();
// jQuery的实现
$("#side").slideDown( 1000, function(){
$(this).slideUp( 1000 );
});
//另一个简单的动画:元素的高度、宽度和透明度全部同步收缩或降低,产生一个很酷的隐藏效果
//此动画的moo.fx实现
new fx.Combo( "body", {
height: true,
width: true,
opacity: true
}).hide();
//此动画的jQuery实现
$("#body").hide( "fast" );
正如你从例子中可能已看到的,moo.fx和jQuery使得执行一些小巧的动画变成非常容易。两个项目都在其网站上提供了大量的应用其代码的例子,这是了解简单的JavaScript动画怎样工作的很好的方式。
moo.fx的主页:http://moofx.mad4milk.net/
mootoolkit文档和示例:http://moofx.mad4milk.net/
jQuery的主页:http://jquery.com/
jQuery效果和文档和示例:http://jquery.com/docs/fx/
Scriptaculous
如果说有一个库在所有的DHTML库中可以王者称之的话,那非Scriptaculous莫属。基于流行的Prototype库,Scriptaculous提供了海量的各式效果,从动画效果到动作(诸如拖放)应有尽有。在Scriptaculous的网站上可以找到大量的信息和示例:
主页: http://script.aculo.us/
文档: http://wiki.script.aculo.us/scriptaculous/
演示: http://wiki.script.aculo.us/scriptaculous/show/Demos/
在拖放的实现方式上,Scriptaculous引入强大的能力,同时又保持了最大限度的简单性。我给出两个简单的例子。
拖放重排序
Scriptaculous使得列表的重排度变得极其简单。考虑到它代码是多么地简单(以及它与Ajax功能挂勾有多简单,如其网站上演示的),它绝对是大多数web开发者的推荐方案。7-24所示是使用Scriptaculous创建的一个简单的可重排序列表。
程序7-24. 怎样使用Scriptaculous中提供的技术创建一个可重排序的列表
代码:
<html>
<head>
<title>script.aculo.us – Drag and Drop Re-Ordering Demo</title>
<script src="prototype.js" type="text/javascript"></script>
<script src="scriptaculous.js" type="text/javascript"></script>
<script src="effects.js" type="text/javascript"></script>
<script src="dragdrop.js" type="text/javascript"></script>
<script type="text/javascript">
window.onload = function(){
//将id为list的元素转换成一个可拖放重排序的列表
Sortable.create('list');
};
</script>
</head>
<body>
<h1>Drag and Drop Re-Ordering</h1>
<p>Drag and drop an item to re-order it.</p>
<ul id="list">
<li>Item number 1</li>
<li>Item number 2</li>
<li>Item number 3</li>
<li>Item number 4</li>
<li>Item number 5</li>
<li>Item number 6</li>
</ul>
</body>
</html>
我希望上面这个例子可以使你相信这个库所包含的能力,但如果还没有的话,你可以看看下一个例子,它创建了一个滑块输入控件。
滑块输入
Scriptaculous提供了一些控件,可以用来解决常见的界面开发问题。使用多数拖放库都很容易实现的一个控件是滑块输入(滑动条块,得到数字输入),Scriptaculous也不例外,如程序7-25所示:
程序7-25. 使用来自Scriptaculous的滑块输入控件创建向表单中年龄输入框的一个替代
代码:
<html>
<head>
<title>script.aculo.us – Slider Input Demo</title>
<script src="prototype.js" type="text/javascript"></script>
<script src="scriptaculous.js" type="text/javascript"></script>
<script src="effects.js" type="text/javascript"></script>
<script src="dragdrop.js" type="text/javascript"></script>
<script src="controls.js" type="text/javascript"></script>
<script type="text/javascript">
window.onload = function(){
//将id为ageHandle的元素变成可拖动的手柄,
//且将id为ageBar的元素变成滑动条
new Control.Slider( 'ageHandle', 'ageBar', {
//当滑块移动或完成移动时,调用updateAge函数
onSlide: updateAge
});
//处理滑动块上发生的的动作
function updateAge(v) {
//滑动条更新时,更新age元素的值,代表用户的当前年龄
$('age').value = Math.floor( v * 100 );
}
};
</script>
</head>
<body>
<h1>Slider Input Demo</h1>
<form action="" method="POST">
<p>How old are you? <input type="text" name="age" id="age" /></p>
<div id="ageBar" style="width:200px; background: #000; height:5px;">
<div id="ageHandle" style="width:5px; height:10px;
background: #000; cursor:move;"></div>
</div>
<input type="submit" value="Submit Age"/>
</form>
</body>
</html>
我强烈推荐你在决定编写下一段交互的代码时查看一些DHTML库,基于这样一个简单的事实:库的作者通常在开发那一段交互代码时投入的时间和气力甚至比你开发整个应用程序所付出的还要多。掌握那些库,将显著缩短你的开发周期。
本章小结
在web应用程序中使用动态的交互是为用户提供更高水平的速度和可用性的一种非常有效果的方式。另外,如果使用流行的库,你实现这一切的时候将能够轻易保持较少的开发时间。下一章中,你将会综合这一章所学到的所有的交互技术,创建一个完全可用的、交互式的应用程序。
在这一章中,你已经学习过了实现JavaScript和CSS协作的所有不同的技术。你将拥有创建醒目的动画和动态的用户交互的能力。
应该记住的是,向一个网页添加任何形式的动态交互都有可能疏远你潜在的受众。应该确保你的应用程序总是可用的,即便是JavaScript和CSS被禁用。创建可平滑地降级的应用程序应该是任何JavaScript开发的理想状况。