大一的时候就知道有缓动函数这种东东(这种炫酷的特技是促使我后来走上IT路的原因之一,~( ╯□╰ )~),但是当时语法不通而且觉得公式推导太深奥了,未详学。最近在整理缓动动画的实现方式,看了古松师兄写的《缓动公式小析》,于是,便决定整理一下Web前端中使用的缓动函数知识,方便以后调用。

一. 缓动函数效果种类

【备注:其中 f(x) 表示动画进度,t 表示时间。】

Linear:无缓动效果,f(t) = t;

Quadratic:二次方的缓动,f(t) = t^2;

Cubic:三次方的缓动,f(t) = t^3;

Quartic:四次方的缓动,f(t) = t^4;

Quintic:五次方的缓动,f(t) = t^5;

Sinusoidal:正弦曲线的缓动,f(t) = sin(t);

Exponential:指数曲线的缓动,f(t) = 2^t;

Circular:圆形曲线的缓动,f(t) = sqrt(1 – t^2);

Elasitc:指数衰减的正弦曲线缓动;

Back:超过范围的三次方缓动,f(t) = (s + 1) t^3 – 3 t^2;

Bounce:指数衰减的反弹缓动;

而每种缓动算法效果都可以分为三个缓动方式,分别是:

easeIn:从0开始加速的缓动;

easeOut:减速到0的缓动;

easeInOut:前半段从0开始加速,后半段减速到0的缓动;

具体的缓动函数曲线如下所示:

此处输入图片的描述

【备注:图片截取自https://msdn.microsoft.com/zh-cn/library/ee308751(v=vs.110).aspx

更多直观生动的缓动函数的种类和实现效果,可以参考《缓动函数速查表》。

二. 缓动算法

缓动算法的原理:x轴代表时间,y轴代表当前值。当t(x轴)逐渐增加到d时,当前值(y轴)会到达目标值(b + c)。

其中,下面缓动算法的疯传函数的四个参数分别为:

t: current time,当前时间;

b: beginning value,初始值;

c: change in value,变化量;

d: duration,持续时间;

【备注:Elastic和Back有其他可选的参数】

var Tween = {
    Linear: function(t, b, c, d) { return c*t/d + b; },
    Quad: {
        easeIn: function(t, b, c, d) {
            return c * (t /= d) * t + b;
        },
        easeOut: function(t, b, c, d) {
            return -c *(t /= d)*(t-2) + b;
        },
        easeInOut: function(t, b, c, d) {
            if ((t /= d / 2) < 1) return c / 2 * t * t + b;
            return -c / 2 * ((--t) * (t-2) - 1) + b;
        }
    },
    Cubic: {
        easeIn: function(t, b, c, d) {
            return c * (t /= d) * t * t + b;
        },
        easeOut: function(t, b, c, d) {
            return c * ((t = t/d - 1) * t * t + 1) + b;
        },
        easeInOut: function(t, b, c, d) {
            if ((t /= d / 2) < 1) return c / 2 * t * t*t + b;
            return c / 2*((t -= 2) * t * t + 2) + b;
        }
    },
    Quart: {
        easeIn: function(t, b, c, d) {
            return c * (t /= d) * t * t*t + b;
        },
        easeOut: function(t, b, c, d) {
            return -c * ((t = t/d - 1) * t * t*t - 1) + b;
        },
        easeInOut: function(t, b, c, d) {
            if ((t /= d / 2) < 1) return c / 2 * t * t * t * t + b;
            return -c / 2 * ((t -= 2) * t * t*t - 2) + b;
        }
    },
    Quint: {
        easeIn: function(t, b, c, d) {
            return c * (t /= d) * t * t * t * t + b;
        },
        easeOut: function(t, b, c, d) {
            return c * ((t = t/d - 1) * t * t * t * t + 1) + b;
        },
        easeInOut: function(t, b, c, d) {
            if ((t /= d / 2) < 1) return c / 2 * t * t * t * t * t + b;
            return c / 2*((t -= 2) * t * t * t * t + 2) + b;
        }
    },
    Sine: {
        easeIn: function(t, b, c, d) {
            return -c * Math.cos(t/d * (Math.PI/2)) + c + b;
        },
        easeOut: function(t, b, c, d) {
            return c * Math.sin(t/d * (Math.PI/2)) + b;
        },
        easeInOut: function(t, b, c, d) {
            return -c / 2 * (Math.cos(Math.PI * t/d) - 1) + b;
        }
    },
    Expo: {
        easeIn: function(t, b, c, d) {
            return (t==0) ? b : c * Math.pow(2, 10 * (t/d - 1)) + b;
        },
        easeOut: function(t, b, c, d) {
            return (t==d) ? b + c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
        },
        easeInOut: function(t, b, c, d) {
            if (t==0) return b;
            if (t==d) return b+c;
            if ((t /= d / 2) < 1) return c / 2 * Math.pow(2, 10 * (t - 1)) + b;
            return c / 2 * (-Math.pow(2, -10 * --t) + 2) + b;
        }
    },
    Circ: {
        easeIn: function(t, b, c, d) {
            return -c * (Math.sqrt(1 - (t /= d) * t) - 1) + b;
        },
        easeOut: function(t, b, c, d) {
            return c * Math.sqrt(1 - (t = t/d - 1) * t) + b;
        },
        easeInOut: function(t, b, c, d) {
            if ((t /= d / 2) < 1) return -c / 2 * (Math.sqrt(1 - t * t) - 1) + b;
            return c / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1) + b;
        }
    },
    Elastic: {
        easeIn: function(t, b, c, d, a, p) {
            var s;
            if (t==0) return b;
            if ((t /= d) == 1) return b + c;
            if (typeof p == "undefined") p = d * .3;
            if (!a || a < Math.abs(c)) {
                s = p / 4;
                a = c;
            } else {
                s = p / (2 * Math.PI) * Math.asin(c / a);
            }
            return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b;
        },
        easeOut: function(t, b, c, d, a, p) {
            var s;
            if (t==0) return b;
            if ((t /= d) == 1) return b + c;
            if (typeof p == "undefined") p = d * .3;
            if (!a || a < Math.abs(c)) {
                a = c; 
                s = p / 4;
            } else {
                s = p/(2*Math.PI) * Math.asin(c/a);
            }
            return (a * Math.pow(2, -10 * t) * Math.sin((t * d - s) * (2 * Math.PI) / p) + c + b);
        },
        easeInOut: function(t, b, c, d, a, p) {
            var s;
            if (t==0) return b;
            if ((t /= d / 2) == 2) return b+c;
            if (typeof p == "undefined") p = d * (.3 * 1.5);
            if (!a || a < Math.abs(c)) {
                a = c; 
                s = p / 4;
            } else {
                s = p / (2  *Math.PI) * Math.asin(c / a);
            }
            if (t < 1) return -.5 * (a * Math.pow(2, 10* (t -=1 )) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b;
            return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p ) * .5 + c + b;
        }
    },
    Back: {
        easeIn: function(t, b, c, d, s) {
            if (typeof s == "undefined") s = 1.70158;
            return c * (t /= d) * t * ((s + 1) * t - s) + b;
        },
        easeOut: function(t, b, c, d, s) {
            if (typeof s == "undefined") s = 1.70158;
            return c * ((t = t/d - 1) * t * ((s + 1) * t + s) + 1) + b;
        },
        easeInOut: function(t, b, c, d, s) {
            if (typeof s == "undefined") s = 1.70158; 
            if ((t /= d / 2) < 1) return c / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)) + b;
            return c / 2*((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2) + b;
        }
    },
    Bounce: {
        easeIn: function(t, b, c, d) {
            return c - Tween.Bounce.easeOut(d-t, 0, c, d) + b;
        },
        easeOut: function(t, b, c, d) {
            if ((t /= d) < (1 / 2.75)) {
                return c * (7.5625 * t * t) + b;
            } else if (t < (2 / 2.75)) {
                return c * (7.5625 * (t -= (1.5 / 2.75)) * t + .75) + b;
            } else if (t < (2.5 / 2.75)) {
                return c * (7.5625 * (t -= (2.25 / 2.75)) * t + .9375) + b;
            } else {
                return c * (7.5625 * (t -= (2.625 / 2.75)) * t + .984375) + b;
            }
        },
        easeInOut: function(t, b, c, d) {
            if (t < d / 2) {
                return Tween.Bounce.easeIn(t * 2, 0, c, d) * .5 + b;
            } else {
                return Tween.Bounce.easeOut(t * 2 - d, 0, c, d) * .5 + c * .5 + b;
            }
        }
    }
}

【备注:以上Tween算法转载自https://github.com/zhangxinxu/Tween/blob/master/tween.js

演示效果请点击《Tween算法》——Cauma

三. Demo

这里以四次方的缓动效果中的easeOut效果为例:

HTML代码:

<div id="box" style="width: 100px; height: 100px; background: #f85455;position: absolute;"></div>

JavaScript代码:

var Tween = {
    Quad: {        easeOut: function(t, b, c, d) {
            return -c *(t /= d)*(t-2) + b;
        }
    }
};

var box = document.getElementById("box");
// t: 当前时间; b: 初始值;c: 变化量; d: 持续时间
var b = 50, c = 100, d = 100, t = 0;
// 定时器
var timer = null;
run();
function run() {
    box.style.left = Math.ceil(Tween.Quad.easeOut(t, b, c, d)) + "px";
    t++;
    timer = setTimeout(run, 1000 / 60);
    if(t >= d) {
        clearTimeout(timer);
    }
}

效果如下:

【备注:请按F5,或者点击“Result”面板上悬浮的“RETURN”刷新后查看~】

其他缓动效果的使用方法类似,只是公式不同而已。

四. 总结

对缓动算法的计算公式依旧感到深奥。项目需要的时候,拿去用便是了。直观的缓动效果可以查看《缓动函数速查表》。当然,我们也可以自定义公式创建缓动效果。

目前,使用缓动算法的比较著名的jQuery库有jquery.easing.jsTween.js等,有兴趣的可以玩一玩。

就酱纸。

本文作者:子匠_Zijor,转载请注明出处:http://www.dengzhr.com/js/516