第5章 操作DOM

所谓Web体验,就是Web服务器与Web浏览器之间合作的结果。过去,都是由服务器生成HTML文档,然后浏览器负责解释并显示该文档。后来,正如我们所看到的,这种情况发生了变化,我们可以用CSS技术来动态修改页面的外观。然而,要想把JavaScript的威力真正发挥出来,还得学会修改文档本身。
本章将学习以下内容:

  1. 利用DOM提供的接口修改文档;
  2. 在网页中根据需要创建元素和文本;
  3. 移动或删除元素;
  4. 通过添加、删除或修改它们的属性来实现文档内容的变换。

4.6 小结

通过使用本章介绍的效果方法,读者应该能够通过JavaScript来修改行内样式属性,为元素应用预定义的jQuery效果,创建自定义动画。特别地,我们学习了使用.css().animate()来渐进地增大或减小文本的大小,通过修改多个属性来逐渐显示和隐藏页面元素。此外,还学习了通过许多方式,同时地或相继地为多个元素实现动画效果。
在本书前面四章中,所有例子都只涉及了操作硬编码到页面的HTML中的元素。在第5章中,我们会探索直接操作DOM,包括使用jQuery来创建新元素的方式,以及把它们插入到选择的DOM结构中。

4.5.3 简单概括

随着在应用效果时需要考虑的变化的增多,要记住这些效果是同时发生还是按顺序发生会变得越来越困难。因此,下面简单的概括可能会对你有所帮助。
(1) 一组元素上的效果:

  • 当在一个.animate()方法中以多个属性的方式应用时,是同时发生的;
  • 当以方法连缀的形式应用时,是按顺序发生的(排队效果)——除非queue选项值为false

(2) 多组元素上的效果:

  • 默认情况下是同时发生的;
  • 当在另一个效果方法或者在.queue()方法的回调函数中应用时,是按顺序发生的(排队效果)。

4.5.2 处理多组元素

与一组元素的情况不同,当为不同组的元素应用效果时,这些效果几乎会同时发生。为了示范这种并发的效果,我们可以在向上滑出一个段落时,向下滑入另一个段落。首先,要用到示例文档中的如下三、四段文本:
接着,为了更清楚地看到效果发生期间的变化,我们为第三段和第四段分别添加1像素宽的边框和灰色的背景。同时,在DOM就绪时立即隐藏第4段,参见代码清单4-23。

1
2
3
4
$(document).ready(function() { 
$('p').eq(2).css('border', '1px solid #333');
$('p').eq(3).css('backgroundColor', '#ccc').hide();
});

这样,示例文档会显示开始的段落,然后是read more链接和带边框的段落,如图4-11所示。
最后,为第三段添加click处理程序,以便单击它时会将第3段向上滑(最终滑出视图) ,同时将第4段向下滑(最终滑入视图),参见代码清单4-24。
通过截取到的这两个滑动效果变化过程中的屏幕截图,如图4-12所示,可以证实,它们确实是同时发生的。
原来可见的第三个段落,正处于向上滑到一半的状态;与此同时,原来隐藏的第四个段落,正处于向下滑到一半的状态。
排队回调函数
为了对不同元素上的效果实现排队,jQuery为每个效果方法都提供了回调函数。同我们在事件处理程序和.queue()方法中看到的一样,回调函数就是作为方法的参数传递的一个普通函数。在效果方法中,它们是方法的最后一个参数。
当使用回调函数排队两个滑动效果时,可以在第3个段落滑上之前,先将第4个段落滑下。首先,我们看一看怎样通过回调函数设置.slideDown()方法,如代码清单4-25所示。

1
2
3
4
5
6
7
8
9
10
$(document).ready(function() { 
$('p').eq(2)
.css('border', '1px solid #333')
.click(function() {
$(this).next().slideDown('slow', function() {
$(this).slideUp('slow');
});
});
$('p').eq(3).css('backgroundColor', '#ccc').hide();
});

不过,这里我们需要注意的是,必须搞清楚要滑上的到底是哪个段落。因为回调函数位于.slideDown()方法中,所以$(this)的环境已经发生了改变。现在,$(this)已经不再是指向.click()的第三个段落了——由于.slideDown()方法是通过$(this).next()调用的,所以该方法中的一切现在都将$(this)视为下一个同辈元素,即第四个段落。因而,如果在回调函数中放入$(this).slideUp('slow'), 那么我们最终还会把刚刚显示出来的段落给隐藏起来。
可靠地引用$(this)的一种简单方法,就是在.click()方法内部把它保存到一个变量中,比如var $ clickedItem = $(this)
这样,无论是在回调函数的外部还是内部,$clickedItem引用的都是第三个段落。使用了新变量之后的代码,参见代码清单4-26。

1
2
3
4
5
6
7
8
9
10
11
$(document).ready(function() { 
$('p').eq(2)
.css('border', '1px solid #333')
.click(function() {
var $clickedItem = $(this);
$clickedItem.next().slideDown('slow', function() {
$clickedItem.slideUp('slow');
});
});
$('p').eq(3).css('backgroundColor', '#ccc').hide();
});

这次效果中途的屏幕截图如图4-13所示,第三段和第四段同时都是可见的,而且,第四段已经完成下滑,第三段刚要开始上滑。
既然讨论了回调函数,那么就可以回过头来基于代码清单4-22解决在接近一系列效果结束时改变背景颜色的问题了。这次,我们不像前面那样使用.queue()方法,而是使用回调函数,如代码清单4-27所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$(document).ready(function() { 
$('div.label').click(function() {
var paraWidth = $('div.speech p').outerWidth();
var $switcher = $(this).parent();
var switcherWidth = $switcher.outerWidth();
$switcher
.css({position: 'relative'})
.fadeTo('fast', 0.5)
.animate({
left: paraWidth - switcherWidth
}, {
duration: 'slow',
queue: false
})
.fadeTo('slow', 1.0)
.slideUp('slow', function() {
$switcher.css({backgroundColor: '#f00'});
})
.slideDown('slow');
});
});

同前面一样,<div id="switcher">的背景颜色在它滑上之后滑下之前,变成了红色。注意,在使用交互的完成回调函数而不是.queue()时,不必在回调中调用next()

4.5 并发与排队效果

通过刚才的例子,可以看出.animate()方法在为一组特定的元素创建并发效果时非常有用。然而,有的时候我们需要的则是排队效果,即让效果一个接一个地发生。

4.5.1 处理一组元素

当为同一组元素应用多重效果时,可以通过连缀这些效果轻易地实现排队。为了示范排队效果,我们仍以代码清单4-17为例,移动Text Size盒子、增加其高度、加宽其边框。不过,这次我们相继地执行这三个效果——很简单,只要把它们分别放在.animate()方法中并连缀起来即可,参见代码清单4-18。

1
2
3
4
5
6
7
8
9
10
11
12
$(document).ready(function() { 
$('div.label').click(function() {
var paraWidth = $('div.speech p').outerWidth();
var $switcher = $(this).parent();
var switcherWidth = $switcher.outerWidth();
$switcher
.css({position: 'relative'})
.animate({left: paraWidth - switcherWidth}, 'slow')
.animate({height: '+=20px'}, 'slow')
.animate({borderWidth: '5px'}, 'slow');
});
});

虽然连缀允许我们把这两个.animate()方法放在同一行,但为了更好的可读性,这里故意将它们分开放在了各自的一行中。
通过使用连缀,可以对其他任何jQuery效果进行排队,而并不限于.animate()方法。比如说,我们可以按照下列顺序对<div id="switcher">上的效果进行排队。
(1) 通过.fadeTo()将其不透明度减退为0.5。
(2) 通过.animate()将其移动到右侧。
(3) 通过.fadeTo()将其渐增回完全不透明。
(4) 通过.slideUp()隐藏它。
(5) 通过.slideDown()再将其显示出来。
我们所要做的,就是在代码中按照相同的顺序连缀这些效果,如代码清单4-19所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$(document).ready(function () {
$('div.label').click(function () {
var paraWidth = $('div.speech p').outerWidth();
var $switcher = $(this).parent();
var switcherWidth = $switcher.outerWidth();
$switcher
.css({ position: 'relative' })
.fadeTo('fast', 0.5)
.animate({ left: paraWidth - switcherWidth }, 'slow')
.fadeTo('slow', 1.0)
.slideUp('slow')
.slideDown('slow');
});
});

1. 越过队列

不过,要是想在这个<div>不透明度减退至一半的同时,把它移动到右侧应该怎么办呢?如果两个动画以相同速度执行,则可以简单地把它们组合到一个.animate()方法中。但这个例子中的.fadeTo()使用的速度字符串是’fast',而向右移动的动画使用的速度字符串是’slow'。在这种情况下,第二种形式的.animate()方法又可以派上用场了,参见代码清单4-20。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$(document).ready(function() { 
$('div.label').click(function() {
var paraWidth = $('div.speech p').outerWidth();
var $switcher = $(this).parent();
var switcherWidth = $switcher.outerWidth();
$switcher
.css({position: 'relative'})
.fadeTo('fast', 0.5)
.animate({
left: paraWidth - switcherWidth
}, {
duration: 'slow',
queue: false
})
.fadeTo('slow', 1.0)
.slideUp('slow')
.slideDown('slow');
});
});

第二个参数(即选项对象)包含了queue选项,把该选项设置为false即可让当前动画与前一个动画同时开始。

2. 手工队列

有关为一组元素应用排队效果的最后一个需要注意的问题,就是排队不能自动应用到其他的非效果方法,如.css()。下面,假设我们想在.slideUp()执行后但在.slideDown()执行前,把<div id="switcher">的背景颜色修改为红色,可以尝试像代码清单4-21这样来做。

然而,即使把修改背景颜色的代码放在连缀序列中正确的位置上,它也会在单击后立即执行。把非效果方法添加到队列中的一种方式,就是使用.queue()方法。代码清单4-22就是在这个例子中使用.queue()方法的代码片段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$(document).ready(function () {
//其他代码...
$('div.label').click(function () {
var paraWidth = $('div.speech p').outerWidth();
var $switcher = $(this).parent();
var switcherWidth = $switcher.outerWidth();
$switcher
.css({ position: 'relative' })
.fadeTo('fast', 0.5)
.animate({
left: paraWidth - switcherWidth
}, {
duration: 'slow',
queue: false
})
.fadeTo('slow', 1.0)
.slideUp('slow')
.queue(function (next) {
$switcher.css({ backgroundColor: '#f00' });
next();
})
.slideDown('slow');
});
});

像这样传递一个回调函数,.queue()方法就可以把该函数添加到相应元素的效果队列中。在这个函数内部,我们把背景颜色设置为红色,然后又调用了next()方法,其返回的结果将作为参数传给回调函数。添加的这个next ()方法可以让队列在中断的地方再接续起来,然后再与后续的.slideDown('slow')连缀在一起。如果在此不使用next()方法,动画就会中断。
在下面讨论多组元素的效果之后,我们会介绍另一种向队列中添加非效果方法的方式。

4.4 创建自定义动画

除了预置的效果方法外,jQuery还提供了一个强大的.animate()方法,用于创建控制更加精细的自定义动画。

animate方法类型

.animate()方法有两种形式,第一种形式接收以下4个参数。

  1. 一个包含样式属性及值的对象:与本章前面讨论的.css()方法中的参数类似。
  2. 可选的时长参数:既可以是预置的字符串,也可以是毫秒数值。
  3. 可选的缓动(easing)类型:现在我们先不介绍,这是第11章中将要讨论的一个高级选项。
  4. 可选的回调函数:将在本章后面讨论。把这4个参数放到一起,结果如下所示:
    1
    2
    3
    4
    5
    .animate({property1: 'value1', property2: 'value2'},  
    duration, easing, function() {
    alert('The animation is finished.');
    }
    );

    类型2

    第二种形式接受两个参数,一个属性对象和一个选项对象:
    1
    .animate({properties}, {options}) 
    实际上,这里的第二个参数是把第一种形式的第2~4个参数封装在了另一个对象中,同时又添加了两个选项。考虑到可读性并调整了换行之后,调用第二种形式的方法的代码如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    .animate(
    {
    property1: 'value1',
    property2: 'value2'
    },
    {
    duration: 'value',
    easing: 'value',
    specialEasing:
    {
    property1: 'easing1',
    property2: 'easing2'
    },
    complete: function ()
    {
    alert('The animation is finished.');
    },
    queue: true,
    step: callback
    }
    );
    现在,我们使用第一种形式的.animate()方法,但在本章后面介绍排队效果时会使用其第二种形式。

    4.4.1 手工创建效果

    现在,我们已经介绍了几个用于显示和隐藏元素的预定义方法。为了讨论.animate()方法,有必要看一看怎么通过这个低级接口来实现与调用.slideToggle()相同的效果。在此,我们把前面例子中调用.slideToggle()方法的代码替换成了自定义动画代码,参见代码清单4-13。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    $(document).ready(function() {
    var $speech = $('div.speech');
    var defaultSize = $speech.css('fontSize');
    $('#switcher button').click(function() {
    var num = parseFloat($speech.css('fontSize'));
    switch (this.id) {
    case 'switcher-large':
    num *= 1.4;
    break;
    case 'switcher-small':
    num /= 1.4;
    break;
    default:
    num = parseFloat(defaultSize);
    }
    $speech.css('fontSize', num + 'px');
    });

    var $firstPara = $('p').eq(1);
    $firstPara.hide();
    $('a.more').click(function(event) {
    event.preventDefault();
    // 自定义动画
    $firstPara.animate({height: 'toggle'}, 'slow');

    var $link = $(this);
    if ($link.text() == 'read more') {
    $link.text('read less');
    } else {
    $link.text('read more');
    }
    });
    });
    通过这个例子可以看出,.animate()方法针对CSS属性提供了方便简写值:'show''hide''toggle', 以便在简写方法不适用时提供另一种简化.slideToggle()等内置效果方法的方式。

    4.4.2 一次给多个属性添加动画效果

    使用.animate()方法可以同时修改多个CSS属性。例如,要在切换第二个段落时,创建一个同时具有滑动和淡入淡出效果的动画,只需在.animate()方法的属性对象参数中添加一个opacity属性值对即可,参见代码清单4-14。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    var $firstPara = $('p').eq(1);
    $firstPara.hide();
    $('a.more').click(function (event) {
    event.preventDefault();
    $firstPara.animate({
    opacity: 'toggle',
    height: 'toggle'
    }, 'slow');
    var $link = $(this);
    if ($link.text() == 'read more') {
    $link.text('read less');
    } else {
    $link.text('read more');
    }
    });
    此外,不仅可以在简写效果方法中使用样式属性,也可以使用其他CSS属性,如:lefttopfontSizemarginpaddingborderWidth。还记得改变演讲段落文本大小的脚本吗?要实现同样的文本大小变化动画,只要把.css()方法替换成.animate()方法即可,参见代码清单4-15。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    $(document).ready(function() { 
    var $speech = $('div.speech');
    var defaultSize = $speech.css('fontSize');
    $('#switcher button').click(function() {
    var num = parseFloat($speech.css('fontSize'));
    switch (this.id) {
    case 'switcher-large':
    num *= 1.4;
    break;
    case 'switcher-small':
    num /= 1.4;
    break;
    default:
    num = parseFloat(defaultSize);
    }
    $speech.animate({fontSize: num + 'px'}, 'slow');
    });
    });
    再使用其他属性,则可以创造出更复杂的效果。例如,可以在把某个项从页面左侧移动到右侧的同时,让该项的高度增加20像素并使其边框宽度增加到5像素。下面,我们就把这个效果应用于<div id="switcher">盒子。图4-8显示了应用效果之前的画面。
    在可变宽度的布局中,需要计算盒子在与页面右侧对齐之前应该移动的距离。假设段落宽度为100%,可以从段落宽度中减去Text Size盒子的宽度。我们使用jQuery.outWidth()方法来计算宽度,包括内边距及边框宽度。我们还使用这个方法计算转换器新的left属性。对于这个例子而言,我们打算通过单击按钮上面的Text Size文本来触发动画,参见代码清单4-16。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    $(document).ready(function() { 
    $('div.label').click(function() {
    var paraWidth = $('div.speech p').outerWidth();
    var $switcher = $(this).parent();
    var switcherWidth = $switcher.outerWidth();
    $switcher.animate({
    borderWidth: '5px',
    left: paraWidth - switcherWidth,
    height: '+=20px'
    }, 'slow');
    });
    });
    在此,有必要详细解释一下这些动画属性。首先,borderWidth属性很明显,只要给它指定一个常量值加一个单位即可,就像在样式表中一样。其次,left属性是计算的数值。这些属性值的单位后缀是可选的,如果不指定,就会默认以px作为单位。最后,height属性使用我们以前没有遇到过的语法,其中属性值前面的+=操作符表示相对值。在这里表示的意思不是以动画方式变化到20像素,而是在原来基础上再以动画方式变化20像素。因为涉及特殊字符问题,所以必须以字符串形式指定相对值,也就是说必须把值放到一对括号内。

4.3.4 切换可见性

有时候,我们需要切换某些元素的可见性,而不像前面例子中那样只把它们显示出来。要实现切换,可以先检查匹配元素的可见性,然后再添加适当的方法。在此,仍然以淡入淡出效果为例,可以把示例脚本修改为如代码清单4-11所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$(document).ready(function() { 
var $firstPara = $('p').eq(1);
$firstPara.hide();
$('a.more').click(function(event) {
event.preventDefault();
if ($firstPara.is(':hidden')) {
$firstPara.fadeIn('slow');
$(this).text('read less');
} else {
$firstPara.fadeOut('slow');
$(this).text('read more');
}
});
});

与我们在本章前面所做的一样,首先缓存选择符以避免重复遍历DOM。而且,这里也不再隐藏被单击的链接,而是修改它的文本。
使用if else语句切换元素的可见性是非常自然的方式。但通过jQuery复合效果方法,却不一定非要使用这个条件语句(尽管在这个例子中,需要条件语句来修改链接的文本) 。

jQuery提供了一个.toggle()方法,该方法的作用类似于.show().hide()方法,而且与它们一样的是,.toggle()方法时长参数也是可选的。
另一个复合方法是.slideToggle(),该方法通过逐渐增加或减少元素高度来显示或隐藏元素。代码清单4-12是使用.slideToggle()方法的脚本。

如何切换可见性

1.使用if else语句判断可见性,然后在调用显示,或隐藏方法实现
2.使用jQuery事项的方法实现,如下所示:

  • .toggle()方法可以可以切换显示/隐藏效果,可以理解为:交替执行.show().hide()这两个方法.
  • .slideToggle()方法可以切换滑上/滑下效果,可以理解为:交替执行:.slideDown().slideUp()这两个方法
  • .fadeToggle()方法可以切换淡入/淡出效果,可以理解为交替执行.fadeIn(),.fadeOut()这两个方法

4.3.3 滑上和滑下

文档流之外的元素适合淡入淡出

对于本来就处于文档流之外的元素,比较适合使用淡入和淡出动画。例如,对于那些覆盖在页面之上的”亮盒”元素来说,采用淡入和淡出就显得很自然。

不过,假如某个元素本来就处在文档流中,那再调用.fadeIn()就会导致文档”跳一下”,以便为新元素腾出地方来。但这种跳跃感在用户眼里就不总是那么美观了。

滑上滑下

,假如某个元素本来就处在文档流中,使用jQuery.slideDown().slideUp()方法通常是正确的选择。这两个动画方法仅改变元素的高度。要让段落以垂直滑入的效果出现,可以像代码清单4-10这样调用.slideDown('slow')

1
2
3
4
5
6
7
8
$(document).ready(function() { 
$('p').eq(1).hide();
$('a.more').click(function(event) {
event.preventDefault();
$('p').eq(1).slideDown('slow');
$(this).hide();
});
});

要实现相反的动画效果,应该调用.slideUp()

两个方法的效果

  • .slideDown()向下滑动,最终的效果是显示.
  • .slideUp()向上滑动,最终的效果是隐藏

4.3.2 淡入和淡出

虽然使用.show().hide()方法在某种程度上可以创造漂亮的效果,但其效果有时候也可能会显得过于花哨。考虑到这一点,jQuery也提供了两个更为精细的内置动画方法。

逐渐地增大不透明度

如果想在显示整个段落时,只是逐渐地增大其不透明度,那么可以使用.fadeIn('slow')方法,.fadeIn()方法会在一开始设置段落的尺寸,以便内容能够逐渐显示出来。

逐渐减少不透明度

类似地,要逐渐减少不透明度,可以使用.fadeOut()方法。

两个方法的效果

  • .fadeIn()方法淡入,表示慢慢的进入显示器,也就是显示的意思
  • .fadeOut()方法表示淡出,表示慢慢的移出显示器,也就是隐藏的意思

4.3.1 指定显示速度

使用字符串指定速度

对于jQuery提供的任何效果,都可以指定两种预设的速度参数:’slow'和’fast'

  • 使用.show('slow')会在600毫秒(0.6秒)内完成效果,
  • .show('fast')则是200毫秒(0.2秒)。
  • 如果传入的是其他字符串,jQuery就会在默认的400毫秒(0.4)秒内完成效果。

使用毫秒数指定速度

要指定更精确的速度,可以使用毫秒数值,例如.show(850)。注意,与字符串表示的速度参数名称不同,数值不需要使用引号。