js运动(四)——链式运动及完美运动框架(元素多属性同时运动)
1.链式运动
所谓链式运动,也即元素的可运动属性一个接着一个的发生变化,有其一定的需求性。前面所学的缓动函数无法满足链式运动的需求,缓动函数的叠加与定时器分配(一个元素只有一个定时器)之间构成了矛盾,造成只有最后一个缓动函数有效的局面。
为了能够实现链式运动,需要重新封装缓动函数。整体的思路是,在缓动函数中加一个可选的函数参数,当传入该函数时,则在一个属性变化完成后调用。
这样之后,每个缓动函数后面都可以再跟一个缓动函数,类似于递归,直至不再传递缓动函数。
缓动函数修改如下。
function chainingMoveTowards(obj, attribute, iTarget, /*optional*/func){
var speed;
clearInterval(obj.timer);
obj.timer = setInterval(function(){
var cur = 0;
if(attribute == 'opacity'){
cur = Math.round(parseFloat(getStyleOf(obj, attribute) * 100));
}else {
cur = parseInt(getStyleOf(obj, attribute));
}
speed = (iTarget - cur) / 6;
speed=speed>0?Math.ceil(speed):Math.floor(speed);
if(cur == iTarget){
clearInterval(obj.timer);
if(func) func();
}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);
}
调用方法如下。
window.onload = function(){
var oDiv = document.getElementById("div1");
oDiv.onmouseover = function(){
chainingMoveTowards(this, 'width', 400, function(){
chainingMoveTowards(oDiv, 'height', 400);
})
};
oDiv.onmouseout = function(){
chainingMoveTowards(this, 'height', 50, function(){
chainingMoveTowards(oDiv, 'width', 100);
})
};
};
以上有几点需要注意:
- 因为给缓动参数的函数参数是可选的,所以从代码可读性来说,应当增加
/*optional*/
标志; - 在链式调用的过程中,谨慎使用
this
,第一次调用的时候this代表元素,第二次再调用时就不是了; - 一般老说,链式运动的进行与恢复之间的顺序应该颠倒,如先变长再变宽,恢复时应该先恢复宽度再恢复长度。
链式运动有其优点,但是仍然无法满足一些特有的运动情况,如它只能使得元素的属性一个接着一个变化,但不能使得属性们同时变化。
作为进阶,需要向元素多属性同时运动方向上思考,这就要说道下面的完美运动框架了。
2.完美运动框架
纵观前面学习的运动知识,因为只给缓动函数传递了一个属性和一个目标值,所以只能对一个属性进行变化,有没有办法传入多个属性和目标值呢?
有!选项对象。
选项对象可以定义多个键值对。在缓动函数中对选项对象进行遍历,并一一作出相应动作,即可实现多个元素同时运动。
(原教程中把这个选项对象说成是json,个人觉得有误,json的键必须带双引号,而选项对象可以没有,所以传入的应该是一个对象而不是json)。
所以缓动函数如下。
// not final one
function multiAttrMoveTowards(elt, obj, /*optional*/func){
var speed;
clearInterval(elt.timer);
elt.timer = setInterval(function(){
for (var attr in obj){
var cur = 0;
if(attr == 'opacity'){
cur = Math.round(parseFloat(getStyleOf(elt, attr) * 100));
}else {
cur = parseInt(getStyleOf(elt, attr));
}
speed = (obj[attr] - cur) / 6;
speed=speed>0?Math.ceil(speed):Math.floor(speed);
if(cur == obj[attr]){
clearInterval(elt.timer);
if(func) func();
}else{
if(attr == 'opacity'){
elt.style.filter = 'alpha(opacity:' + (cur + speed) + ")";
elt.style.opacity = (cur + speed) / 100;
}else{
elt.style[attr] = cur + speed + "px";
}
}
}
},30);
}
这个缓动函数完成了多个属性的同时变化。但当其中一个属性变化很小时,属性们就有可能无法运行到终点。因为看似没有问题的程序,其实仔细一看,当有其中一个属性到达的时候,就关闭了定时器,其他属性自然无法再运动。
if(cur == obj[attr]){
clearInterval(elt.timer);
if(func) func();
}
因此,需要一个判断。一旦有一个没完成运动,则不关闭定时器。则,缓动函数及其应用如下。
window.onload = function (){
var oDiv = document.getElementsByTagName("div")[0];
oDiv.onmouseover = function(){
multiAttrMoveTowards(this, {width: 101, height: 400, opacity:100});
};
oDiv.onmouseout = function(){
multiAttrMoveTowards(this, {width: 100, height: 50 , opacity:30} );
};
};
function multiAttrMoveTowards(elt, obj, /*optional*/func){
var speed;
clearInterval(elt.timer);
elt.timer = setInterval(function(){
var allArrive = true;
for (var attr in obj){
var cur = 0;
if(attr == 'opacity'){
cur = Math.round(parseFloat(getStyleOf(elt, attr) * 100));
}else {
cur = parseInt(getStyleOf(elt, attr));
}
speed = (obj[attr] - cur) / 6;
speed=speed>0?Math.ceil(speed):Math.floor(speed);
if(cur != obj[attr]){
allArrive = false;
}
if(attr == 'opacity'){
elt.style.filter = 'alpha(opacity:' + (cur + speed) + ")";
elt.style.opacity = (cur + speed) / 100;
}else{
elt.style[attr] = cur + speed + "px";
}
}
if (allArrive){
clearInterval(elt.timer);
if(func) func();
}
},30);
}
为了规范化,将缓动函数里面的参数进行了更改。elt为元素(Element), obj为选项对象(Object)。最后一个可选的函数参数可以不要,但为了与链式运动的缓动函数兼容,可保留。
3.总结
以上即为今日所学。原教程中有几个对应的例子,但其只针对js进行讲说,忽略了元素布局,恰恰布局是其中比较重要的部分,因此,例子部分需时间专研,延后实施。
参考文献
【1】智能社:JavaScript教程——从入门到精通 ( https://ke.qq.com/course/152997?taid=766917950461349 )
【2】js运动(三)—— 多元素的运动)( https://blog.csdn.net/qq_31305965/article/details/89917314 )