JavaScript学习总结【11】、JS 运动

时间:2022-12-11 19:26:58

  动画效果在网站中是一种非常常见的交互式体验效果,比如侧边栏分享、图片淡入淡出,我们把这种动画效果就叫做运动,也就是让物体动起来。如果想让一个物体动起来,无非就是改变它的速度,也就是改变属性值,比如 left、right、width、height、opacity ,那么既然是运动,就可以分为很多种,如匀速运动、缓冲运动、多物体运动、任意值运动、链式运动和同时运动。我们从最简单的动画开始,如何让单个物体运动,逐步深入多物体运动、多动画同时运动到实现完美运动框架的封装,在这个过程中,每一个运动都封装为一个函数,可以更好的培养和锻炼我们的编程思想,增强逻辑思维。

1、简单运动

  简单运动的实现都是匀速运动,顾名思义就是运动速度不变,通过宽、高、透明度等的变化,实现简单的动画效果,下面我们就让一个 div 运动起来

 <!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>简单运动</title>
<style>
*{margin:0;padding:0;}
#div1{
width:200px;
height:200px;
background:green;
position:absolute;
}
</style>
<script>
function startMove(){
var oDiv = document.getElementById('div1');
setInterval(function (){
oDiv.style.left = oDiv.offsetLeft + 10 + 'px';
},30);
}
</script>
</head>
<body>
<input type="button" value="动起来" onclick="startMove()">
<div id="div1"></div>
</body>
</html>

  让一个 div 动起来,只需要开一个定时器,用于定义速度,告诉物体运动的快慢,上面的代码,当点击按钮后,div 每隔 30毫秒 从左向右运动 10像素。

  这里需要注意,让 div 向右运动,在定义样式时,一定要给运动的物体加绝对定位,也就是相对于哪个位置进行运动,offsetLeft 代表物体的当前位置,所以每次运动,都是给当前的 offsetLeft 加 10像素。

  虽然是让 div 动起来了,但是问题多多,动起来后根本停不下来,这样就太任性了,而且当重复点击的话,运动速度还会加快,这都不是我们想要的,下面就我们就让他停止在指定位置处

 <!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>简单运动</title>
<style>
*{margin:0;padding:0;}
#div1{
width:200px;
height:200px;
background:green;
position:absolute;
}
</style>
<script>
var timer = null;
function startMove(){
var oDiv = document.getElementById('div1');
clearInterval(timer);
timer = setInterval(function (){
if(oDiv.offsetLeft == 300){
clearInterval(timer);
}
else{
oDiv.style.left = oDiv.offsetLeft + 10 + 'px';
}
},30);
}
</script>
</head>
<body>
<input type="button" value="动起来" onclick="startMove()">
<div id="div1"></div>
</body>
</html>

  上面的代码,点击按钮后,div 从左向右运动到 300像素 时停止运动。

  若要停止一个物体的运动,只要关闭定时器即可,也就是判断 div 的 offsetLeft 值是否等于 300,若等于 300 则清空定时器。这里需要注意,在做判断时,一定要给运动位置加 else,判断语句为二选一,成立或者不成立时执行,这样再点击按钮就不会运动了,否则当 div 运动到 300像素 时,再点击按钮,div 还会向右移动 10像素,虽然在到达 300像素 时已经关闭了定时器,但是按钮的点击事件,还会执行一次函数,所以加了 else 之后,当再点击按钮时,这时候条件已经成立了,也就不会再执行 else 中的语句了。

  重复点击按钮,运动速度会不断加快,是因为每点击一次按钮,startMove 函数被执行一次,多次点击,也就相当于开了多个定时器,所以需要在执行 startMove 函数时,首先清空定时器,这样重复点击按钮时,会先把之前运行的定时器关闭,再开一个新的定时器运行,这就保证了始终是一个定时器在工作。

  下面我们看两个简单动画的实例:

  实例:侧边栏分享

  实现思路:网站侧边栏菜单是最常见的动画效果,该效果在初始时,只显示一个按钮或者菜单项,当鼠标移上去时,滑出隐藏部分,展示内容。该效果在做布局时,主要用绝对定位实现隐藏,left 的值为内容容器 width 的值,该值为负值,动画效果实现就是改变它的 offsetLeft 的值,当鼠标移入时,增加 offsetLeft 的值,值为 0 时停止运动,当鼠标移开时,offsetLeft 的值从 0 减小到 left 的值。

 <!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>侧边栏分享</title>
<style>
*{margin:0;padding:0;}
#div1{
width:150px;
height:210px;
background:lightgreen;
position:absolute;
left:-150px;
}
#div1 span{
width:20px;
height:60px;
line-height:20px;
color:white;
background:green;
position:absolute;
right:-20px;
top:70px;
}
</style>
<script>
window.onload = function (){
var oDiv = document.getElementById('div1');
oDiv.onmouseover = function (){
startMove();
};
oDiv.onmouseout = function (){
stopMove();
};
};
var timer = null;
function startMove(){
var oDiv = document.getElementById('div1');
clearInterval(timer);
timer = setInterval(function (){
if(oDiv.offsetLeft == 0){
clearInterval(timer);
}
else{
oDiv.style.left = oDiv.offsetLeft + 10 + 'px';
}
},30);
}
function stopMove(){
var oDiv = document.getElementById('div1');
clearInterval(timer);
timer = setInterval(function (){
if(oDiv.offsetLeft == -150){
clearInterval(timer);
}
else{
oDiv.style.left = oDiv.offsetLeft - 10+ 'px';
}
},30);
}
</script>
</head>
<body>
<div id="div1">
<span>分享到</span>
</div>
</body>
</html>

  上面的代码,当鼠标移入"分享到",隐藏的 div 即内容容器每隔 30毫秒 从左向右运动 10像素,offsetLeft 值为 0 时停止运动,当鼠标移开时,显示的 div 每隔 30毫秒 从右向左移动 5像素,offsetLeft 值为 -150 时停止运动。这里要注意的是,offsetLeft 值的变化,从左向右运动时,值为正值,也就是 +10,从右向左运动时,值为负值,也就是 -10。

  我们可以看到,startMove 和 stopMove 都有相同的代码结构,如果代码中存在大致相同的代码,就可以对代码进行优化,上面的代码,就只有 offsetLeft 的移动位置 和 停止位置不同,因此可以用函数传参的方式将上面的代码简化为:

 <script>
window.onload = function (){
var oDiv = document.getElementById('div1');
oDiv.onmouseover = function (){
startMove(10, 0);
};
oDiv.onmouseout = function (){
startMove(-10, -150);
};
};
var timer = null;
function startMove(speed, iTarget){
var oDiv = document.getElementById('div1');
clearInterval(timer);
timer = setInterval(function (){
if(oDiv.offsetLeft == iTarget){
clearInterval(timer);
}
else{
oDiv.style.left = oDiv.offsetLeft + speed + 'px';
}
},30);
}
</script>

  移动位置也就是速度,用 speed 参数传入,停止位置也就是目标位置,用 iTarget 参数传入。在功能相同的情况下,一个函数传入的参数越少越好,那么还可以简化为:

 <script>
window.onload = function (){
var oDiv = document.getElementById('div1');
oDiv.onmouseover = function (){
startMove(0);
};
oDiv.onmouseout = function (){
startMove(-150);
};
};
var timer = null;
function startMove(iTarget){
var oDiv = document.getElementById('div1');
clearInterval(timer);
timer = setInterval(function (){
var speed = 0;
if(oDiv.offsetLeft > iTarget){
speed = -10;
}
else{
speed = 10;
}
if(oDiv.offsetLeft == iTarget){
clearInterval(timer);
}
else{
oDiv.style.left = oDiv.offsetLeft + speed + 'px';
}
},30);
}
</script>

  速度值和目标值,目标值肯定是不能省略的,就好比坐火车,买票肯定得有一个终点,所以速度值可以被省略掉,不管是动车还是普通车,都会到达终点,不同的就是速度的快慢。首先让速度值等于 0,再做一个判断,判断目标值与物体当前位置的关系,如果 offsetLeft 值大于目标值,那么就要从右向左运动,所以为负值,否则,也就是目标值大于 offsetLeft 值,这说明此时物体是隐藏的,那么就从左向右运动,速度值为正值。

  侧边栏分享效果如果用缓冲运动做的话,效果更好。

  实例:淡入淡出

  淡入淡出效果是鼠标移入移出改变透明度,透明度动画也属于运动效果,可以使用上例中 startMove 框架完成这种效果。

 <!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>淡入淡出</title>
<style>
#div1{
width:200px;
height:200px;
background:red;
filter:alpha(opacity:30);
opacity:0.3;
}
</style>
<script>
window.onload = function (){
var oDiv = document.getElementById('div1');
oDiv.onmouseover = function (){
startMove(100);
};
oDiv.onmouseout = function (){
startMove(30);
};
};
var alpha = 30; //将透明度值存储在变量中
var timer = null;
function startMove(iTarget){
var oDiv = document.getElementById('div1');
clearInterval(timer);
timer = setInterval(function (){
var speed = 0;
if(alpha > iTarget){
speed = -10;
}
else{
speed = 10;
}
if(alpha == iTarget){
clearInterval(timer);
}
else{
alpha += speed; //透明度值增加效果
oDiv.style.opacity = alpha/100; //高版本滤镜为:0.1-1,所以除以100
oDiv.style.filter = 'alpha(opacity:'+ alpha +')'; //IE低版本浏览器使用
}
},30);
}
</script>
</head>
<body>
<div id="div1"></div>
</body>
</html>

  改变透明度的值,可没有 offsetalpha 这样的写法,透明度值没有直接的属性可以改变,但是可以将透明度值存储在变量中,通过判断变量的值和目标值之间的关系,就可以确定速度值,有了速度值之后,再将这个速度值通过变量赋值给他,就可以达到改变透明度值的效果。

  

2、缓冲运动

  匀速运动的速度始终都是不变的,而缓冲运动的速度则是逐渐变慢,最后停止的,就像火车一样,出站后速度都是很快的,距离目标位置越近,速度会逐渐变慢,到站后停止,也就是缓冲运动的速度是由距离决定的,速度和距离成正比的,距离越远,速度越大。

  那么,缓冲运动的速度就可以用这个公式表示:速度 = (目标值 - 当前值)/缩放系数

  何为缩放系数,为什么要除以缩放系数,我们看下面的实例。

 <!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>缓冲运动</title>
<style>
#div1{
width:100px;
height:100px;
background:green;
position:absolute;
left:0;
top:50px;
}
#div2{
width:1px;
height:300px;
background:black;
position:absolute;
left:300px;
top:0;
}
</style>
<script>
function startMove(){
var oDiv = document.getElementById('div1');
setInterval(function (){
var speed = 300 - oDiv.offsetLeft;
oDiv.style.left = oDiv.offsetLeft + speed + "px";
},30);
}
</script>
</head>
<body>
<input type="button" value="动起来" onclick="startMove()">
<div id="div1"></div>
<div id="div2">300px处</div>
</body>
</html>

  上面的代码,在点击按钮后,让 div 运动到 300像素 处停止,可以看到 div 直接就跳到 300像素 处了,他的运动速度等于目标位置减去当前位置,初始位置为 0,所以点击按钮后,一瞬间就跳到 300像素 处了。

  速度太大了,就没有缓冲的效果,所以要除以缩放系数,就是让他有一个缓冲的过程,我们给他除以 10,初始位置 0,点击按钮后,速度为 30,当在最后靠近 300像素 处,速度就为 0,距离越小,则速度就越小。

var speed = (300 - oDiv.offsetLeft)/10;

  这样虽然是达到了缓冲的效果,但是可以很明显的看到,div 并不是停在 300像素 处,还差那么一点,打开调试工具可以看到,此时的 left 值为 296.4px,像素的最小单位是 px,我们平时写代码时,不可能这么写 100.5px 或者 200.9px,出现这样的情况就是因为这句代码:oDiv.style.left = oDiv.offsetLeft + speed + "px",也就是 div 的 left 值是当前值加速度值,所以 296 就是当前的 left 值,296 到 300 差 4,再除以缩放系数 10,就是 0.4,因此 div 的 left 值就为296.4px,这并不是我们想要的,速度不能为小数,解决方法也很简单,那就是向上取整。

speed = Math.seil(speed);

  那么,问题又来了,如果 div 的初始值是在 600像素 处,这时候就是从左向右运动,速度小于 0,也就是速度为负值,向上取整之后,离要运动到的 300px 处还差一点,这时候需要向下取整,但是向下取整之后,初始值为为 0 时,运动到 300px 处还是差一点,这时候就需要做判断,当速度大于 0 时,速度为正,向上取整,如果小于 0 时,速度为负,则向下取整。

 <!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>缓冲运动</title>
<style>
#div1{
width:100px;
height:100px;
background:green;
position:absolute;
left:0;
top:50px;
}
#div2{
width:1px;
height:300px;
background:black;
position:absolute;
left:300px;
top:0;
}
</style>
<script>
function startMove(){
var oDiv = document.getElementById('div1');
setInterval(function (){
var speed = (300 - oDiv.offsetLeft)/10;
speed = speed>0 ? Math.ceil(speed) : Math.floor(speed);
oDiv.style.left = oDiv.offsetLeft + speed + "px";
},30);
}
</script>
</head>
<body>
<input type="button" value="动起来" onclick="startMove()">
<div id="div1"></div>
<div id="div2">300px处</div>
</body>
</html>

  

  这里一定要注意,缓冲运动一定要取整,否则就到不了目标位置。下面是一个用缓冲运动做的侧边栏分享效果

 <!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>缓冲分享</title>
<style>
*{margin:0;padding:0;}
#div1{
width:150px;
height:210px;
background:lightgreen;
position:absolute;
left:-150px;
}
#div1 span{
width:20px;
height:60px;
line-height:20px;
color:white;
background:green;
position:absolute;
right:-20px;
top:70px;
}
</style>
<script>
window.onload = function (){
var oDiv = document.getElementById('div1');
oDiv.onmouseover = function (){
startMove(0);
};
oDiv.onmouseout = function (){
startMove(-150);
};
};
var timer = null;
function startMove(iTarget){
var oDiv = document.getElementById('div1');
clearInterval(timer);
timer = setInterval(function (){
var speed = (iTarget - oDiv.offsetLeft)/10;
speed = speed>0 ? Math.ceil(speed) : Math.floor(speed);
if(oDiv.offsetLeft == iTarget){
clearInterval(timer);
}
else{
oDiv.style.left = oDiv.offsetLeft + speed + 'px';
}
},30);
}
</script>
</head>
<body>
<div id="div1">
<span>分享到</span>
</div>
</body>
</html>

  缓冲运动的停止条件是当两点重合时,也就是物体当前的 left 值等于目标值,而匀速运动的停止条件是距离足够近时,也就是物体当前的 left 值大于或小于目标值,再强行让他的 left 值等于目标值,下面我们通过一个实例来更好的理解。

 <!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>运动的停止</title>
<style>
#div1{
width:100px;
height:100px;
background:green;
position:absolute;
left:600px;
top:50px;
}
#div2{
width:1px;
height:300px;
background:black;
position:absolute;
left:200px;
top:0;
}
#div3{
width:1px;
height:300px;
background:black;
position:absolute;
left:400px;
top:0;
}
</style>
<script>
var timer = null;
function startMove(iTarget){
var oDiv = document.getElementById('div1');
clearInterval(timer);
timer = setInterval(function (){
var speed = 0;
if(oDiv.offsetLeft > iTarget){
speed = -7;
}
else{
speed = 7;
}
if(Math.abs(iTarget - oDiv.offsetLeft) <= 7){
clearInterval(timer);
oDiv.style.left = iTarget + 'px';
}
else{
oDiv.style.left = oDiv.offsetLeft + speed + "px";
}
},30);
}
</script>
</head>
<body>
<input type="button" value="到200" onclick="startMove(200)">
<input type="button" value="到400" onclick="startMove(400)">
<div id="div1"></div>
<div id="div2">200px处</div>
<div id="div3">400px处</div>
</body>
</html>

  上面的代码,点击 "到200" 按钮,div 从右向左每隔 30毫秒 运动 7像素,速度为负,运动到 200像素 处停止,再点击 "到400" 按钮,div 从左向右每隔 30毫秒 运动 7像素,速度为正,运动到 400像素 处停止。匀速运动的速度始终是保持不变的,这里的运动速度为 7,初始位置运动到目标位置的移动速度除不尽,这样就会出现左右徘徊的情况,也就是 div 在目标点左右闪动,原因是前进一个 7,比目标位置大了,后退一个 7,比目标位置小了,所有会出现这种现象。解决办法就是使用绝对值进行判断,因为他的距离可能是正7,也可能是负7。那么使用绝对值做判断,目标位置减去物体当前位置若小于等于 7,就算到了,这时候问题就来了,虽然到了不闪动了,但是可以很明显看到还有一点距离,所以最关键的一步,就是在清空定时器后,直接让 div 的 left 值等于目标点的值,这样就完成了匀速运动的停止。

  

3、多物体运动和任意值运动

  (1)、多物体运动

  我们之前做的都是单个物体的运动,而在网站中,并不是单个物体在运动,而是多个物体的运动,下面我们就套用之前的 startMove 框架让多个 div 运动起来

 <!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>多个div变宽</title>
<style>
div{
width:100px;
height:50px;
background:green;
margin:10px;
}
</style>
<script>
window.onload = function (){
var aDiv = document.getElementsByTagName('div');
for(var i=0; i<aDiv.length; i++){
aDiv[i].onmouseover = function (){
startMove(this,400);
};
aDiv[i].onmouseout = function (){
startMove(this,100);
};
}
};
var timer = null;
function startMove(obj,iTarget){
clearInterval(timer);
timer = setInterval(function (){
var speed=(iTarget - obj.offsetWidth)/6;
speed = speed>0 ? Math.ceil(speed) : Math.floor(speed); if(obj.offsetWidth == iTarget){
clearInterval(timer);
}
else{
obj.style.width = obj.offsetWidth + speed + 'px';
}
},30);
}
</script>
</head>
<body>
<div></div>
<div></div>
<div></div>
</body>
</html>

  上面的代码,有 3 个 div,在鼠标移入时宽从 100像素 运动到 400像素,而当鼠标移出后从 400像素 运动回 100像素,因为是多个物体的运动,因此要使用 for 循环让每个物体都获得事件,那么要确定当前获得事件的物体,就需要再传入一个参数 this,我们不能确定当前是哪个物体获得事件,所以使用 this 指向,但是我们之前写的 startMove 框架只有一个参数,而多物体的运动传入了2个参数,因为要确定当前运动的物体,所以只要再给 startMove 传入一个参数 obj 就好了。那么多物体的运动框架,就是再传入一个参数,这样就能获取当前运动的物体了。

  这样就算做完了吗?肯定没有,当鼠标同时滑过 3 个 div 时,就出事了,会发现变宽之后变不回来了,这是因为定时器,3 个 div 只用一个定时器,虽然在刚开始运行时,我们就清空了定时器,但只是清空了整个定时器,不知道清空的是谁的,这样就造成了混乱,肯定就出错了,所以要给每个 div 都设置一个定时器,我们知道可以给对象添加一个自定义属性设置索引号 aDiv[i].index = 0,那么就可以把定时器也作为物体的属性 aDiv[i].timer = null 。

 <!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>多个div变宽</title>
<style>
div{
width:100px;
height:50px;
background:green;
margin:10px;
}
</style>
<script>
window.onload = function (){
var aDiv = document.getElementsByTagName('div');
for(var i=0; i<aDiv.length; i++){
aDiv[i].timer = null;
aDiv[i].onmouseover = function (){
startMove(this,400);
};
aDiv[i].onmouseout = function (){
startMove(this,100);
};
}
};
function startMove(obj,iTarget){
clearInterval(obj.timer);
obj.timer = setInterval(function (){
var speed=(iTarget - obj.offsetWidth)/6;
speed = speed>0 ? Math.ceil(speed) : Math.floor(speed); if(obj.offsetWidth == iTarget){
clearInterval(obj.timer);
}
else{
obj.style.width = obj.offsetWidth + speed + 'px';
}
},30);
}
</script>
</head>
<body>
<div></div>
<div></div>
<div></div>
</body>
</html>

  下面再看一个多个 div 淡入淡出的实例。

 <!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>多个div淡入淡出</title>
<style>
div{
width:200px;
height:200px;
background:red;
float:left;
margin:20px;
filter:alpha(opacity:30);
opacity:0.3;
}
</style>
<script>
window.onload = function (){
var aDiv = document.getElementsByTagName('div');
for(var i=0; i<aDiv.length; i++){
aDiv[i].onmouseover = function (){
startMove(this,100);
};
aDiv[i].onmouseout = function (){
startMove(this,30);
};
}
};
var alpha = 30;
function startMove(obj,iTarget){
clearInterval(obj.timer);
obj.timer = setInterval(function (){
var speed=(iTarget - alpha)/6;
speed = speed>0 ? Math.ceil(speed) : Math.floor(speed); if(alpha == iTarget){
clearInterval(obj.timer);
}
else{
alpha += speed;
obj.style.opacity = alpha/100;
obj.style.filter = 'alpha(opacity: '+ alpha +')';
}
},30);
}
</script>
</head>
<body>
<div></div>
<div></div>
<div></div>
<div></div>
</body>
</html>

  上面的代码,4 个 div 初始的透明度都为 0.3,在鼠标移入后透明度变为 1,当鼠标移出后变回 0.3,单个移入移出都没有问题,但是当快速移动鼠标时,又出事了,并没有达到我们预期的效果,我们给当前的物体都设置了定时器,但为什么还会出现这种情况呢?其实这并不是定时器的问题,而是用于存储透明度的变量 alpha 导致的,4 个 div 公用了一个滤镜,就导致了混乱,从这我们可以看出,凡是多物体运动,所有东西都不能公用,也就是要改变的属性,必须给每个物体设置,要把属性和运动东西绑定。

 <!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>多个div淡入淡出</title>
<style>
div{
width:200px;
height:200px;
background:red;
float:left;
margin:20px;
filter:alpha(opacity:30);
opacity:0.3;
}
</style>
<script>
window.onload = function (){
var aDiv = document.getElementsByTagName('div');
for(var i=0; i<aDiv.length; i++){
aDiv[i].alpha = 30;
aDiv[i].onmouseover = function (){
startMove(this,100);
};
aDiv[i].onmouseout = function (){
startMove(this,30);
};
}
};
function startMove(obj,iTarget){
clearInterval(obj.timer);
obj.timer = setInterval(function (){
var speed=(iTarget - obj.alpha)/6;
speed = speed>0 ? Math.ceil(speed) : Math.floor(speed); if(obj.alpha == iTarget){
clearInterval(obj.timer);
}
else{
obj.alpha += speed;
obj.style.opacity = obj.alpha/100;
obj.style.filter = 'alpha(opacity: '+ obj.alpha +')';
}
},30);
}
</script>
</head>
<body>
<div></div>
<div></div>
<div></div>
<div></div>
</body>
</html>

  (2)、任意值运动

  之前我们都做的是物体的单一运动,都是改变宽度或者改变透明度,在网站中,也肯定不只是单一的运动,有可能是变宽,也有可能是变高,下面我们就看一下 div 的变宽和变高

 <!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>div变宽和变高</title>
<style>
div{
width:100px;
height:50px;
background:green;
margin-bottom:10px;
}
</style>
<script>
window.onload = function (){
var oDiv1 = document.getElementById('div1');
var oDiv2 = document.getElementById('div2'); oDiv1.onmouseover = function (){
startMove(this,400);
};
oDiv1.onmouseout = function (){
startMove(this,100);
}; oDiv2.onmouseover = function (){
startMove2(this,400);
};
oDiv2.onmouseout = function (){
startMove2(this,50);
};
};
function startMove(obj,iTarget){
clearInterval(obj.timer);
obj.timer = setInterval(function (){
var speed = (iTarget - obj.offsetWidth)/6;
speed = speed>0 ? Math.ceil(speed) : Math.floor(speed);
if(obj.offsetWidth == iTarget){
clearInterval(obj.timer);
}
else{
obj.style.width = obj.offsetWidth + speed + 'px';
}
},30);
}
function startMove2(obj,iTarget){
clearInterval(obj.timer);
obj.timer = setInterval(function (){
var speed = (iTarget - obj.offsetHeight)/6;
speed = speed>0 ? Math.ceil(speed) : Math.floor(speed);
if(obj.offsetHeight == iTarget){
clearInterval(obj.timer);
}
else{
obj.style.height = obj.offsetHeight + speed + 'px';
}
},30);
}
</script>
</head>
<body>
<div id="div1">变宽</div>
<div id="div2">变高</div>
</body>
</html>

  上面的代码,div1 在鼠标移入时宽度从 100像素 运动到 400像素,移出后运动回 100像素,div2 在鼠标移入时高度从 50像素 运动到 400像素,移出后运动回 50像素,我们只要调用 2 个 startMove 框架就可以很轻松的完成这样的效果,但是代码显得十分繁琐,而且这 2 个框架的功能是完全相同的,唯一不同的就是一个改变宽度,一个改变高度,之前我们说过,对于功能完全相同的代码,就可以对代码进行简化,把不同的东西作为参数传入,这样就可以得到一个任意值的运动框架。

  在这之前,我们先来看一下 offset 这个属性前边我们在做运动时,只给物体进行了简单的样式定义,宽、高和背景,但在实际的开发中,或许还有其他的样式属性,比如内外边距或者边框等,那如果在定义一个边框属性,会如何呢?看下面的实例:

 <!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>offset属性</title>
<style>
#div1{
width:200px;
height:200px;
background:red;
border:1px solid black;
}
</style>
<script>
setInterval(function (){
var oDiv = document.getElementById('div1');
oDiv.style.width = oDiv.offsetWidth - 1 + 'px';
},30);
</script>
</head>
<body>
<div id="div1">变窄</div>
</body>
</html>

  上面的代码,我们给 div 加了 1像素 的边框,并且让他每隔 30毫秒 从右向左运动 1像素,速度为负,也就是 offsetWidth - 1,本应该打开 demo 之后,会看到 div 会逐渐变窄,但是事与愿违,div 并没有逐渐变窄,反而逐渐变宽了,如果不加边框的话,div 肯定会逐渐变窄,这就是 offset 属性的一个小 bug在设置了边框后,当前的宽度就要加上边框的宽度,实际上 offsetWidth 的值就为 202,计算后为 201 并赋值给 width,当下一次运行时 width 的值就为 201,再加上边框值 offsetWidth 的值则为 203,计算后为 202 并赋值给 width,依次类推,所以就会出现逐渐变宽的现象。

  要解决这个 bug,就是不使用 offset 属性,最直接的办法就是将 width 属性放在行间,style.width 值只考虑本身的 width,而不考虑边框或者内边距及其他的影响因素,但是 style 仅仅只能取行间样式,如果是写在内部样式表或外部样式表,就没法用了,并且这也不符合 W3C 结构和表现相分离的原则,所以该方法是不可取的,但是我们可以看以下他是怎么实现的。

 <!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>offset的bug</title>
<style>
#div1{
height:200px;
background:red;
border:1px solid black;
}
</style>
<script>
setInterval(function (){
var oDiv = document.getElementById('div1');
oDiv.style.width = parseInt(oDiv.style.width) - 1 + 'px';
},30);
</script>
</head>
<body>
<div id="div1" style="width:200px">变窄</div>
</body>
</html>

  上面的代码,我们将 div 的 width 属性写在了行间,通过 oDiv.style.width 获取,再使用 parseInt 函数将字符串转换为一个整数,再减去速度,这样问题就解决了。

  最佳的解决方法,就是获取非行间样式,我们可以封装一个 getStyle 函数,用于获取非行间样式,再使用时传入相应的参数。

 <!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>offset的bug</title>
<style>
#div1{
width:200px;
height:200px;
background:red;
border:1px solid black;
}
</style>
<script>
function getStyle(obj,name){
if(obj.currentStyle){
return obj.currentStyle[name];
}
else{
return getComputedStyle(obj,false)[name];
}
}
setInterval(function (){
var oDiv = document.getElementById('div1');
oDiv.style.width = parseInt(getStyle(oDiv,'width')) - 1 + 'px';
},30);
</script>
</head>
<body>
<div id="div1">变窄</div>
</body>
</html>

  getStyle 函数有 2 个参数,第一个参数 obj 为要获取的对象,第二个参数 name 为要获取的属性,并且做了兼容处理,currentStyle 针对 IE 浏览器,getComputedStyle 针对火狐浏览器。

  既然 offset 这个属性存在 bug,那么再做动画效果时就不能使用该属性了,使用获取非行间样式的方法来做,下面我们就用这个方法来做 div 的变宽和变高

 <!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>div变宽和变高</title>
<style>
div{
width:100px;
height:50px;
background:green;
margin-bottom:10px;
border:5px solid black;
}
</style>
<script>
window.onload = function (){
var oDiv = document.getElementById('div1');
var oDiv2 = document.getElementById('div2');
oDiv.onmouseover = function (){
startMove(this,400);
};
oDiv.onmouseout = function (){
startMove(this,100);
}; oDiv2.onmouseover = function (){
startMove2(this,400);
};
oDiv2.onmouseout = function (){
startMove2(this,50);
};
};
function getStyle(obj,name){
if(obj.currentStyle){
return obj.currentStyle[name];
}
else{
return getComputedStyle(obj,false)[name];
}
}
function startMove(obj,iTarget){
clearInterval(obj.timer);
obj.timer = setInterval(function (){
var cur = parseInt(getStyle(obj,'width'));
var speed = (iTarget - cur)/6;
speed = speed>0 ? Math.ceil(speed) : Math.floor(speed);
if(cur == iTarget){
clearInterval(obj.timer);
}
else{
obj.style['width'] = cur + speed + 'px';
}
},30);
}
function startMove2(obj,iTarget){
clearInterval(obj.timer);
obj.timer = setInterval(function (){
var cur = parseInt(getStyle(obj,'height'));
var speed = (iTarget - cur)/6;
speed = speed>0 ? Math.ceil(speed) : Math.floor(speed);
if(cur == iTarget){
clearInterval(obj.timer);
}
else{
obj.style['height'] = cur + speed + 'px';
}
},30);
}
</script>
</head>
<body>
<div id="div1">变宽</div>
<div id="div2">变高</div>
</body>
</html>

  上面的代码,我们给 div 设置了 5像素 的边框,鼠标移入移出变宽和变高都是没问题的,为了代码的简洁,我们将获取物体当前属性值的代码保存在一个变量中,var cur = parseInt(getStyle(obj,'width')),方便之后调用,我们知道 obj.style.width 也可以写为 obj.style['width'],只是后者写起来比较麻烦,平时大家不这样写,此处这样写,可以更好的说明问题,仔细观察代码,原本的框架只能让某个值运动起来,如果想要其他值运动起来,就必须修改程序,2 个 startMove 框架结构是完全相同的,只有传入的属性值不同,那么我们就可以进行代码优化了,将属性值作为参数传入  startMove 框架中,在使用时,传入什么属性,就可以改变什么属性,这就是任意值运动框架。

  下面我们就检测以下我们的任意值运动框架,做一个滤镜效果,div 的淡入淡出

 <!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>div淡入淡出</title>
<style>
div{
width:200px;
height:200px;
background:red;
border:1px solid black;
filter:alpha(opacity:30);
opacity:0.3;
}
</style>
<script>
window.onload = function (){
var oDiv = document.getElementById('div1');
oDiv.onmouseover = function (){
startMove(this,'opacity',100);
};
oDiv.onmouseout = function (){
startMove(this,'opacity',30);
};
};
function getStyle(obj,name){
if(obj.currentStyle){
return obj.currentStyle[name];
}
else{
return getComputedStyle(obj,false)[name];
}
}
function startMove(obj,attr,iTarget){
clearInterval(obj.timer);
obj.timer = setInterval(function (){
var cur = parseFloat(getStyle(obj,attr))*100;
var speed = (iTarget - cur)/10;
speed = speed>0 ? Math.ceil(speed) : Math.floor(speed);
if(cur == iTarget){
clearInterval(obj.timer);
}
else{
obj.style.opacity = (cur+speed)/100;
obj.style.filter = "alpha(opacity: '+ (cur + speed) +')";
document.getElementById('txt1').value = obj.style.opacity;
}
},30);
}
</script>
</head>
<body>
<input type="text" id="txt1">
<div id="div1"></div>
</body>
</html>

  上面的代码,div 初始透明度为 0.3,鼠标移入时变为 1,鼠标移开后恢复,还定义了一个文本框,用于显示 div 的透明度值,这里需要注意,透明度的值为小数,所以要使用 parseFloat 函数,将字符串转换为浮点数,而不是使用 parseInt 函数,他用于将字符串转换为整数,最后为了便于计算,在给这个数值乘以 100。注意观察文本框显示的属性值,在鼠标移入时,显示为 1.00999等 一长串数字,而当鼠标移开后,透明度的值一直在 0.300001 到 0.29000001 之间徘徊,怎么会出现这情况呢?那是因为计算机存储的小数是一个近似值,所以会有误差,看下面的实例。

 <script>
alert(0.03*100); //返回:3
alert(0.05*100); //返回:5
alert(10/3); //返回:3.33……35
alert(0.07*100); //返回:7.00……01
</script>

  上面的代码,计算 0.03*100 和 0.05*100,结果为 3 和 5,计算结果正确,而计算 10/3 小数点后最后一位为 5,更离谱的是计算 0.07*100,结果居然不是7,这误差也太大了,因此不建议进行小数点计算,所以解决上面问题的方法,就是取浮点数之后再进行四舍五入。

  那么再观察上面改变透明度的代码,跟之前改变宽和高的代码,虽然使用的框架是相同的,但是结构又是不相同的,也就是不能使用改变宽高的代码来进行改变透明度,那这岂不是任意值的运动框架了,要怎么办呢?很简单,对透明度特殊对待,也就是使用判断,如果要改变的属性为透明度,那么就使用改变透明度的方法,否则就是改变其他属性,那就使用改变宽高的方法。

  下面我们看一下任意值运动的综合实例

 <!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>任意值运动框架</title>
<style>
#div1{
filter:alpha(opacity:30);
opacity:0.3;
}
div{
width:100px;
height:50px;
background:red;
margin-bottom:10px;
border:1px solid black;
font-size:14px;
}
</style>
<script>
window.onload = function (){
var oDiv = document.getElementById('div1');
var oDiv2 = document.getElementById('div2');
var oDiv3 = document.getElementById('div3');
var oDiv4 = document.getElementById('div4');
oDiv.onmouseover = function (){
startMove(this,'opacity',100);
};
oDiv.onmouseout = function (){
startMove(this,'opacity',30);
};
oDiv2.onmouseover=function (){
startMove(this,'fontSize',24);
};
oDiv2.onmouseout=function (){
startMove(this,'fontSize',12);
};
oDiv3.onmouseover = function (){
startMove(this,'width',400);
};
oDiv3.onmouseout = function (){
startMove(this,'width',100);
};
oDiv4.onmouseover = function (){
startMove(this,'height',400);
};
oDiv4.onmouseout = function (){
startMove(this,'height',50);
};
};
function getStyle(obj,name){
if(obj.currentStyle){
return obj.currentStyle[name];
}
else{
return getComputedStyle(obj,false)[name];
}
}
function startMove(obj,attr,iTarget){
clearInterval(obj.timer);
obj.timer = setInterval(function (){
var cur = 0; //用于存储物体当前的属性,方便之后调用
if(attr == 'opacity'){
cur = Math.round(parseFloat(getStyle(obj,attr))*100);
}
else{
cur = parseInt(getStyle(obj,attr));
}
var speed = (iTarget - cur)/10;
speed = speed>0 ? Math.ceil(speed) : Math.floor(speed);
if(cur == iTarget){
clearInterval(obj.timer);
}
else{
if(attr == 'opacity'){
obj.style.opacity = (cur+speed)/100;
obj.style.filter = "alpha(opacity: '+ (cur + speed) +')";
}
else{
obj.style[attr] = cur + speed + "px";
}
}
},30);
}
</script>
</head>
<body>
<div id="div1">改变透明度</div>
<div id="div2">改变文字大小</div>
<div id="div3">改变宽度</div>
<div id="div4">改变高度</div>
</body>
</html>

  上面的代码,不只改变了 div 的透明度,也改变了宽度和高高度,还改变了字体的大小,当然也可以使用该框架改变他的边框等其他属性。

  

4、链式运动

  所谓链式运动,就像链条一样,一个扣一个,当一个运动结束后,开始下一个运动,要实现这样的效果,只需要放一个回调函数,也就是在运动停止时,执行的函数,再调用一次 startMove 框架,开始下一次运动,那这样就好办了,既然还是使用使用之前的框架,那么就再给他传入一个参数,这样就可以完成链式运动,下面我们就来看一下在代码中具体是怎么实现的。

 <!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>链式运动框架</title>
<style>
div{
width:100px;
height:100px;
background:red;
filter:alpha(opacity:30);
opacity:0.3;
}
</style>
<script>
window.onload = function (){
var oDiv = document.getElementById('div1');
oDiv.onmouseover = function (){
startMove(oDiv,'width',300,function(){
startMove(oDiv,'height',300,function (){
startMove(oDiv,'opacity',100)
});
});
};
oDiv.onmouseout = function (){
startMove(oDiv,'opacity',30,function (){
startMove(oDiv,'height',100,function (){
startMove(oDiv,'width',100);
});
});
};
};
function getStyle(obj,name){
if(obj.currentStyle){
return obj.currentStyle[name];
}
else{
return getComputedStyle(obj,false)[name];
}
}
function startMove(obj,attr,iTarget,fn){
clearInterval(obj.timer);
obj.timer = setInterval(function (){
var cur = 0;
if(attr == 'opacity'){
cur = Math.round(parseFloat(getStyle(obj,attr))*100);
}
else{
cur = parseInt(getStyle(obj,attr));
}
var speed = (iTarget - cur)/10;
speed = speed>0 ? Math.ceil(speed) : Math.floor(speed);
if(cur == iTarget){
clearInterval(obj.timer);
if(fn)fn();
}
else{
if(attr == 'opacity'){
obj.style.opacity = (cur+speed)/100;
obj.style.filter = "alpha(opacity: '+ (cur + speed) +')";
}
else{
obj.style[attr] = cur + speed + "px";
}
}
},30);
}
</script>
</head>
<body>
<div id="div1"></div>
</body>
</html>

  上面的代码,div 的初始透明度为 0.3,宽100px 高100px,鼠标移入后,div 的宽度先从 100px 运动到 300px 处停止,然后高度再从 100px 运动到 300px 处停止,最后透明度从 0.3 变为 1,当鼠标移开后,以相反的顺序运动,即 div 的透明度先从 1 变回 0.3,然后高度从 300px 运动回 100px,最后宽度从300px 运动回 100px,这就是一个简易的链式运动。

  观察上面的代码,我们要完成链式运动,那么当一次运动结束后,就需要再执行一次 startMove 框架,那么再传入一个 fn 参数,fn 就是一个函数,当执行完一段代码之后,没有马上结束,而是再调用一次这个函数,再执行一个新的程序。这里最需要注意的是,当传入一个参数进来后,需要检测运动停止,在运动结束时清空定时器后,就要判断一下,if(fn)fn(),只有当这个函数传进来再调用,也就是第一次运动停止后,如果有这个 fn,也就是传入了一个 fn,那么就让这个 fn 执行一次,如果没有最后一个参数,那么就不需要再往下执行,运动就停止了。

  

5、同时运动

  我们刚封装了链式运动框架,就是一个扣一个,div 先变宽完成之后再变高,最后再改变透明度,这些都是单一的运动效果,即每一次运动都只能改变一个属性,我们在某些网站应都见过这么一种效果,当鼠标移入时图片的宽度和高度会同时发生变化,这样一种效果就是多物体动画同时运动效果,也可以叫做多值运动。

  那要怎么完成这种动画效果呢?可以先用之前封装好的运动框架尝试一下,既然是同时改变 div 的宽和高,那么就不能像做链式运动那样,传入一个回调函数,当某一个值运动结束后再运动下一个值,这样就成了链式运动,而不是同时运动,那如果定义两次呢,先来看一下。

 <!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>现有框架的问题</title>
<style>
div{
width:100px;
height:100px;
background:red;
}
</style>
<script>
window.onload = function (){
oBtn = document.getElementById('btn1');
oDiv = document.getElementById('div1');
oBtn.onclick = function (){
startMove(oDiv,'width',300);
startMove(oDiv,'height',300);
};
};
function getStyle(obj,name){
if(obj.currentStyle){
return obj.currentStyle[name];
}
else{
return getComputedStyle(obj,false)[name];
}
}
function startMove(obj,attr,iTarget,fn){
clearInterval(obj.timer);
obj.timer = setInterval(function (){
var cur = 0;
if(attr == 'opacity'){
cur = Math.round(parseFloat(getStyle(obj,attr))*100);
}
else{
cur = parseInt(getStyle(obj,attr));
}
var speed = (iTarget - cur)/10;
speed = speed>0 ? Math.ceil(speed) : Math.floor(speed);
if(cur == iTarget){
clearInterval(obj.timer);
if(fn)fn();
}
else{
if(attr == 'opacity'){
obj.style.opacity = (cur+speed)/100;
obj.style.filter = "alpha(opacity: '+ (cur + speed) +')";
}
else{
obj.style[attr] = cur + speed + "px";
}
}
},30);
}
</script>
</head>
<body>
<input id="btn1" type="button" value="运动">
<div id="div1"></div>
</body>
</html>

  上面的代码,div 的初始宽高都为 100px,我们同时定义了让 div 的宽和高都运动到 300px,点击运动按钮,会发现 div 的高度从 100px 运动到 300px 了,而宽并没有改变。想要同时改变 div 的宽和高,而 attr 参数只能传一个,如果定义两次,系统会执行后边的参数设置,因为执行 startMove 前先清空计时器,刚清空了计时器之后,后便的传参就会立马执行,也就是前边的一个参数被覆盖了,这是现有框架的一个小问题,不能让几个值同时运动,要解决这个问题,可以使用 JSON 完成,现在我们先回忆一下 JSON。

 <script>
var json = {a:5,b:12};
for(var i in json){
alert(i + ' = ' + json[i]);
}
</script>

  JSON 是一种轻量级的数据交互格式,JSON 语法是 JS 对象表示语法的一个子集,数据在键值对中,是以对值出现的,即 名称/值,名称和值由冒号连接,并用逗号分隔,花括号保存对象,方括号保存数组,JSON 值可以是:数字(整数或浮点数)、字符串(包含在双引号中)、对象(包含在花括号中)、数组(包含在方括号中)、逻辑值(true 或 false)、null。可以使用 for in 循环遍历 JSON 中的数据,var i in json ,i 是定义的变量,in 就是在什么里,那就是在 json 里循环遍历,上面的代码,先弹出 a = 5,再弹出 b = 12,那么 i 就表示 json 中的名称,而 json[i] 就表示 json 中某项的值,也就是 json 中变量 i 所对应的值。

  了解了 JSON 之后,那么我们如何使用 JSON 完成同时运动呢,现有的运动框架有4个参数,分别是 obj(对象)、attr(属性)、iTarget(目标)、fn(回调函数),在这4个参数中,attr 和 iTarget 也就是属性值和目标值是一对值,属性就是 json 中的名称,让谁做运动,目标就是值,到哪个位置时结束运动。那么就是说现有的运动框架,只能改变一对值,而不能实现多对值的变化,如果要实现多对值的变化,就需要用到 JSON 格式,因此就可以把 startMove 写为 startMove(obj, {attr1 : iTarget1, attr2 : iTarget1}, fn),把参数中的这一对值变成 JSON 的格式,用函数传参的方式就写为 startMove(obj, json, fn)。

  使用了 JSON 之后,还需要把 JSON 中的数据循环遍历出来,再配合 for in 循环,那要怎样循环呢,之前的运动框架,先要开启定时器,然后取当前的值,再计算速度,最后检测停止,完成这一系列的过程,当前物体触发的运动才算整个完成,那现在我们要完成多对值同时触发运动,就是让现在这整个过程多做几次循环就可以了,下面我们再来看一下代码的实现

 <!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>同时运动</title>
<style>
div{
width:100px;
height:100px;
background:red;
}
</style>
<script>
window.onload = function (){
oBtn = document.getElementById('btn1');
oDiv = document.getElementById('div1');
oBtn.onclick = function (){
startMove(oDiv, {width:300, height:300});
};
};
function getStyle(obj,name){
if(obj.currentStyle){
return obj.currentStyle[name];
}
else{
return getComputedStyle(obj,false)[name];
}
}
function startMove(obj, json, fn){
clearInterval(obj.timer);
obj.timer = setInterval(function (){
for(var attr in json){
//1、取当前值
var cur = 0;
if(attr == 'opacity'){
cur = Math.round(parseFloat(getStyle(obj,attr))*100);
}
else{
cur = parseInt(getStyle(obj,attr));
}
//2、算速度
var speed = (json[attr] - cur)/10;
speed = speed>0 ? Math.ceil(speed) : Math.floor(speed);
//3、检测停止
if(cur == json[attr]){
clearInterval(obj.timer);
if(fn)fn();
}
else{
if(attr == 'opacity'){
obj.style.opacity = (cur+speed)/100;
obj.style.filter = "alpha(opacity: '+ (cur + speed) +')";
}
else{
obj.style[attr] = cur + speed + "px";
}
}
}
},30);
}
</script>
</head>
<body>
<input id="btn1" type="button" value="运动">
<div id="div1"></div>
</body>
</html>

  上面的代码,点击运动按钮后,div 的宽和高同时从 100px 变为 300px。在开启定时器之后,就使用 for in 循环,将之前框架中 attr 参数定义为一个变量,就是在 json 中循环遍历 attr,也就是存储在 json 中的属性值,此时已经没有 iTarget 参数了,那么要设置目标值,就要把原来的 iTarget 改为 json[attr],最后在调用 startMove 时就可以用 JSON 格式传入多个值了,startMove(oDiv, {width:300, height:300}),这样就完成了同时运动。

  

6、完美运动框架

  我们已经封装了同时运动框架,到这一刻,我们离完美的运动框架就已经不远了,那为什么还有一点距离呢,我们通过下面的实例来看一下。

 <!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>同时运动</title>
<style>
*{margin:0;padding:0}
div{
width:200px;
height:100px;
background:red;
border:2px solid black;
filter:alpha(opacity:30);
opacity:0.3;
}
</style>
</head>
<script>
window.onload = function (){
oDiv = document.getElementById('div1');
oDiv.onmousemove = function (){
startMove(oDiv, {width:201, height:200, opacity:100});
};
oDiv.onmouseout = function (){
startMove(oDiv, {width:200, height:100, opacity:30});
};
};
function getStyle(obj,name){
if(obj.currentStyle){
return obj.currentStyle[name];
}
else{
return getComputedStyle(obj,false)[name];
}
}
function startMove(obj, json, fn){
clearInterval(obj.timer);
obj.timer = setInterval(function (){
for(var attr in json){
//1、取当前值
var cur = 0;
if(attr == 'opacity'){
cur = Math.round(parseFloat(getStyle(obj,attr))*100);
}
else{
cur = parseInt(getStyle(obj,attr));
}
//2、算速度
var speed = (json[attr] - cur)/10;
speed = speed>0 ? Math.ceil(speed) : Math.floor(speed);
//3、检测停止
if(cur == json[attr]){
clearInterval(obj.timer);
if(fn)fn();
}
else{
if(attr == 'opacity'){
obj.style.opacity = (cur+speed)/100;
obj.style.filter = "alpha(opacity: '+ (cur + speed) +')";
}
else{
obj.style[attr] = cur + speed + "px";
}
}
}
},30);
}
</script>
<body>
<div id="div1"></div>
</body>
</html>

  上面的代码,使用了同时运动框架,在其实例中,多值运动并没有什么问题,多个值可以同时变化,观察上面的代码,div 的初始宽度为 200px,高度为 100px,透明度为 0.3,在鼠标移入后,我们让他的宽度变为 201px,高度变为 200px,透明度变为 1,这时候就出事了,打开调试工具进行测试,可以看到在鼠标移入时,div 的宽度确实是从 200px 运动到 201px,但是他的高度和透明度远远没有达到我们的要求,我们来分析一下为什么会出现这种情况呢,只有宽度到达了目标值,而高度和透明度并没有达到目标值,整个运动就停止了。

  回过头检查同时运动框架,我们不难发现,在做检测停止时,if(cur == json[attr]),我们判断如果当前值达到目标值时,就关闭定时器,同时运动框架和完美运动框架的距离就产生在这了,我们并没有去判断是不是所有的运动都到达了终点,那么只要有一个运动达到了终点,他就会关闭定时器,那这样肯定是不行的,就好比参加旅游团,不可能人没到齐就出发了,这样肯定是不靠谱的。那要怎么解决呢,我们需要去判断是不是所有的运动都到达了目标值,如果所有的运动都达到了目标值,那么才能关闭定时器,也就是说,如果有没有达到的,就不能关闭定时器。

  下面我们就来看看完美的运动框架到底长什么样。

 <script>
function startMove(obj, json, fn){
clearInterval(obj.timer);
obj.timer = setInterval(function (){
var bStop = true; //假设所有的值都达到了目标值,这一次运动就结束了。
for(var attr in json){
//1.取当前值
var cur = 0;
if(attr == 'opacity'){
cur = Math.round(parseFloat(getStyle(obj,attr))*100);
}
else{
cur = parseInt(getStyle(obj,attr));
}
//2.算速度
var speed = (json[attr]-cur)/6;
speed = speed>0 ? Math.ceil(speed) : Math.floor(speed);
//3.检测停止
//如果有一个值不等于当前值。
if(cur != json[attr]){
bStop = false;
}
if(attr == 'opacity'){
obj.style.opacity = (cur+speed)/100;
obj.style.filter = "alpha(opacity: "+(cur + speed)+")";
}
else{
obj.style[attr] = cur + speed + "px";
}
}
//如果所有的值都达到了目标值,再关闭定时器。
if(bStop){
clearInterval(obj.timer);
if(fn){
fn();
}
}
},30);
}
</script>

  我们把同时运动框架稍微完善以下,就是完美的运动框架了,首先在开启定时器执行循环之前,首先定义一个变量 bStop 值为 true,假设所有值都达到了目标值,那么就一次运动才算是结束了,然后在做检测停止时,再做判断,如果有一个值不等于当前值,也就是有一个值还没有达到目标值,就说明 bStop 为 false,那么就让他继续执行他的运动,在完成了整个循环之后,还要再做一个判断,如果所有值都达到了目标值,那么再关闭定时器。

  到现在,我们这个运动框架就算是真正的完美了,我们可以把这个框架保存为一个单独的 JS 文件,命名为 move.js,对于上面的问题可以简单的验证以下,再做一些其他值的运动。

 <!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>完美运动</title>
<style>
*{margin:0;padding:0}
div{
width:200px;
height:100px;
background:red;
border:2px solid black;
filter:alpha(opacity:30);
opacity:0.3;
}
</style>
<script src="JS/move++.js"></script>
<script>
window.onload = function (){
oDiv = document.getElementById('div1');
oDiv.onmousemove = function (){
startMove(oDiv, {width:301, height:402, opacity:100, fontSize:30, borderWidth:5});
};
oDiv.onmouseout = function (){
startMove(oDiv, {width:200, height:100, opacity:30, fontSize:12, borderWidth:2});
};
};
</script>
</head>
<body>
<div id="div1">多值同时运动</div>
</body>
</html>

  上面我们使用了完美运动框架,鼠标移入时,同时改变了 div 的宽、高、透明度、字体大小和边框宽度,鼠标移出时恢复初始定义,一点问题都没有,非常完美。

  

7、运动框架的总结

  其实我们可以在网上找到很多个想要的动画效果,直接拿来套用,万能的互联网,出产任何你能想到的东西,这样也相当省事,但是对于一个学习者来说,如果所有的东西都直接搬来用,这样是没有任何问题,一样可以达到目的,但是就算你拿来直接套用,你也得搞清楚他内部是怎么实现的,不了解是怎么实现的也没法套用,所以一个效果的实现过程才是最值得研究的,也是最有价值的,因此不仅要自己多写代码,还要多阅读别人的代码,在这个过程中,才能更好的锻炼逻辑思维,培养编程的思想,只有最基础的知识掌握了,根基扎实了,才能在编程的道路上走的更高更远,所谓一法通则万法通,多悟多总结,很多东西都可以触类旁通,道法自然,当然每个人都有自己的学习方式,只要是适合自己的,那才是最好的。

  闲话就不多说了,我们来看一下运动框架的演变过程。

  最简单的运动框架:startMove(iTarget)

  我们在刚开始定义了一个 startMove 函数,只需要开一个定时器,就可以让 div 动起来,但是根本停不下来,我们又做了判断,如果达到了目标值就清空定时器,但是还有一点小问题,虽然到目标位置了,定时器也清空了,但是再点击按钮,div 还是会运动一下,因为点击按钮之后 startMove 还会被执行一次,所以我们在执行 startMove 时,首先就要清空定时器。之后我们做了匀速运动的分享到侧边栏效果,因为有 2 个目标值,我们定义了 startMove 和 stopMove 两个函数,用于内容容器的展示和隐藏,这两个函数长的几乎一样,具有相同的功能,我们又做了代码优化,把不同的东西找出来,用参数的方式传入。

  起初我们给 startMove 定义了 2 个参数,speed 和 iTarget,在功能相同的情况下,传入的参数越少越好,目标值参数是不可以省略的,因为得有一个终点,而速度值是可以省略的,因为不管速度快还是慢,都会到达终点。

  我们还说了侧边栏分享效果使用缓冲运动做效果更好。

  多物体运动框架:startMove(obj, iTarget)

  想让多个物体运动起来,只需要再传入一个参数,也就是对象参数,我们传入哪个对象,就运动哪个对象。

  任意值运动框架:startMove(obj, attr, iTarget)

  多个物体运动起来还不够,如果想让任意属性值运动,那就再传入一个参数,也就是属性参数,我们传入哪个属性,哪个属性就发生变化。

  我们还了解了 offset 属性的一个 bug,我们想让 div 的宽度逐渐变窄时,给他加了边框属性,不但没有变窄,反而是逐渐变宽了,因为 offset 属性受到其他属性的影响,他判断当前的属性值为宽度值加上边框值,最好的解决办法就是获取非行间样式,我们又封装了一用于获取非行间样式的函数 getStyle。

  链式运动框架:startMove(obj, attr, iTarget, fn)

  链式运动就是让宽变完之后再变高,高变完之后再变其他属性,一环扣一环,那么使用回调函数就可以解决了,当一次运动结束后,再调用一次 startMove,开始一个新的运动,那么就给他再传一个参数。

  同时运动框架:startMove(obj, json)

  之前的框架都做的是单一的运动,那么既想让宽变化,也想让高变化,最好的办法就是使用 JSON 格式,我们简单的回忆了一下 JSON,他是一种轻量级的数据交换格式,在 startMove 这4个参数中,分别是对象、属性、目标值、回调函数,属性值和目标值是一对值,那么就以 JSON 的方式传入,再使用 for in 循环执行整个运动,就可以实现多值的运动了。

  完美运动框架:startMove(obj, json, fn)

  在封装了同时运动框架时,我们离完美运动框架就差一个判断了,同时运动框架,在检测停止时,判断如果是达到了目标值就清空定时器,而如果有一个值达到了目标值,其他值并未达到目标值,他也会清空定时器,这显然是不科学的,这并不是先到先到,所以我们定义了一个变量,假设所有值都达到了目标值,这一次运动才算结束,然后再做判断,如果有一个值没有达到目标值,那么就继续执行他的运动,最后直到所有值都达到目标值了,再关闭定时,这就完成了完美运动框架的封装。

  

  现在,我们就可以使用完美运动框架来完成网站中一些常见的动画效果了,比如图片轮播、幻灯片展示、以及微博实时滚动效果等。

  

JavaScript学习总结【11】、JS 运动的更多相关文章

  1. Javascript学习记录——原生JS实现旋转木马特效

    昨天学习到了JS特效部分,然后老师讲了旋转木马特效的实现,如上图.不过只是讲了通过点击箭头实现图片的切换,对于点击图片本身以及二者联动却是没有讲解. 本着一颗追求完美的心,今天花费了一个中午终于将整个 ...

  2. javascript学习一、js的初步了解

    1.javascript的简介: *javascript 是一种基于对象和事件驱动的语言,主要应用于客户端. -- 基于对象:  ** 提供了很多对象,可以直接使用. --事件驱动: ** html做 ...

  3. JavaScript学习02(js快速入门)

    快速入门 基本语法 JavaScript的语法和Java的语法类似,每个语句以;结束,语句块用{...}.但是JavaScrip并不强制要求在每个语句的结尾加;,浏览器中负责执行JavaScript代 ...

  4. JavaScript学习01(js概述)

    JavaScript概述 JavaScript历史 要了解JavaScript,我们首先要了解一下JavaScript的诞生. 在上个世纪的1995年,当时的网景公司正凭借其Navigator浏览器成 ...

  5. 【JavaScript学习整理】js基础

    HTML,CSS属于标记语言, JavaScript是基于客户端的脚本语言. 变量: 语法  var 变量名 = value var是系统内部关键字,用来声明变量 变量名规则:  1.不能以数字开头  ...

  6. javascript学习5、JS面向对象

    创建对象的几种常用方式 1.使用Object或对象字面量创建对象 2.工厂模式创建对象 3.构造函数模式创建对象 4.原型模式创建对象 1.使用Object或对象字面量创建对象 JS中最基本创建对象的 ...

  7. JavaScript学习笔记&lpar;11&rpar;——HTML DOM Event对象

    w3cshool:时间参考手册:http://www.w3school.com.cn/jsref/dom_obj_event.asp

  8. javascript学习笔记&lpar;四&rpar; Number 数字类型

    数字格式化方法toFixed().toExponential().toPrecision(),三个方法都四舍五入 toFixed() 方法指定小数位个数  toExponential() 方法 用科学 ...

  9. JavaScript学习11 数组排序实例

    JavaScript学习11 数组排序实例 数组声明 关于数组对象的声明,以前说过:http://www.cnblogs.com/mengdd/p/3680649.html 数组声明的一种方式: va ...

  10. JavaScript学习12 JS中定义对象的几种方式

    JavaScript学习12 JS中定义对象的几种方式 JavaScript中没有类的概念,只有对象. 在JavaScript中定义对象可以采用以下几种方式: 1.基于已有对象扩充其属性和方法 2.工 ...

随机推荐

  1. zenefits oa - random&lpar;5&rpar; to generate a random&lpar;7&rpar;

    If given a function that generates a random number from 1 to 5, how do you use this function to gene ...

  2. maven-deploy失败

    昨天遇到的问题,mavne项目执行deploy的时候,出错.提示 Return code is: 401, ReasonPhrase: Unauthorized. -> [Help 1] 很直白 ...

  3. 如何使用开源库&comma;吐在VS2013发布之前&comma;顺便介绍下VS2013的新特性&quot&semi;Bootstrap&quot&semi;

    刚看到Visual Studio 2013 Preview - ASP.NET, MVC 5, Web API 2新功能搶先看 看了下VS2013带来的"新特性",直觉上看,除了引 ...

  4. Struts2的Action中如何操作作用域对象

    得到作用域对象有三种方法,这里用代码来解释: package com.cy.action; import javax.servlet.ServletContext; import javax.serv ...

  5. &lt&semi;五&gt&semi;面向对象分析之UML核心元素之边界

    一:基本概念

  6. 第二百五十天 how can I 坚持

    html排版,好烦心. 我以为我会哭,但是我没有.---<领悟> 确实是搞不懂自己,到底想要什么?弟弟最近也不知道咋的了,感觉有点不对劲呢. 最近雾霾好严重,希望我们的后代不会知道雾霾是什 ...

  7. DNS没有生效的几个原因

    1.记录没有正确添加 请确认你的域名记录是否完全正确的添加.线路类型正确,记录类型正确 2.域名还没有生效 这个情况还会有另外一个现象,就是域名有时候可以ping,有时候不能ping. 这是因为你当地 ...

  8. &lbrack;bzoj2836&rsqb; 魔法树

    俩操作:增加路径上的点的权值.查询子树的权值和. 想了想似乎只能树链剖分了..好久没写链剖+数据结构了TAT 一开始没开LL炸了一发(明明有想到的..我果然是傻逼= = #include<cst ...

  9. ORACLE ORA-01653&colon; unable to extend table 的错误

    ORACLE ORA-01653: unable to extend table 的错误 今天用PL SQL Developer往oracle数据库中导入数据时,突然报错,只能终止,错误的具体内容如下 ...

  10. php值callback类型和匿名函数(闭包)

    callback.callable类型 自PHP5.4起可以使用callable类型制定回调类型callback. 本文档基于同样理由使用callback类型信息. 一些函数如call_user_fu ...