在web网页设计中,经常要使用到动画(如轮播图、随滚动窗口移动的广告栏等等),封装动画会节省很多代码,也让结构更简单清晰。这里先封装了一个匀速移动的动画和一个缓速移动的动画(通过offsetLeft/offsetTop来左右上下移动),然后逐渐深入下去。
匀速动画
ele为要移动的盒子,target为目标位置(像素),spd为计数器的频率
function animate1(ele,target,spd){
//要用定时器,先清除定时器,一个盒子只能有一个定时器,这样的话,不会和其他盒子出现定时器冲突
//而定时器本身将成为盒子的一个属性
clearInterval(ele.timer);
//我们要求盒子既能向前又能向后,那么我们的步长就得有正有负
//目标值如果大于当前值取正,目标值如果小于当前值取负
var speed = target>ele.offsetLeft?10:-10;
ele.timer = setInterval(function () {
//在执行之前就获取当前值和目标值之差
var val = target - ele.offsetLeft;
ele.style.left = ele.offsetLeft + speed + "px";
//目标值和当前值只差如果小于步长,那么就不能前进了
//因为步长有正有负,所有转换成绝对值来比较
if(Math.abs(val)<Math.abs(speed)){
ele.style.left = target + "px";
clearInterval(ele.timer);
}
},spd)
}
缓速动画(先快后慢)
ele为要移动的盒子,target为目标位置(像素),spd为计数器的频率
function animate2(ele,target,spd) {
//要用定时器,先清定时器
clearInterval(ele.timer);
//定义定时器
ele.timer = setInterval(function () {
//获取步长
//步长应该是越来越小的,缓动的算法。
var step = (target-ele.offsetLeft)/10;
//对步长进行二次加工(大于0向上取整,小于0向下取整)
step = step>0?Math.ceil(step):Math.floor(step);
//动画原理: 目标位置 = 当前位置 + 步长
ele.style.left = ele.offsetLeft + step + "px";
//检测缓动动画有没有停止
console.log(“完成”);
if(Math.abs(target-ele.offsetLeft)<=Math.abs(step)){
//处理小数赋值
ele.style.left = target + "px";
clearInterval(ele.timer);
}
},spd);
}
以上动画通过控制offsetLeft来移动,只能在父盒子中左右移动,我们如果想要盒子在窗口任意位置移动呢?
(前面的封装方法通过offsetLeft/offsetTop来左右上下移动,其实用元素距离父盒子的距离代替元素本身的top、left属性。为什么不直接用left、top呢?
因为div.style.left此类方法是完全对行内式来操作的,对它赋值没有问题,但是在获取值的时候如果本身没有定义则获取不到。导致我们获取left值时,是用offsetLeft模拟)
学习了一个新的方法,可以获取任意类型的CSS样式的属性值,它的兼容性写法如下
function getStyle(ele,attr){
if(window.getComputedStyle){
return window.getComputedStyle(ele,null)[attr];
}
return ele.currentStyle[attr];
}
基于此,我们将之前的缓速动画进行了新的封装
缓速动画(先快后慢)(单个属性)
这里ele表示移动的元素,attr表示要改变的属性,target表示要修改的属性值
function animate(ele,attr,target){
//先清定时器
clearInterval(ele.timer);
ele.timer = setInterval(function () {
var leader = parseInt(getStyle(ele,attr)) || 0;
//1.获取步长
var step = (target - leader)/10;
//2.二次加工步长
step = step>0?Math.ceil(step):Math.floor(step);
leader = leader + step;
//3.赋值
ele.style[attr] = leader + "px";
//4.清除定时器
if(Math.abs(target-leader)<=Math.abs(step)){
ele.style[attr] = target + "px";
clearInterval(ele.timer);
}
},25);
}
上面的动画,一次调用只能更改一个属性,如果我们要同时更改多个属性要怎么做?
封装一个可以对多个属性同时改变的缓动动画
缓速动画(先快后慢)(多个属性)
这里ele表示要移动的盒子,Json存储了它要改变的多个属性:值。
function animate(ele,json){
//先清定时器
clearInterval(ele.timer);
ele.timer = setInterval(function () {
//开闭原则
var bool = true;
//遍历属性和值,分别单独处理json
//attr == k(键) target == json[k](值)
for(var k in json){
//四步
var leader = parseInt(getStyle(ele,k)) || 0;
//1.获取步长
var step = (json[k] - leader)/10;
//2.二次加工步长
step = step>0?Math.ceil(step):Math.floor(step);
leader = leader + step;
//3.赋值
ele.style[k] = leader + "px";
//4.清除定时器
//判断: 目标值和当前值的差大于步长,就不能跳出循环
//不考虑小数的情况:目标位置和当前位置不相等,就不能清除清除定时器。
if(json[k] !== leader){
bool = false;
}
}
console.log(1);
//只有所有的属性都到了指定位置,bool值才不会变成false;
if(bool){
clearInterval(ele.timer);
}
},25);
}
注意到这里清除定时器的方法用到了开闭原则。另外,将需要更改的多个属性值,存在json中,举个例子
var json = {"left":10,"top":200,"width":300,"height":200};
这个动画还可以再升级,如果我们将盒子挪到一个新地方,同时改变了各种属性,完成了这一系列动作后,我们又要让他返回原来的属性或者再变化成一个新的属性,而又不想重复调用,触发两次事件,要怎么做?
套用回调函数
缓速动画(先快后慢)(多个属性+回调函数)
这里ele表示要移动的盒子,Json存储了它要改变的多个属性:值。fn表示新的函数
function animate(ele,json,fn){
//先清定时器
clearInterval(ele.timer);
ele.timer = setInterval(function () {
//开闭原则
var bool = true;
//遍历属性和值,分别单独处理json
//attr == k(键) target == json[k](值)
for(var k in json){
//四部
var leader = parseInt(getStyle(ele,k)) || 0;
//1.获取步长
var step = (json[k] - leader)/10;
//2.二次加工步长
step = step>0?Math.ceil(step):Math.floor(step);
leader = leader + step;
//3.赋值
ele.style[k] = leader + "px";
//4.清除定时器
//判断: 目标值和当前值的差大于步长,就不能跳出循环
//不考虑小数的情况:目标位置和当前位置不相等,就不能清除清除定时器。
if(json[k] !== leader){
bool = false;
}
}
console.log(1);
//只有所有的属性都到了指定位置,bool值才不会变成false;
if(bool){
clearInterval(ele.timer);
//所有程序执行完毕了,现在可以执行回调函数了
//如果存在fn,执行fn
if(fn){
fn();
}
}
},25);
}
在所有第一步程序执行完毕后,判断有没有回调函数,如果有,执行它。
调用方式:
animate(div,json2, function () {
animate(div,json1);
});
这是可以层层往里套用的。
做到这里,发现还有一个问题,对于没有px单位的属性值,上面的封装框架无法赋值,如
border-radius:1px 2px 4px 6px; //分别赋值:div.style.borderTopLeftRadius="1px";
opacity:0.5;//见下文
background:rgba(0,0.5,0,0.4);//Jquary中调用插件来赋值,暂且不谈
z-index:2;//见下文
如果在我们前面的Json中加入opacity和Z-index,则需要再加入句子,特殊取值特殊处理
(基本思路,对于opacity,要写成百分制,在取值时对当前值*100,赋值时再/100;对于z-index,不用缓动赋值,直接一步到位)
加入了透明度和层级,对程序进行优化
var json = {"left":300,"top":200,"width":300,"height":200,"opacity": 30,"zIndex": 5};
缓速动画(先快后慢)(多个属性+回调函数+兼容透明度和层级属性)
function animate(ele,json,fn){
//先清定时器
clearInterval(ele.timer);
ele.timer = setInterval(function () {
//开闭原则
var bool = true;
//遍历属性和值,分别单独处理json
//attr == k(键) target == json[k](值)
for(var k in json){
var leader;
//判断如果属性为opacity的时候特殊获取值
if(k === "opacity"){
leader = getStyle(ele,k)*100 || 1;
}else{
leader = parseInt(getStyle(ele,k)) || 0;
}
//1.获取步长
var step = (json[k] - leader)/10;
//2.二次加工步长
step = step>0?Math.ceil(step):Math.floor(step);
leader = leader + step;
//3.赋值
//特殊情况特殊赋值
if(k === "opacity"){
ele.style[k] = leader/100;
//兼容IE678
ele.style.filter = "alpha(opacity="+leader+")";
//如果是层级,一次赋值成功,不需要缓动赋值
}else if(k === "zIndex"){
ele.style.zIndex = json[k];
}else{
ele.style[k] = leader + "px";
}
//4.清除定时器
//判断: 目标值和当前值的差大于步长,就不能跳出循环
//不考虑小数的情况:目标位置和当前位置不相等,就不能清除清除定时器。
// if(json[k] !== leader){
if(Math.abs(json[k]-leader)>Math.abs(step)){
bool = false;
}
}
console.log(1);
//只有所有的属性都到了指定位置,bool值才不会变成false;
if(bool){
clearInterval(ele.timer);
//所有程序执行完毕了,现在可以执行回调函数了
//只有传递了回调函数,才能执行
if(fn){
fn();
}
}
},25);
}
目前就知道这些,但是还是有一个bug没有解决。在IE中可以清除定时器,但是在谷歌浏览器中无法清除定时器,从而无法进入函数执行回调函数。这个问题尚有待解决