学习资源来自慕课网《js动画效果》:http://www.imooc.com/video/3049/0。
在上一节《js动画效果》之多物体动画效果中的例子1 :
1. 网页上有一ul, ul中有三个li元素,要求鼠标移到(onmouseover) li 元素上产生动画效果——宽度值增加到300px,鼠标移出(onmouseout) li 元素产生动画效果——宽度值复原到200px.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style type="text/css">
*{
padding:0;
margin:0;
}
<span style="color:#ff0000;">ul li{
list-style:none;
width:200px;
height:100px;
background:green;
margin:20px;
}</span>
</style>
<script type="text/javascript">
/***
实现多物体运动的关键点:
1)获取到每个元素;
2)给每个元素绑定上事件;
3)注意使用this区分当前操作的元素;
4)定时器不能只设一个了,应该给每个元素分配一个定时器,避免多个元素抢同一个定时器造成bug
记住:多物体运动不能共用同一个东西(某一参数,比如定时器)
***/
window.onload = function(){
var liEles = document.getElementsByTagName("li");
for(var i=0; i<liEles.length; i++){
//给每一个li元素定义一个定时器,避免多个元素抢同一个定时器造成bug
liEles[i].timer = null;
liEles[i].onmouseover = function(){
//传入this值,指定当前操作的是哪个li
playFun(this, 300);
};
liEles[i].onmouseout = function(){
playFun(this, 200);
};
}
}
//var timer = null;
function playFun(obj,itarget){
clearInterval(obj.timer);
obj.timer = setInterval(function(){
var speed = (itarget - obj.offsetWidth)/10;
speed = speed>0?Math.ceil(speed):Math.floor(speed);
if(obj.offsetWidth == itarget){
clearInterval(obj.timer);
}else{
obj.style.width = obj.offsetWidth + speed + "px";
}
},50);
}
</script>
</head>
<body>
<ul>
<li></li>
<li></li>
<li></li>
</ul>
</body>
</html>
li 元素的样式中没有设置 border属性,下面我们重新设置li元素的样式为:
ul li{
list-style:none;
width:200px;
height:100px;
background:green;
margin:20px;
<span style="color:#ff0000;">border:2px #ccc solid;</span>
}
然后在浏览器中运行代码,会发现物体在停止运动时并没有达到规定的目标值200px、300px,如下图示:
代码中一直采用 offsetWidth 来计算物体的宽度, 而 offsetWidth = width + padding(left + right) + border(left + right) ,下面来分析代码,当物体停止运动,console.log(obj.offsetWidth )的输出值为 240 ,对应 width 的值为236,目前定时器仍在运行中,将target 值 200 和 obj.offsetWidth的当前值 240 带入下面公式计算得出 speed = -4
var speed = (itarget - obj.offsetWidth)/10;
speed = speed>0?Math.ceil(speed):Math.floor(speed);
再将obj.offsetWidth的值 240和speed的值 -4 带入公式
obj.style.width = obj.offsetWidth + speed + "px";
得出 width = 236;
下面进入下次定时器的运算,offsetWidth = width + border( 2+2 ) = 240, 又带入各公式计算出width的值还是 236 ,如此死循环下去。
解决方法:
法1)此法是课程中老师讲解的方法——封装一个专门获取属性值得方法,代码如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style type="text/css">
*{
padding:0;
margin:0;
}
ul li{
list-style:none;
width:200px;
height:100px;
background:green;
margin:20px;
border:2px #ccc solid;
}
</style>
<script type="text/javascript">
/***
实现多物体运动的关键点:
1)获取到每个元素;
2)给每个元素绑定上事件;
3)注意使用this区分当前操作的元素;
4)定时器不能只设一个了,应该给每个元素分配一个定时器,避免多个元素抢同一个定时器造成bug
记住:多物体运动不能共用同一个东西(某一参数,比如定时器)
***/
window.onload = function(){
var liEles = document.getElementsByTagName("li");
for(var i=0; i<liEles.length; i++){
//给每一个li元素定义一个定时器,避免多个元素抢同一个定时器造成bug
liEles[i].timer = null;
liEles[i].onmouseover = function(){
//传入this值,指定当前操作的是哪个li
playFun(this, 300);
};
liEles[i].onmouseout = function(){
playFun(this, 200);
};
}
}
//var timer = null;
function playFun(obj,itarget){
clearInterval(obj.timer);
obj.timer = setInterval(function(){
var speed = (itarget - <span style="color:#ff0000;">parseInt(getStle(obj,"width"))</span>)/10;
speed = speed>0?Math.ceil(speed):Math.floor(speed);
if(<span style="color:#ff0000;">parseInt(getStle(obj,"width"))</span> == itarget){
clearInterval(obj.timer);
}else{
//obj.style.width = obj.offsetWidth + speed + "px";
obj.style.width = <span style="color:#ff0000;">parseInt(getStle(obj,"width"))</span> + speed + "px";
}
},50);
}
//获取属性值
function getStle(ele,attr){
if(ele.currentStyle){ //兼容IE浏览器
return ele.currentStyle[attr];
}else{ //兼容firefox浏览器
return getComputedStyle(ele,false)[attr];
}
}</span>
</script>
</head>
<body>
<ul>
<li></li>
<li></li>
<li></li>
</ul>
</body>
</html>
法2)offsetWidth的值包含了border, 但是 offsetClient = width + padding 没有包含border的值,单单就这个例子而言,用 offsetClient 还是可以解决问题,修改部分代码如下。但是如果css中设置了padding值,就不好使了,所以,还是采用老师的方法最好,封装一个专门获取css属性值得函数,可以获取别的属性。
function playFun(obj,itarget){
clearInterval(obj.timer);
obj.timer = setInterval(function(){
var speed = (itarget - obj.clientWidth)/10;
speed = speed>0?Math.ceil(speed):Math.floor(speed);
if(obj.clientWidth == itarget){
clearInterval(obj.timer);
}else{
obj.style.width = obj.clientWidth + speed + "px";
}
},50);
}
3. 将代码封装成可以获取任意属性值(这里主要要对属性为opacity时做单独的处理),练习代码如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style type="text/css">
*{
padding:0;
margin:0;
}
ul li{
list-style:none;
width:200px;
height:100px;
background:green;
margin:20px;
border:2px #ccc solid;
filter:alpha(opacity:30);
opacity:0.3;
}
</style>
<script type="text/javascript">
window.onload = function(){
var liEles = document.getElementsByTagName("li");
for(var i=0; i<liEles.length; i++){
//给每一个li元素定义一个定时器,避免多个元素抢同一个定时器造成bug
liEles[i].timer = null;
liEles[i].onmouseover = function(){
//传入this值,指定当前操作的是哪个li
//playFun(this, 300, "width");
playFun(this, 100, "opacity");
};
liEles[i].onmouseout = function(){
playFun(this, 30, "opacity");
};
}
}
function playFun(obj,itarget,attr){
clearInterval(obj.timer);
obj.timer = setInterval(function(){
var getValue = 0;
if(attr == "opacity"){
//parseFloat返回小数值
//由于计算机存储小数有误差,采用Math.round()四舍五入得整数
getValue = Math.round(parseFloat(getStle(obj,attr))*100);
}else{
getValue = parseInt(getStle(obj,attr));
}
var speed = (itarget - getValue)/10;
speed = speed>0?Math.ceil(speed):Math.floor(speed);
if(getValue == itarget){
clearInterval(obj.timer);
}else{
//obj.style[attr],采用中括号传参
if(attr == "opacity"){
getValue += speed;
obj.style["filter"] = "alpha(opacity:"+ getValue +")";
obj.style["opacity"] = getValue/100;
}else{
obj.style[attr] = getValue + speed + "px";
}
}
},50);
}
//获取属性值
function getStle(ele,attr){
if(ele.currentStyle){ //兼容IE浏览器
return ele.currentStyle[attr];
}else{ //兼容firefox浏览器
return getComputedStyle(ele,false)[attr];
}
}
</script>
</head>
<body>
<ul>
<li></li>
<li></li>
<li></li>
</ul>
</body>
</html>
封装时的注意点:
1)opacity的取值,getStle(obj,attr)获取的opacity返回值为小数,需要采用parseFloat(getStle(obj,attr))*100 来获取并乘以100转为整数;
2)由于计算机存储小数有误差,所以还需采用Math.round()四舍五入得整数;
3)最后赋值css时,注意浏览器兼容:
obj.style["filter"] = "alpha(opacity:"+ getValue +")";
obj.style["opacity"] = getValue/100; // opacity 的值以小数表示