首页 » JavaScript » JS 动画初探(下)

最后更新于 2018年12月17日

上一章我们讲在元素移动到end位置的时候,会有两个动作同时产生,一个是正常的移动element.style[attr] = parseInt(getStyle(element,attr)) + step + 'px';一个是if判断如果当前的位置已经大于或者等于end的时候,element.style[attr] = end + 'px';这样可以解决动画的最后一些的突兀感,不会先超出,然后再移动回来,而是在同一个时间完成,但是这样并不是很好的解决办法。

实际上你移动距离还是超出了,只不过后面又回来了,这样可能对以后js代码有影响,所以这里就再次调整。

解决超出返回的回弹问题

var timer = setInterval(function(){
            if(step > 0 && Math.abs(parseInt(getStyle(element,attr)) - end) <= step) {
                element.style[attr] = end + 'px';
                clearInterval(timer);
            }else if(step < 0 && (parseInt(getStyle(element,attr)) - end) <= Math.abs(step)) {
                element.style[attr] = end + 'px';
                clearInterval(timer);
            }else {
                element.style[attr] = parseInt(getStyle(element,attr)) + step + 'px';
            }
        },time)

这里普通状态的 移动在else中运行,当元素当前的位置和end的值相减得到还需要移动的距离,如果这个距离小于或者等于每次step步进的值,那么久直接让他的位置等于end的位置,这样完美的解决了超过然后返回来的问题。

当元素的位置减去end的值,如果是往左得到的是负值,所以要用Math.abs()来转换成绝对值,也就是正值去比对,如果是往右,在元素不可能超出屏幕外的前提下,也就是说元素最大的移动位置值是0,那么0 - end的值,end往左是-xxx的负值,负负得正,这里得到的是正值,不用转换,但是步进step是负值,前面if判断的时候写的,所以要对step转换。

即便0-end的值是负值,

下面我们再来优化一下设置移动方向的问题,我们之前用的是top,left这两个值,但是很容易让人误解,以为right和bottom也能使用,但实际上只有top和left这两个选择,那么我们可以改用x和y这两个坐标名来传参,那么就要做个判断了。

优化移动方向传参

var attr = obj['attr'] == 'x' ? 'left' : obj['attr'] == 'y' ? 'top' : 'left';

把attr的判断改一下,如果是x就返回left,如果是y则返回top,默认是left。

缓冲远动

元素先快然后越来越慢,最后停止。

//设置动画
$('#pox').click(function() {
    $('#box').animation({
        'attr': 'x',
        'step': 7,
        'start': 300,
        'end': -500,
        'type': 1,
        'speed': 6
    })
});

//设置动画
Base.prototype.animation = function(obj) {
    for (var i = 0; i < this.arr.length; i++) {
        var element = this.arr[i];
        var attr = obj['attr'] == 'x' ? 'left' : obj['attr'] == 'y' ? 'top' : 'left';
        var step = obj['step'] != undefined ? obj['step'] : 10;
        var start = obj['start'] != undefined ? obj['start'] : parseInt(getStyle(element, attr));
        var time = obj['time'] != undefined ? obj['time'] : 50;
        var end = start + obj['end'];
        var type = obj['type'] == 0 ? 'constant' : obj['type'] == 1 ? 'buffer' : 'buffer';
        var speed = obj['speed'] != undefined ? obj['speed'] : 6;
        if (start > end) step = -step;
        element.style[attr] = start + 'px';
        clearInterval(window.timer);
        timer = setInterval(function() {

            if (type == 'buffer') {
                step = (end - parseInt(getStyle(element, attr))) / speed;
                step = (step > 0) ? Math.ceil(step) : Math.floor(step);
            }
            if (step > 0 && Math.abs(parseInt(getStyle(element, attr)) - end) <= step) {
                element.style[attr] = end + 'px';
                clearInterval(timer);
            } else if (step < 0 && (parseInt(getStyle(element, attr)) - end) <= Math.abs(step)) {
                element.style[attr] = end + 'px';
                clearInterval(timer);
            } else {
                element.style[attr] = parseInt(getStyle(element, attr)) + step + 'px';
            }
        }, time)
    }
    return this;
}

这里我们新增加了两个值,一个是type,表示是否缓动,一个是speed表示倍速。

然后if判断,如果是是obj[‘attr’]是1,那么type等于缓动buffer,然后再判断是否设置了倍速,这里我们设置了6。

为了实现缓动效果,那么每次步进的值都不一样,所以要在间歇调用的函数开头就要对step做改动。

if判断如果type是buffer,那么让step等于(end - 当前元素位置)除以speed。

然后step在往右时得到的是正值,但是会有小数点,我们要做舍入操作,Math中有一个ceil()方法,它不管小数点后面的数字多大,只要有便会进1。

step往左的时候,得到的是负值,负值使用ceil()无法进1,反倒是会舍弃小数点后面的数,这里就要使用floor()方法,floor()方法在负数的时候只要小数点后面有值,就会进1。

ceil()和floor()的区别

ceil在值是正值的时候,小数点后面有值便会进1,负值的时候舍去小数点后面的值。floor则是相反,正值的时候舍去小数点后面的值,负值的时候小数点后面有值便会进1。

为什么要使用这两个方法,是因为当元素移动的距离与end值越来越进的时候,step得到的值会越来越小,如果我们不让他后面的值都是一样的话,就没有缓动的效果了,他会是一个加速状态,如果使用了这两个方法,那么最后的那段距离会每次以1px的值进行移动,应为0.xx的时候这两个方法都进1了,这样最后就会达到想要的效果。

计算的过程:

end = -500;start = 300;speed = 6;type = 1;传入的值

end = start + obj[‘end’] = 300 - 500 = -200;

step = (-200 - 300)/6 = -83.3333333.... ≈ -84;

第一次移动300-84 = 216,然后这样无限重复,然后到了元素的位置是-198的时候

step = (-200 + 198)/6 = -0.3333.... ≈ -1;

if判断stpe<0;并且-198 -(-200) <= -1;判断条件不成立,于是位置移动1px,直到位置到了end的位置-200的时候,step=0,if判断的时候stpe<0;-200 - (-200) <= 0;条件成立,element.style[attr] = end + 'px';然后清除间歇调用。

简单点来说,假设我们移动的位置是正值,移动100px,那么end = 当前位置300 + 100 = 400,然后step不管怎么变,他永远是最终位置end 减去当前位置其中的一段距离而已(需要移动的距离中的一段),那么不断的移动下去,总能移动到end上,然后我们用舍入的方法让他最后的时候移动距离都是+1,最后移动到400的时候,需要移动的距离是0了,那么stpe也等于0,那么移动完毕,因为舍入的关系,移动的最小距离就只能是1了,再加上每次舍入都是整数,所以实际上Math.abs(parseInt(getStyle(element,attr)) - end) <= step这段不存在小于的,只有等于,但是以防万一加了小于。

如果说移动的位置是负值,移动到-500的位置,那么end得到的是300 + (-500) = -200; 最终的位置是-200,但是实际要移动的位置要移动800才对,这个800在step中就已经算了,就是end-元素当前位置,得到的是总共要移动位置,剩下的就都一样了。

attr中传入w和h来改变元素的宽度和高度

我们通过parseInt(getStyle(element,attr))可以返回当前元素的对应的值,那么宽高这些自然也可以获取到。

//设置动画
Base.prototype.animation = function(obj) {
    for(var i = 0;i<this.arr.length;i++){
        var element = this.arr[i];
        var attr = obj['attr'] == 'x' ? 'left' : obj['attr'] == 'y' ? 'top' : obj['attr'] == 'w' ? 'width' : obj['attr'] == 'h' ? 'height' : 'left';
        var step = obj['step'] != undefined ? obj['step'] : 10;
        var start = obj['start'] != undefined ? obj['start'] : parseInt(getStyle(element,attr));        
        var time = obj['time'] != undefined ? obj['time'] : 50;
        var end = start + obj['end'];
        var type = obj['type'] == 0 ? 'constant' : obj['type'] == 1 ? 'buffer' : 'buffer';
        var speed = obj['speed'] != undefined ? obj['speed'] : 6;
        if(start > end) step = -step;
        element.style[attr] = start + 'px';
        clearInterval(window.timer);
        timer = setInterval(function(){
            
            if(type == 'buffer') {
                step = (end - parseInt(getStyle(element,attr))) / speed;
                step = (step > 0) ? Math.ceil(step) : Math.floor(step);
            }
            document.getElementById('dox').innerHTML += getStyle(element, attr) + '<br/>';
            if(step > 0 && Math.abs(parseInt(getStyle(element,attr)) - end) <= step) {
                element.style[attr] = end + 'px';
                clearInterval(timer);
            }else if(step < 0 && (parseInt(getStyle(element,attr)) - end) <= Math.abs(step)) {
                element.style[attr] = end + 'px';
                clearInterval(timer);
            }else {
                element.style[attr] = parseInt(getStyle(element,attr)) + step + 'px';
            }
        },time)
    }
    return this;
}

设置既可以使用增加量,也可以使用目标量。

增加量表示需要增加多少,目标量表示到这个位置。

那么就要做个判断了。

$('#pox').click(function(){
    $('#box').animation({
        'attr' : 'h',
        'step' : 7,
        'end' : 300,
        'add' : 50,
        'type' : 1,
        'speed' : 6
    })
});

//设置动画
Base.prototype.animation = function(obj) {
    for(var i = 0;i<this.arr.length;i++){
        var element = this.arr[i];
        var attr = obj['attr'] == 'x' ? 'left' : obj['attr'] == 'y' ? 'top' : obj['attr'] == 'w' ? 'width' : obj['attr'] == 'h' ? 'height' : 'left';
        var step = obj['step'] != undefined ? obj['step'] : 10;
        var start = obj['start'] != undefined ? obj['start'] : parseInt(getStyle(element,attr));        
        var time = obj['time'] != undefined ? obj['time'] : 50;
        var end = obj['end'];
        var add = obj['add'];
        if(add != undefined && end == undefined) {
            end = add + start;
        }else if(add == undefined && end == undefined) {
            throw new Error('end目标量或者add增加量没有值!');
        }
        var type = obj['type'] == 0 ? 'constant' : obj['type'] == 1 ? 'buffer' : 'buffer';
        var speed = obj['speed'] != undefined ? obj['speed'] : 6;
        if(start > end) step = -step;
        element.style[attr] = start + 'px';
        clearInterval(window.timer);
        timer = setInterval(function(){
            
            if(type == 'buffer') {
                step = (end - parseInt(getStyle(element,attr))) / speed;
                step = (step > 0) ? Math.ceil(step) : Math.floor(step);
            }
            document.getElementById('dox').innerHTML += getStyle(element, attr) + '<br/>';
            if(step > 0 && Math.abs(parseInt(getStyle(element,attr)) - end) <= step) {
                element.style[attr] = end + 'px';
                clearInterval(timer);
            }else if(step < 0 && (parseInt(getStyle(element,attr)) - end) <= Math.abs(step)) {
                element.style[attr] = end + 'px';
                clearInterval(timer);
            }else {
                element.style[attr] = parseInt(getStyle(element,attr)) + step + 'px';
            }
        },time)
    }
    return this;
}

if判断,如果增量add存在,目标量end不存在,那么end = add + start(元素本身大小),如果都不存在,那么抛出错误,如果两个都存在,if判断的时候会直接跳过了,因为条件都不对,然后以end目标量为主。

  • weixiao kaixin tushetou jingkong deyi fanu liezui liuhan daku ganga bishi nanguo lihai qian yiwen numu tu yi haixiu se fadai minyan hehe henkaixin huaji biyiyan kuanghan maimeng shui xiaku penqi zhangzui pen aini ye niu laji ok chigua renshi kongbu shuai xiaoxiese touxiao huaixiao jingnu chihuai kaisang xiaoku koubi zhuangbi lianhong kanbujian shafa zhijing xiangjiao dabian yaowan redjing lazhu rizhi duocang chixigua hejiu xixi xiaopen goukun xiaobuchu shenme wusuowei guancha lajing chouyan xiaochi bie zhadanzui zhadanxiao