首先来看最基础的运动:单个物体向右匀速运动到某一点停止
例子:一个按钮,一个div,点击按钮,让div向右运动到某一个位置暂停
// 原理: 1 获取物体当前的位置 oDiv.offsetleft // 2 利用定时器,每隔一定的时间,让物体位置增加相同的距离(10px)。 // 3 判断物体是否移动到指定位置(500px),如果到达,就清除定时器 var oBtn = document.getElementById(‘btn‘); var oDiv = document.getElementById(‘div1‘); var iTimer = null; oBtn.onclick = function() { iTimer = setInterval(function() { if (oDiv.offsetLeft == 500) { clearInterval(iTimer); } else { oDiv.style.left = oDiv.offsetLeft + 10 + ‘px‘; } }, 30); } // 存在问题: 1 当我们一直不停的点按钮的时候,物体运动速度会加快。 // 原因:我们每点击一次按钮,就会开启一个定时器,有时候上次定时器还没清除, 这个就开启了,当多个定时器一起物体运动就会加快。 // 解决: 在点击按钮的时候,先清除上次的定时器。保证运动中,只有一个定时器在执行。 // 2 当我们指定的位置不是500,是别的位置的时候,有可能物体停不下来。 // 原因:物体每次向前移动10px,不一定就正好到我们指定的位置,除非这个数字刚好整 除每次移动的距离。其实也不算是个问题,但是个需要注意的点。 // 解决:在作条件判断的时候,判断范围,而不是指定的位置。(oDiv.offsetLeft >= 500)
所以我们可以知道运动需要注意:1 运动前清除上次定时器
2 开启定时器,指向运动
3 运动中指定运动的形式(匀速缓存还是什么的),并且加入条件判断,以便停止
封装函数:对于以上运动的改进代码,我就不做具体的示范了,我们直接来进行简单的封装。在封装函数中顺便改进上面的函数了。
var oBtn = document.getElementById(‘btn‘); var oDiv = document.getElementById(‘div1‘); var iTimer = null; oBtn.onclick = function() { startMove(); } // 封装后的函数 function startMove() { clearInterval(iTimer); //运动前清除定时器 iTimer = setInterval(function() { if (oDiv.offsetLeft == 500) { clearInterval(iTimer); //满足条件清除定时器 } else { oDiv.style.left = oDiv.offsetLeft + 10 + ‘px‘; //每次向前移动 } }, 30); }
扩展需求1:根据上面的函数我们可以实现,一个物体向右运动到某一个位置。那么我们现在如果有两个物体,一个向左运动,一个向右运动,且运动的目的地是不同的,那么上面的函数是不是就不满足了呢?
改进:多物体多方向运动
// 函数封装原则: 1 不变的不动 // 2 改变的东西作为参数 // 3 改变太多的进行条件判断 // 改变的是运动的物体(obj)运动方向(iSpeed)和运动的目标(iTarget) ,如下作为参数 oDiv1.onmouseover = function() { startMove(this,0, 10); } oDiv1.onmouseout = function() { startMove(this,-100, -10); } function startMove(obj, iTarget, iSpeed) { clearInterval(iTimer); iTimer = setInterval(function() { if (obj.offsetLeft == iTarget) { clearInterval(iTimer); } else { obj.style.left = obj.offsetLeft + iSpeed + ‘px‘; } }, 30); }
扩大需求2:现在我们不仅仅改变的是物体左右移动,我们想让一个物体的透明度也能改变。能作出淡入淡出的效果。
存在的问题是:
问题1 : 在css中,不同浏览器对透明度的设置方式不同。
IE: filter: alpha(opacity=30);(0-100) 其他: opacity: 0.3;(0-1) // 所以存在的问题是:用哪种方式来判断透明度是否到达我们所指定的目标,是用0-1,还是0-100来判断呢? 在这里要普及一个知识 : alert(0.1+0.2) // 0.30000004 alert(0.2+0.7) // 0.89999999 // 看上面的代码,会发现并没有像我们想象中弹出0.3,和0.9,是因为在JS中对于小数//的计算结果,是近似值。所以我们用0-100判断更加准确。
问题2 :如何获取到物体的透明度,用obj.style.opacity 吗?
这里要说明一个知识点,在JS中用style只能获取行间样式。 不能获取css中的样式。这个时候我们用什么呢?下面的两个方法可以获取非行间样式,有兼容问题:
1 getComputedStyle: getComputedStyle(element,随意值(写什么都可以,比如false)).attr:这个获取的是计算机计算后的样式,不能获取复合样式,只有标准浏览器支持IE6 7 8不兼容。
2 currentStyle :currentStyle[attr],只有IE6 7 8兼容 ,可以获取非行间样式,还可以获取自定义样式。
所以需要做一下兼容,我们可以封装成一个函数,如下:
function getStyle ( obj, attr ) { if(obj.currentStyle){ obj.currentStyle[attr] }else{ getComputedStyle( obj )[attr]; } } //简写方式 function getStyle ( obj, attr ) { return obj.currentStyle?obj.currentStyle[attr] : getComputedStyle( obj )[attr]; } //注意 IE下虽然没有opacity,但也能获取到opacity值,主要是因为currentStyle的原因,它能读取到任何样式的值,哪怕不存在
所以对于透明度的改变,我们可以封装成如下的函数:
function startMove2(obj, iTarget, iSpeed) { clearInterval(iTimer); var iCur = 0; //用来存放当前的透明度值 iTimer = setInterval(function() { // iCur = getStyle( obj, ‘opacity‘ ) * 100; // 因为getStyle取出来的是小数,乘以100, 是29.999900000045 这样的数,这样我们无法和iTarget进行判断,所以我们可以进行四舍五入,如下: iCur = Math.round(getStyle( obj, ‘opacity‘ ) * 100); if (iCur == iTarget) { clearInterval(iTimer); } else { //对不同的浏览器进行分别处理 obj.style.opacity = (iCur + iSpeed) / 100; obj.style.filter = ‘alpha(opacity=‘+ (iCur + iSpeed) +‘)‘; } }, 30); }
这只是改变透明度的函数封装,要是能把透明度的封装函数和前面的运动封装函数,结合起来,不就能封装成一个可以改变透明度,也可以改变位置的函数了吗。。。。
那么依照我们的合并原则,找出两个封装函数的不同之处:
1 运动的属性不同(attr)
2 因为不同的属性处理不同,其实主要是透明度处理是有差别的,其他宽高改变位置改变其实都一样)这个时候的差别,可以采用判断来解决
// 还解决了解决了一个问题是因为之前只有一个定时器,现在因为有多个属性可以运动了,所以就不能只用一个定时器了去控制了,每个物体运动的时候就应该有自己的定时器,所以就把定时器就用obj.timer上,这样每个物体都不同啦。 function startMove(obj, attr, iTarget, iSpeed) { clearInterval(obj.iTimer); var iCur = 0; obj.iTimer = setInterval(function() { //对透明度进行判断如果是就进行上面的处理,不是就进行下面的处理 if (attr == ‘opacity‘) { iCur = Math.round(getStyle( obj, ‘opacity‘ ) * 100); } else { iCur = parseInt(getStyle(obj, attr)); } if (iCur == iTarget) { clearInterval(obj.iTimer); } else { if (attr == ‘opacity‘) { obj.style.opacity = (iCur + iSpeed) / 100; obj.style.filter = ‘alpha(opacity=‘+ (iCur + iSpeed) +‘)‘; } else { obj.style[attr] = iCur + iSpeed + ‘px‘; } } }, 30); } function getStyle(obj, attr) { if (obj.currentStyle) { return obj.currentStyle[attr]; } else { return getComputedStyle(obj, false)[attr]; } }
不过上面的函数,还是不能满足某些需求,比如我想要一个物体的两个属性同时运动,下面这样是实现不了的。
//下面的运动会清除掉上面的定时器 startMove(this, ‘width‘, 200, 10); startMove(this, ‘height‘, 200, 10);
那么怎么样实现这两个属性可以同时运动呢?这个时候我们可以考虑用json的格式
oDiv1.onclick = function() { startMove(this, { width : 200, height: 300 }, 10); } function startMove(obj, json, iSpeed) { clearInterval(obj.iTimer); var iCur = 0; obj.iTimer = setInterval(function() { //定时器每走一下就要把要运动的属性都推进一次 for ( var attr in json ) { var iTarget = json[attr];//把我们传进来的属性值赋给目标值 if (attr == ‘opacity‘) { iCur = Math.round(getStyle( obj, ‘opacity‘ ) * 100); } else { iCur = parseInt(getStyle(obj, attr)); } if (iCur == iTarget) { clearInterval(obj.iTimer); } else { if (attr == ‘opacity‘) { obj.style.opacity = (iCur + iSpeed) / 100; obj.style.filter = ‘alpha(opacity=‘+ (iCur + iSpeed) +‘)‘; } else { obj.style[attr] = iCur + iSpeed + ‘px‘; } } } }, 30); } function getStyle(obj, attr) { if (obj.currentStyle) { return obj.currentStyle[attr]; } else { return getComputedStyle(obj, false)[attr]; } }
存在问题:上面的运动速度都是一样的,但是传进去的目标点是不一样的,这个时候就会有一个属性先到,那么运动就终止了。所以我们需要解决的是,当所有的属性都到达了目标点,才让运动终止。
解决:定义一个开关,每次运动前假设它是真的,在运动中,只要有一个属性没运动到终点,就把开关变成假,在循环外面进行判断,当所有属性都运动到终点了,开关肯定都是真的,就会清除定时器了。
oDiv1.onclick = function() { startMove(this, { width : 200, height: 300 }, 10); } function startMove(obj, json, iSpeed) { clearInterval(obj.iTimer); var iCur = 0; obj.iTimer = setInterval(function() { //定时器每走一下就要把要运动的属性都推进一次 var iBtn = true;//每一次运动前都初始化为真,就是假设所有属性都到了 for ( var attr in json ) { var iTarget = json[attr];//把我们传进来的属性值赋给目标值 if (attr == ‘opacity‘) { iCur = Math.round(getStyle( obj, ‘opacity‘ ) * 100); } else { iCur = parseInt(getStyle(obj, attr)); } if (iCur != iTarget) { iBtn = false; //如果有一个属性没到,就把这个开关变成假if (attr == ‘opacity‘) { obj.style.opacity = (iCur + iSpeed) / 100; obj.style.filter = ‘alpha(opacity=‘+ (iCur + iSpeed) +‘)‘; } else { obj.style[attr] = iCur + iSpeed + ‘px‘; } } } //在这里来看下,所有属性是不是都到了目标点,当这里变真的了,说明所有属性都到了目标点,就可以清除定时器了 if (iBtn) { clearInterval(obj.iTimer); } }, 30); } function getStyle(obj, attr) { if (obj.currentStyle) { return obj.currentStyle[attr]; } else { return getComputedStyle(obj, false)[attr]; } }
扩大需求:假如我们这次实现的不是多个属性同时运动,我希望先把高改变,接着再改变宽,这个时候,我们需要的就是个回调函数了(fn)。za
oDiv1.onclick = function() { startMove(this, { width : 200 }, 10, function() { startMove(this, { height : 200 }, 10); }); } function startMove(obj, json, iSpeed, fn) { clearInterval(obj.iTimer); var iCur = 0; obj.iTimer = setInterval(function() { var iBtn = true; for ( var attr in json ) { var iTarget = json[attr]; if (attr == ‘opacity‘) { iCur = Math.round(getStyle( obj, ‘opacity‘ ) * 100); } else { iCur = parseInt(getStyle(obj, attr)); } if (iCur != iTarget) { iBtn = false; if (attr == ‘opacity‘) { obj.style.opacity = (iCur + iSpeed) / 100; obj.style.filter = ‘alpha(opacity=‘+ (iCur + iSpeed) +‘)‘; } else { obj.style[attr] = iCur + iSpeed + ‘px‘; } } } if (iBtn) { clearInterval(obj.iTimer); fn && fn.call(obj); //如果回调函数存在再执行,call方向为了修正this的指向,之前关于this文章有讲过这个用法。
} }, 30); } function getStyle(obj, attr) { if (obj.currentStyle) { return obj.currentStyle[attr]; } else { return getComputedStyle(obj, false)[attr]; } }