最近学习了物体的运动框架,觉得有必要做下总结!
1. 匀速运动
首先准备一个div,css样式如下:
#div1 { width: 200px; height: 200px; background: red; position: absolute; left: 0; top: 0; }
接下来,我们让这个div动起来
function startMove(obj, iTarget) { clearInterval(obj.timer); //防止重复开启定时器 obj.timer = setInterval(function() { var speed = obj.offsetLeft < iTarget ? 10 : -10; //判断速度的正负 if (obj.offsetLeft == iTarget) { clearInterval(obj.timer); //达到目标,关闭定时器 } else { obj.style.left = obj.offsetLeft + speed + 'px'; } }, 30); }
div每30毫秒向右运动10px,当div.style.left等于iTarget的时候,停下来,这段代码看似没有问题。其实当速度设置 7 的时候,问题就出来了。当div.style.left增加到 399的时候,再增加一个7 就变成 406了,div发现多了6, 就会减7 ,发现又少了……反反复复,div.style.left就是到不了目标位置,div也就停不下来了。
修改代码,注意红色代码:
function startMove2(obj, iTarget) { clearInterval(obj.timer); obj.timer = setInterval(function() { var speed = obj.offsetLeft < iTarget ? 7 : -7; if (Math.abs(obj.offsetLeft - iTarget) <= speed) { clearInterval(obj.timer); obj.style.left = iTarget + 'px'; } else { obj.style.left = obj.offsetLeft + speed + 'px'; } }, 30); }
这样,不管速度设置是多少,都能停下来。
2. 变速运动
变速运动关键在于速度不是一个定值,它一般随着距离的减小而减小,换句话说,距离目标大的时候速度快,距离目标小的时候速度慢。
如何做到这一点呢? 将目标值与当前值相减的差值 除以 一个数(这个数的大小由你决定),这样速度就在不断变化。具体看代码:
1 function startMove3(obj, iTarget) { 2 clearInterval(timer); 3 timer = setInterval(function() { 4 var speed = (iTarget - obj.offsetHeight) / 10; 5 6 if (obj.offsetHeight == iTarget) { 7 clearInterval(timer); 8 } else { 9 obj.style.height = obj.offsetHeight + speed + 'px'; 10 } 11 }, 30); 12 }
同样也是存在问题的。最后速度会变成零点几,一个小数,但css能分辨的最小单位是1px,零点几的px都默认是0,因此物体会在目标值前面一小段距离停止不动,尽管定时器还没满足关闭的条件,但是速度已经为0,每次都是加零,所以停止不动了。
改进代码:
function startMove3(obj, iTarget) { clearInterval(timer); timer = setInterval(function() { var speed = (iTarget - obj.offsetHeight) / 10; speed = obj.offsetHeight < iTarget ? Math.ceil(speed) : Math.floor(speed); if (obj.offsetHeight == iTarget) { clearInterval(timer); } else { obj.style.height = obj.offsetHeight + speed + 'px'; } }, 30); }
3. 函数的改进
以上函数都有一个确定,将改变的属性设死了,要么只能变化高度或宽度。我们可以再加入一个 attribute 参数 ,使得函数更加灵活。
改进代码:
function startMove(obj, attribute, iTarget) { clearInterval(obj.timer); obj.timer = setInterval(function() { var cur = parseInt(getStyle(obj, attribute)); var speed = (iTarget - cur) / 6; speed = speed > 0 ? Math.ceil(speed) : Math.floor(speed); if (cur == iTarget) { clearInterval(obj.timer); } else { obj.style[attribute] = cur + speed + 'px'; } }, 30); } //获取元素的CSS属性 function getStyle(obj, name) { if (obj.currentStyle) { return obj.currentStyle[name]; } else { return getComputedStyle(obj, false)[name]; } }
其中,getStyle(obj,name)是一个获取css属性的函数。
这个运动函数目前只能针对属性值含有px的,因此我们还需要多一条判断满足透明度改变的需求。
改进2:
function startMove(obj, attribute, iTarget) { clearInterval(obj.timer); obj.timer = setInterval(function() { var cur = attribute == 'opacity' ? Math.round(parseFloat(getStyle(obj, attribute) * 100)) : parseInt(getStyle(obj, attribute)); var speed = (iTarget - cur) / 6; speed = speed > 0 ? Math.ceil(speed) : Math.floor(speed); if (cur == iTarget) { clearInterval(obj.timer); } else { if (attribute == 'opacity') { obj.style.filter = "alpha(opacity:" + (cur + speed) + ")"; //针对IE obj.style.opacity = (cur + speed) / 100; //针对FireFox Chrome } else { obj.style[attribute] = cur + speed + 'px'; } } }, 30); }
代码中 Math.round(parseFloat(getStyle(obj, attribute) * 100)) 对 opacity 数值的处理 , 由于不同浏览器对小数取舍不一样,使用Math.round() 进行四舍五入 , 这样有的浏览器不会出现透明度到达目标值后闪了。
4. 链式运动
function startMove(obj, attribute, iTarget ,fnEnd) { clearInterval(obj.timer); obj.timer = setInterval(function() { var cur = attribute == 'opacity' ? Math.round(parseFloat(getStyle(obj, attribute) * 100)) : parseInt(getStyle(obj, attribute)); var speed = (iTarget - cur) / 6; speed = speed > 0 ? Math.ceil(speed) : Math.floor(speed); if (cur == iTarget) { clearInterval(obj.timer); if(fnEnd){ fnEnd(); } } else { if (attribute == 'opacity') { obj.style.filter = "alpha(opacity:" + (cur + speed) + ")"; obj.style.opacity = (cur + speed) / 100; } else { obj.style[attribute] = cur + speed + 'px'; } } }, 30); }
5. 多值多属性同时运动
引入一个json {width : target1,height:target2,opacity:target3}.
function startMove(obj, json, fnEnd) { clearInterval(obj.timer); obj.timer = setInterval(function() { for (var attr in json) { var cur = attr == 'opacity' ? Math.round(parseFloat(getStyle(obj, attr) * 100)) : parseInt(getStyle(obj, attr)); var speed = (json[attr] - cur) / 6; speed = speed > 0 ? Math.ceil(speed) : Math.floor(speed); if (cur == json[attr]) { clearInterval(obj.timer); if (fnEnd) { fnEnd(); } } else { if (attr == 'opacity') { obj.style.filter = "alpha(opacity:" + (cur + speed) + ")"; obj.style.opacity = (cur + speed) / 100; } else { obj.style[attr] = cur + speed + 'px'; } } } }, 30); }
如果多个属性目标值不一致,定时器启动的时候,目标值小的属性会先满足了关闭定时器的条件,而目标值大的属性还没有到达目标,因此会产生偏差。
改进代码(最终完美版运动代码):
function startMove(obj, json, fnEnd) { clearInterval(obj.timer); obj.timer = setInterval(function() { var bStop = true; for (var attr in json) { var cur = attr == 'opacity' ? Math.round(parseFloat(getStyle(obj, attr) * 100)) : parseInt(getStyle(obj, attr)); var speed = (json[attr] - cur) / 6; speed = speed > 0 ? Math.ceil(speed) : Math.floor(speed); if (cur != json[attr]) { bStop = false; } if (attr == 'opacity') { obj.style.filter = "alpha(opacity:" + (cur + speed) + ")"; obj.style.opacity = (cur + speed) / 100; } else { obj.style[attr] = cur + speed + 'px'; } } if(bStop){ clearInterval(obj.timer); if(fnEnd){ fnEnd(); } } }, 30); }