JavaScript实现动画的简易框架

运动的效果一般上都是在一定的时间内对其样式进行更改,从而在视觉上达到运动的感觉。这时候就要利用setInterval()在间隔时间内对其样式进行相应的改变。
想要一个物体运动一般需要原物体的位置,速度,运动到达的位置,运动的形式等。这些都会作为方法的参数进行传递。

前期准备

创建一个方法来进行获取该对象的属性值。需要的参数有两个:对象,属性名(运动的形式)。代码如下:

 function getStyle(obj, attr) {
    if (obj.currentStyle) {a
        return obj.currentStyle[attr];
    } else {
        return getComputedStyle(obj, false)[attr];
    }
} 

currentStyle是为了兼容IE6浏览器,兼容性情况如下

currentStyle.png

getComputedStyle兼容现在的大多数浏览器

getComputedStyle.png

方法的具体使用可以查看文档

1.单方向上的运动

html页面里面有三个li标签,用来演示效果。原始宽度为200px,高度为100px。

window.onload = function(){
    var lis = document.getElementsByTagName("li");
    for(var i = 0;i<lis.length;i++){
        lis[i].onmouseover = function(){
            startMove(this,10,400);
        }
        lis[i].onmouseout = function(){
            startMove(this,-10,200);
        }
    }
}
var timer = null;
function startMove(obj,speed,target){
    //一开始就进行清楚,是为了消除onmouseon和onouseover事件之间的影响
    //例如,当鼠标移上去后再移出来后就会发现li的宽度一直在10px之间不断的进行增加和减少的循环操作
    //这是因为onmouseover和onmouseout的setInterval还存在,固有下面一行来清楚之间的影响
    clearInterval(timer);
    timer = setInterval(function(){
        //如果已经达到了目标值,就停止
        if(obj.offsetWidth == target){
            clearInterval(timer);
        }else{
            obj.style.width = obj.offsetWidth + speed + "px";
        }
    },30)
}

这里先用宽度来进行展示。首先要弄懂style.width和offsetWidth的区别。

  • offsetWidth属性可以返回对象的padding+border+width属性值之和,style.width返回值就是定义的width属性值。
  • offsetWidth属性仅是可读属性,而style.width是可读写的。
  • offsetWidth属性返回值是整数,而style.width的返回值是字符串。
  • style.width仅能返回以style方式定义的内部样式表的width属性值。

startMove方法传入的是三个参数

  1. obj,对象参数
  2. speed,动画的变化速度
  3. target,变化所要达到的值

但是这里会有一个问题,就是timer是作为全局变量,每个li对象都是公用这么一个timer。
每个li运动之间的效果都会相互之间影响。问题如图所示:

相互之间的影响
相互之间的影响

解决这个问题很简单,就是把全局变量改成诶个li对象自己的变量。也就是为对象添加一个timer属性。
改进后的代码如下:

window.onload = function(){
    var lis = document.getElementsByTagName("li");
    for(var i = 0;i<lis.length;i++){
        lis[i].timer = null; //为每个li对象添加timer属性
        lis[i].onmouseover = function(){
            startMove(this,10,400);
        }
        lis[i].onmouseout = function(){
            startMove(this,-10,200);
        }
    }
}
//下面的timer也要相应的改成obj.timer
function startMove(obj,speed,target){
    clearInterval(obj.timer);
    obj.timer = setInterval(function(){
        if(obj.offsetWidth == target){
            clearInterval(obj.timer);
        }else{
            obj.style.width = obj.offsetWidth + speed + "px";
        }
    },30)
}

2.根据传递的属性来改变运动的形式

上面的代码只能进行单方向的运动,如果想要进行高度上的变化,就要把代码中的width相应的改成height。
这样就显得过于麻烦,可以改造一下startMove函数,可以根据传递进去的属性来改变运动的形式。这时候就要用到前期准备中的getStyle(obj,attr)方法了.

window.onload = function(){
    var lis = document.getElementsByTagName("li");
    for(var i = 0;i<lis.length;i++){
        lis[i].timer = null;
        lis[i].onmouseover = function(){
            startMove(this,10,200,"height");
        }
        lis[i].onmouseout = function(){
            startMove(this,-10,100,"height");
        }
    }
}
function startMove(obj,speed,target,attr){
    clearInterval(obj.timer);
    obj.timer = setInterval(function(){
        //用getStyle方法来获取对应属性的属性值
        var iattr = parseInt(getStyle(obj,attr));
        if(iattr == target){
            clearInterval(obj.timer);
        }else{
            //这里不能够使用style.+"attr"的形式
            obj.style[attr] = iattr + speed + "px";
        }
    },30)
}

但是这里只有够对width或者height这样简单的变化,如果是opacity透明度的话就要进行相应的判断处理。(这里的opacity使用的是0~1)

function startMove(obj,speed,target,attr){
    clearInterval(obj.timer);
    obj.timer = setInterval(function(){
       if(iattr == target){
            clearInterval(obj.timer);
        }else{
            //进行属性的判断
            if(attr == "opacity"){
                obj.style.opacity = target;
            }else{
                obj.style[attr] = iattr + speed + "px";
            }
        }
    },30)
}

3.缓冲运动

上面的运动的速度都是固定,并且要向startMove方法里面传递速度值。固定的速度看起来可能太僵硬,加上缓冲感觉更好些。缓冲运动的实现就是运动的加速度不断地额变小,这里就是将速度的值不断的变小,并且不传递进来速度值,而是根据目标值和原有值之间的差来决定。

function startMove(obj, target, attr) {
    clearInterval(obj.timer);
    obj.timer = setInterval(function() {
        var iattr = null;
        var speed = null;
        if (attr == "opacity") {
            //将小数的情况转成整数,方便后面进行速度的变化
            iattr = Math.round(parseFloat(getStyle(obj, attr)) * 100);
            speed = (target * 100 - iattr) / 8;
        } else {
            iattr = parseInt(getStyle(obj, attr));
            speed = (target - iattr) / 8;
        }
        //如果出现小数的情况,浏览器会自动社区小数点,故而需要向上或者向下取整
        speed = speed > 0 ? Math.ceil(speed) : Math.floor(speed);
        if (iattr == target) {
            clearInterval(obj.timer);
        } else {
            if (attr == "opacity") {
                obj.style.opacity = (iattr + speed) / 100;
            } else {
                obj.style[attr] = iattr + speed + "px";
            }
        }
    }, 30)
}

4.链式运动

上面的物体只能够执行一次运动,并没有第二次的运动。如何让其执行第一次执行变化后紧接着执行第二次的变化呢?这里让li的宽先变成400,紧接着高变为200。一开始想到的就是在鼠标事件里面再让其执行一个startMove()方法,情况如下:

lis[i].onmouseover = function() {
            startMove(this, 400, "width");
            startMove(this,200,"height");
        }

运行的时候会发现,只执行了第二个startMove方法。这是因为当第二个startMove方法执行的时候就会立刻把第一个的timer给清除了,所以只剩下第二个方法在执行。

解决的方法就是向startMove方法里面传递一个函数。

window.onload = function() {
    var lis = document.getElementsByTagName("li");
    for (var i = 0; i < lis.length; i++) {
        lis[i].timer = null;
        lis[i].onmouseover = function() {
            //第二个匿名function传递对象时不能够传递this,要将当前的this保存为一个变量再进行传递
            var li = this;
            startMove(this, 400, "width", function() {
                startMove(li, 200, "height");
            });
        }
        lis[i].onmouseout = function() {
            var li = this;
            startMove(this, 100, "height", function() {
                startMove(li, 200, "width");
            });
        }
    }
}
function startMove(obj, target, attr, fn) {
    clearInterval(obj.timer);
    obj.timer = setInterval(function() {
        var iattr = null;
        var speed = null;
        if (attr == "opacity") {
           iattr = Math.round(parseFloat(getStyle(obj, attr)) * 100);
            speed = (target * 100 - iattr) / 8;
        } else {
            iattr = parseInt(getStyle(obj, attr));
            speed = (target - iattr) / 8;
        }
        speed = speed > 0 ? Math.ceil(speed) : Math.floor(speed);
        if (iattr == target) {
            clearInterval(obj.timer);
            //当上一个运动完成时执行下一个运动
            if (fn) {
                fn();
            }
        } else {
            if (attr == "opacity") {
                obj.style.opacity = (iattr + speed) / 100;
            } else {
                obj.style[attr] = iattr + speed + "px";
            }
        }

    }, 30)
}

5.同时运动

同时运动需要一次传递多个属性和目标,要达到这个目的可以使用json。就相当于使用键值对的形式,如{width:400,height:200}。这样又可以吧startMove的的参数减少一个。

window.onload = function() {
    var lis = document.getElementsByTagName("li");
    for (var i = 0; i < lis.length; i++) {
        lis[i].timer = null;
        lis[i].onmouseover = function() {
            //第二个匿名function传递对象时不能够传递this,要将当前的this保存为一个变量再进行传递
            var li = this;
            startMove(this, {
                width: 400,
                height: 200,
                opacity: 0.3
            });
        }
        lis[i].onmouseout = function() {
            startMove(this, {
                width: 200,
                height: 100,
                opacity: 1
            })
        }
    }
}
//只需要对json进行遍历即可,并且target相应的换成json[attr]
function startMove(obj, json, fn) {
    clearInterval(obj.timer);
    obj.timer = setInterval(function() {
        var iattr = null;
        var speed = null;
        for (var attr in json) {
            if (attr == "opacity") {
               iattr = Math.round(parseFloat(getStyle(obj, attr)) * 100);
                speed = (json[attr] * 100 - iattr) / 8;
            } else {
                iattr = parseInt(getStyle(obj, attr));
                speed = (json[attr] - iattr) / 8;
            }
            speed = speed > 0 ? Math.ceil(speed) : Math.floor(speed);
            if (iattr == json[attr]) {
                clearInterval(obj.timer);
               if (fn) {
                    fn();
                }
            } else {
                if (attr == "opacity") {
                    obj.style.opacity = (iattr + speed) / 100;
                } else {
                    obj.style[attr] = iattr + speed + "px";
                }
            }
        }

    }, 30)
}

这里还有一个小问题,就是当一个属性的变化较小时,其他运动也到不到设置的目标值。解决这个问题只需要加入一个标志,用来判断运动是否达到当前目标值。

function startMove(obj, json, fn) {
    clearInterval(obj.timer);
    //运动是否完成的标志
    var flag = true;
    var iattr = null;
    var speed = null;
    obj.timer = setInterval(function() {
        for (var attr in json) {
            if (attr == "opacity") {
                iattr = Math.round(parseFloat(getStyle(obj, attr)) * 100);
                speed = (json[attr] * 100 - iattr) / 8;
            } else {
                iattr = parseInt(getStyle(obj, attr));
                speed = (json[attr] - iattr) / 8;
            }
            speed = speed > 0 ? Math.ceil(speed) : Math.floor(speed);
            //运动为完成,flag为false
            if (iattr != json[attr]) {
                flag = false;
            }
            if (attr == "opacity") {
                obj.style.opacity = (iattr + speed) / 100;
            } else {
                obj.style[attr] = iattr + speed + "px";
            }
            //运动完成
            if (flag) {
                clearInterval(obj.timer);
                if (fn) {
                    fn();
                }
            }
        }
    }, 30)
}

这个简易框架基本上已经完成了。这篇文章总结自慕课网JS动画效果

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容

  • 简介 W3C参考资料 基础入门 初探JavaScript 什么是JS? JS是通过事件给网页添加交互、修改样式 编...
    guodd369阅读 320评论 0 0
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,580评论 18 139
  • 问答题47 /72 常见浏览器兼容性问题与解决方案? 参考答案 (1)浏览器兼容问题一:不同浏览器的标签默认的外补...
    _Yfling阅读 13,725评论 1 92
  • 注意力真的太分散了,完全不能集中到一点 写文章, 贴代码,搜Markdown,找到了 Ulysses 这个工具,这...
    Albert陈凯阅读 167评论 1 0
  • 落花飘荡出沁芳,会真余香间。缠绵梨园妙韵中,幽归自怜锁眉葬红嫣。 微腮带怒颜面笑,宝玉何曾晓。水流...
    獨居老狗阅读 235评论 0 2