JavaScript-学习笔记(8):动画效果
动画是 CSS-DOM 最具动感的内容,让网页上的元素动起来。
基础知识
js 可以按照预定的时间重复调用函数,,这意味着我们可以随着时间的失衡而不断改变某个元素的样式。
位置:网页元素在浏览器窗口里的位置是一种表示性信息,一般由 CSS 负责。例如:
1
2
3
4
5element {
position: absolute;
top: 50px;
left: 100px;
}下面的 DOM 代码实现了同样的效果:
1
2
3element.style.position = "absolute";
element.style.top = "50px";
element.style.left = "100px";假设有这么一个元素:
1
<p id="message">Wheel</p>
再用 js 设置位置:
1
2
3
4
5
6
7
8function positionMessage() {
if (!document.getElementById) return false;
if (!document.getElementById("message")) return false;
var elem = document.getElementById("message");
elem.style.position = "absolute";
elem.style.left = "50px";
elem.style.top = "100px";
}添加到
windows.onload:1
2
3
4
5
6
7
8
9
10
11
12function addLoadEvent(func) {
var oldonload = window.onload;
if (typeof window.onload != "function") {
window.onload = func;
} else {
window.onload = function () {
oldonload();
func();
};
}
}
addLoadEvent(positionMessage);结果:

再编写一个移动的函数:
1
2
3
4
5
6function moveMessage() {
if (!document.getElementById) return false;
if (!document.getElementById("message")) return false;
var elem = document.getElementById("message");
elem.style.left = "200px";
}结果:

这是立即改变位置,要想获得动画效果,还要让位置随着时间的改变而不断改变。
时间
js 的 setTimeout 能够让某个经过一段预定的时间后才开始执行。
用法:1
variable = setTimeout("function", interval);
可以用 clearTimeout 的函数来消 “等待执行”队列的某个函数。
1
clearTimeout(variable);
修改 postitonMessage 函数,让其在 5 秒后才调用 moveMessage 函数。
1
2
3
4
5
6
7
8
9function positionMessage() {
if (!document.getElementById) return false;
if (!document.getElementById("message")) return false;
var elem = document.getElementById("message");
elem.style.position = "absolute";
elem.style.left = "50px";
elem.style.top = "100px";
movement = setTimeout("moveMessage()", 5000);
}positionMessage 依旧在加载时调用。
时间递增量
动画应该是一个渐变的过程,元素应该从出发点逐步移动到目的点,而不是一下子跳过去。
更新 moveMessage 函数,让元素以逐渐移动的方式发生。步骤:
- 获得元素的当前位置;
- 如果元素已经到达目的地,则退出;
- 如果元素尚未到达它的目的地,则把它向目的地移动;
- 一段时间后重复上述步骤。
获取位置的值:
1
2var xpos = parseInt(elem.style.left);
var ypos = parseInt(elem.style.top);只有使用 DOM 脚本或 style 属性分配位置后,这里的 parseInt 才起作用。
编辑 moveMessage 函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18function moveMessage() {
if (!document.getElementById) return false;
if (!document.getElementById("message")) return false;
var elem = document.getElementById("message");
var xpos = parseInt(elem.style.left);
var ypos = parseInt(elem.style.top);
// 如果相等就退出递归
if (xpos == 200 && ypos == 100) return true;
// 如果不相等就继续加减
if (xpos < 200) xpos++;
if (xpos > 200) xpos--;
if (ypos < 100) ypos++;
if (ypos > 100) ypos--;
elem.style.left = xpos + "px";
elem.style.top = ypos + "px";
// 每隔10毫秒递归
movement = setTimeout("moveMessage()", 10);
}抽象
上面的代码只能完成特定的函数,如果把里面的常数改为变量,那么就通用性就会大大提升。
将移动元素的 ID、元素的目的地的位置,以及停顿间隔改为变量。
定义一个新的函数function moveElement(elementID, final_x, final_y, interval),具体代码为。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
29function moveElement(elementID, final_x, final_y, interval) {
if (!document.getElementById) return false;
if (!document.getElementById(elementID)) return false;
var elem = document.getElementById(elementID);
var xpos = parseInt(elem.style.left);
var ypos = parseInt(elem.style.top);
// 如果相等就退出递归
if (xpos == final_x && ypos == final_y) return true;
// 如果不相等就继续加减
if (xpos < final_x) xpos++;
if (xpos > final_x) xpos--;
if (ypos < final_y) ypos++;
if (ypos > final_y) ypos--;
elem.style.left = xpos + "px";
elem.style.top = ypos + "px";
// 每隔 interval 毫秒递归
var reqpeat =
"moveElement('" +
elementID +
"'," +
final_x +
"," +
final_y +
"," +
interval +
")";
movement = setTimeout(reqpeat, interval);
}使用 moveElement 函数。
创建一个名为 message.html 的文件,包含一个id=message的 p 元素:1
2
3
4
5
6
7
8
9
10
11
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Position</title>
</head>
<body>
<p id="message">Wheel</p>
</body>
</html>再编写一个 positionMessage.js 的文件,添加 positionMessage() 函数:
id=message2的 p 元素,1
2
3
4
5
6
7
8
9
10
11function positionMessage() {
if (!document.getElementById) return false;
if (!document.getElementById("message")) return false;
var elem = document.getElementById("message");
elem.style.position = "absolute";
elem.style.left = "50px";
elem.style.top = "100px";
moveElement("message", 200, 100, 10);
}
addLoadEvent(positionMessage);再在 HTML 文件上添加相对应的 js 文件,就可以显示出简单的动画效果。
改变 interval 的值即可改变动画的速度,改变 x 和 y 的值即可改变最终位置。添加一个
id=message2的 p 元素,<p id="message2">Whoal</p>,再在 positionMessage 函数中添加相对应的操作:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16function positionMessage() {
if (!document.getElementById) return false;
if (!document.getElementById("message")) return false;
var elem = document.getElementById("message");
elem.style.position = "absolute";
elem.style.left = "50px";
elem.style.top = "100px";
var elem_2 = document.getElementById("message2");
elem_2.style.position = "absolute";
elem_2.style.left = "50px";
elem_2.style.top = "50px";
moveElement("message", 125, 25, 10);
moveElement("message2", 125, 125, 20);
}结果:

实用的动画
上面的动画用处不大,还有可能使用户厌烦。
提出问题:有一个怨念一系列链接的网页。当用用户的鼠标指针悬停在某个链接上时,展示预览一张图片。
添加一个文件 list.html,代码为:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Web Design</title>
</head>
<body>
<h1>Web Design</h1>
<p>These are the things you should know:</p>
<ol id="linklist">
<li>
<a href="./structure.html">structure</a>
</li>
<li>
<a href="./presentation.html">presentation</a>
</li>
<li>
<a href="./behavior.html">behavior</a>
</li>
</ol>
</body>
</html>这次需要在 onmouseover 事件被触发时显示一张图片。
解决问题:
- 为所有的预览图片生成一张“集体照”形式的图片;
- 隐藏这张集体照图片的绝大部分。
- 当用户把鼠标指针悬停在某个链接上方时,只显示这张“集体照”图片相应的部分。
集体照:

添加到 list.html 最后面:1
<img src="./topic.png" alt="building blocks of web Design" id="preview" />
CSS
css 的 overflow 属性用来处理一个元素的尺寸超出其容器尺寸的情况。
overflow 属性有 4 种取值:visible(不作任何改动)、hidden(将溢出部分隐藏)、scroll(隐藏,但提供一个滚动条)和 auto(类似 scroll,但只有溢出时才显示滚动条)。这里选取 hidden 属性。
添加 div,将 img 包进去,再添加对应的样式:
1
2
3<div id="slideshow">
<img src="./topic.png" alt="building blocks of web Design" id="preview" />
</div>1
2
3
4
5
6#slideshow {
width: 130px;
height: 100px;
position: relative;
overflow: hidden;
}接下来解决的时,让网页对用户的操作做出正确响应,当鼠标指针指在某个地方时,把对应的图片显示出来。
JavaScript
编写一个 prepareSlideshow 来完成工作。
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
30function prepareSlideshow() {
if (!document.getElementsByTagName) return false;
if (!document.getElementById) return false;
if (!document.getElementById("linklist")) return false;
if (!document.getElementById("preview")) return false;
// 为图片应用样式
var preview = document.getElementById("preview");
preview.style.position = "absolute";
preview.style.left = "0px";
preview.style.top = "0px";
var list = document.getElementById("linklist");
var links = list.getElementsByTagName("a");
links[0].onmouseover = function () {
moveElement("preview", -100, 0, 10);
};
links[1].onmouseover = function () {
moveElement("preview", -200, 0, 10);
};
links[2].onmouseover = function () {
moveElement("preview", -300, 0, 10);
};
}
addLoadEvent(prepareSlideshow);将 js 文件添加到 html 文件,运行后将鼠标移到对应的链接上会出来动画效果:

但是来回移动时会出现混乱的情况。
变量的作用域
动画效果出错是由全局变量引起的。
1
movement = setTimeout(reqpeat, interval);
但是直接使用局部变量也会出错,需要一种介乎二者之间的东西,也就是属性。
js 允许为元素创建属性:1
element.property = value;
所以更改为属性即可解决问题:
1
elem.movement = setTimeout(reqpeat, interval);
改进动画效果:
每次移动 1px,速度未免过慢。
可以设置每次前进这个距离的十分之一。1
2dist = (final_x - xpos) / 10;
xpos = xpos + dist;当差距小于 10 的时候,用这个差距除 10 的结果小于 1,将移动不了。
解决方法是使用 Math 对象的 ceil 函数,它可以返回一个不小于 dist 的值的一个整数,用法:1
Math.ceil(number);
最终代码:
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
34
35
36
37
38
39
40
41
42
43
44function moveElement(elementID, final_x, final_y, interval) {
if (!document.getElementById) return false;
if (!document.getElementById(elementID)) return false;
var elem = document.getElementById(elementID);
// 声明 dist
var dist = 0;
var xpos = parseInt(elem.style.left);
var ypos = parseInt(elem.style.top);
// 如果相等就退出递归
if (xpos == final_x && ypos == final_y) return true;
// 如果不相等就继续加减
if (xpos < final_x) {
dist = Math.ceil((final_x - xpos) / 10);
xpos = xpos + dist;
}
if (xpos > final_x) {
dist = Math.ceil((xpos - final_x) / 10);
xpos = xpos - dist;
}
if (ypos < final_y) {
dist = Math.ceil((final_y - ypos) / 10);
ypos = ypos + dist;
}
if (ypos > final_y) {
dist = Math.ceil((ypos - final_y) / 10);
ypos = ypos - dist;
}
elem.style.left = xpos + "px";
elem.style.top = ypos + "px";
// 每隔 interval 毫秒递归
var reqpeat =
"moveElement('" +
elementID +
"'," +
final_x +
"," +
final_y +
"," +
interval +
")";
// movement = setTimeout(reqpeat, interval);
elem.movement = setTimeout(reqpeat, interval);
}添加安全检查
诸如 elem.style.left 是否存在等。生成 HTML 标记:
一些内容是为了存在动画效果而存在的,但是如果不支持 js,那么那些内容留在那里岂不是很尴尬,所以干脆让 js 来生成这些标记。
小结
实现动画效果并不难,问题是尖不应该使用动画。
动画技术可以让我们创建出很多非常酷的效果,但四处移动的元素用处不大。现在有一个通用性的函数,在有必要创建动画效果时帮上大忙。