本文深入分析JavaScript中多物体同时运动的实现原理,重点解决多物体共用计时器导致的冲突问题。详细探讨了offsetWidth包含padding和border引发的尺寸计算Bug,以及opacity取值取整和浮点数精度导致的闪烁问题,提供了基于currentStyle和乘100法的完整兼容方案。
多物体运动框架实现原理
本文通过具体实例解析了JavaScript中实现多个物体同时运动的功能逻辑,旨在为开发者提供可参考的代码实现与优化思路。

多物体运动与单物体运动的核心区别在于:系统必须明确当前正在执行动画的目标对象。因此,运动函数需要接收两个关键参数:目标位置iTarget以及对应的DOM对象obj。此外,每个物体必须拥有独立的计时器,若多个物体共用一个计时器,当一个物体尚未完成运动而另一个物体触发运动时,会导致动画卡顿或混乱。
window.onload=function(){
var aDiv=document.getElementsByTagName("div");
var timer=null;
var i;
for(i=0;i<aDiv.length;i++){
aDiv[i].timer=null;
aDiv[i].onmouseover=function(){
startMove(this,300);
};
aDiv[i].onmouseout=function(){
startMove(this,100);
};
}
function startMove(obj,iTarget){
clearInterval(obj.timer);
obj.timer=setInterval(function(){
var iSpeed=(iTarget-obj.offsetWidth)/8;
iSpeed=iSpeed>0?Math.ceil(iSpeed):Math.floor(iSpeed); //凡是要用缓冲运动,肯定缺不了取整,为了让他能到目的点(不然无法完全贴合)
if(obj.offsetWidth==iTarget){
clearInterval(obj.timer);
}
else{
obj.style.width=obj.offsetWidth+iSpeed+'px';
}
},30);
};
};
在多物体运动场景中,所有相关变量和状态均不能全局公用,必须与具体的运动对象绑定。
速度变量及其他属性值(例如透明度等)必须作为运动对象的独立属性进行存储,以确保各物体运动的独立性。
offsetWidth计算Bug与解决方案
在使用offsetWidth、offsetHeight、offsetLeft等属性时,存在一个常见的计算陷阱。以offsetWidth为例,该属性返回的值不仅包含元素的width,还包含了padding和border的宽度。例如,一个div的width设置为100px且带有1px边框,其offsetWidth实际为102px。若在运动过程中直接通过div.style.width = div.offsetWidth - 1 + 'px'进行计算,在没有边框的情况下,元素会不断缩小直至消失;而在有边框的情况下,width从100px变为101px时,offsetWidth变为103px,导致元素不断变大,产生尺寸异常。
针对上述offsetWidth的计算问题,可以采用以下解决方案:
使用currentStyle或getComputedStyle获取实际渲染宽度,并结合parseInt进行整数解析。具体代码为:div.style.width = parseInt(getStyle(div, 'width')) - 1 + 'px'。其中,getStyle是封装好的获取样式的函数,内部处理了currentStyle兼容逻辑;parseInt用于将字符串转换为整数,确保尺寸计算的准确性。
在此基础上,可以扩展运动框架以支持任意属性的变化。
通过同一套运动框架,可以实现一个物体宽度变化、另一个物体高度变化,以及第三个物体透明度变化的复合动画效果。
window.onload=function(){
var aDiv=document.getElementsByTagName("div");
var timer=null;
aDiv[0].onmouseover=function(){
startMove(this,'width',300);
};
aDiv[0].onmouseout=function(){
startMove(this,'width',100);
};
function getStyle(obj,attr){
if(obj.currentStyle){
return obj.currentStyle[attr];
}
else{
return getComputedStyle(obj,false)[attr];
}
};
function startMove(obj,attr,iTarget){
clearInterval(obj.timer);
obj.timer=setInterval(function(){
var iCur=parseInt(getStyle(obj,attr));
var iSpeed=(iTarget-iCur)/8;
iSpeed=iSpeed>0?Math.ceil(iSpeed):Math.floor(iSpeed); //凡是要用缓冲运动,肯定缺不了取整,为了让他能到目的点(不然无法完全贴合)
if(iCur==iTarget){
clearInterval(obj.timer);
}
else{
obj.style[attr]=iCur+iSpeed+'px';
}
},30);
};
};
透明度兼容性与浮点数精度处理
然而,这套基础运动框架在初期版本中存在一个缺陷:未对透明度(opacity)属性提供支持。
问题一:
var iCur=parseInt(getStyle(obj,attr));
opacity的值通常是小数(如0.5),若直接使用parseInt进行取整,结果始终为0,导致透明度无法正确显示。因此,需要对opacity的处理逻辑进行修改。
if(attr=='opacity'){
var iCur=parseFloat(getStyle(obj,attr))*100; //为了其他程序不用修改,这里统一乘100
}
else{
var iCur=parseInt(getStyle(obj,attr));
}
问题二:
obj.style[attr]=iCur+iSpeed+'px';
按照原有的运动写法,若直接处理小数opacity,会导致动画无法平滑过渡。
aDiv.style.opacity=50px;
应改为针对opacity进行特殊处理,具体逻辑如下:
if(attr=='opacity'){
obj.style.filter='alpha(opacity:'+(iCur+iSpeed)+')';
obj.style.opacity=(iCur+iSpeed)/100;
}
else{
obj.style[attr]=iCur+iSpeed+'px'
}
在计算机内部,小数并非以实际数值存储,而是通过模拟方式存储。一个简单的例子是:
执行alert(0.07 * 100),输出结果并非精确的7,而是7.0000...001。类似地,许多小数运算都存在精度误差,这会影响动画的平滑性。
因此,若直接使用var iCur = parseFloat(getStyle(obj, attr)) * 100,可能会因浮点数精度问题导致动画闪烁。解决方法是避免直接使用小数,而是通过整数运算来模拟透明度变化。
var iCur=parseInt(parseFloat(getStyle(obj,attr))*100);
更多关于JavaScript相关内容感兴趣的读者可查看本站专题:《JavaScript运动效果与技巧汇总》、《JavaScript切换特效与技巧总结》、《JavaScript查找算法技巧总结》、《JavaScript动画特效与技巧汇总》、《JavaScript错误与调试技巧总结》、《JavaScript数据结构与算法技巧总结》、《JavaScript遍历算法与技巧总结》及《JavaScript数学运算用法总结》。
希望本文所述的JavaScript多物体运动实现方案及兼容性处理技巧,能对广大JavaScript程序设计者有所帮助。