缓动公式

一、过渡函数

  • ease 规定慢速开始,然后变快,然后慢速结束的过渡效果
  • ease-in 规定以慢速开始的过渡效果
  • ease-out 规定以慢速结束的过渡效果
  • ease-in-out 规定以慢速开始和结束的过渡效果


二、参数解析

首先说说四个参数的含义:

  • t:timestamp,动画执行到当前帧所进过的时间
  • b:begining,起始值
  • c:change,需要变化的量
  • d:duration,动画的总时间


三、数学原理

首先要清楚一点,动画中每一帧所经过的时间是相同的,只是由于上一帧与下一帧的位移量不同,因此速度在视觉上感受不同,位移量小,感觉上速度就慢了。

下面简要分析一下原理,一步一步来:

  1. 动画执行时间的变化可表达为0 -> d,提取出常数d,就变成d*(0 -> 1),变化部分为(0->1),记为x轴变化
  1. 动画总的变化量开始值是已知的,其变化可以表达为b -> b+c,提取一下变为b+c*(0 -> 1),变化部分也是(0->1),记为y轴变化
  1. t用来指示事件当前的时间点,将其变为指示动画完成的百分比,即t/d
  1. 通过上面的变换,我们需要做的事情就是构造x轴区间为[0,1],y轴区间也为[0,1]的线性或者非线性关系了。线性关系多数是y=x,也就是常用的linear了,非线性复杂一点。
  1. 然后我们看看可以构造出哪些非线性关系,并给出函数关系表达式:

    1. 利用指数函数(x的n次方)可以构造一大堆easein的效果,再根据他们的轴对称或者中心对称做翻转和位移,又可以构造出其对应的easeout效果:

    2. 利用平方根(Math.sqrt)或者立方根来实现这种非线性关系:

    3. sin或者cos函数可以通过调节参数构造两种运动趋势(下面主要给函数表达式):

      • easein: y = 1-cos(0.5πx)
      • easeout: y = sin(0.5πx)
    4. 通过幂函数或者对数函数:

      • easein: y = 2^(10x-10) (当x=0时,y=0)
      • easeout: y = 1-2^(-10x) (当x=1时,y=1)
    5. 效果还可以叠加呀,叠加的结果除以2,就能创造弹簧效果了。

  1. 下面来看看缓动公式运用了哪些吧

    • Sine表示由三角函数实现
    • Quad是二次方,Cubic是三次方,Quart是四次方,Quint是五次方
    • Circ是开平方根(Math.sqit),Expo是幂函数(Math.pow)
    • Elastic是结合三角函数和开立方根
    • Back则引入了常数1.70158


四、代码实现

原理也就差不多分析完了,那么来看看具体实现吧,下面一2次方Quad为例子来实现,其它的都差不多。

首先是实现easein,函数表达是为: y = x*x,因此实现为:

1
2
3
4
5
function easeInQuad(t,b,c,d){
var x = t/d; //x值
var y = x*x; //y值
return b+c*y; //套入最初的公式
}

然后来看看easeout,函数表达式为: y = -x*x+2*x,因此实现为:

1
2
3
4
5
function easeOutQuad(t,b,c,d){
var x = t/d; //x值
var y = -x*x + 2*x; //y值
return b+c*y; //套入最初的公式
}

下面再看看easeinout的实现,它的实现就是一半的时间用easein走完一半的路程,另一半时间用easeout走完另一半路程,那么我们计算就套用上面两个公式就会非常直观了。

1
2
3
4
5
6
7
8
9
function easeInOutQuad(t,b,c,d){
if(t<d/2){ //前半段时间
return easeInQuad(t,b,c/2,d/2);//改变量和时间都除以2
}else{
var t1 = t-d/2; //注意时间要减去前半段时间
var b1 = b + c/2;//初始量要加上前半段已经完成的
return easeOutQuad(t1,b1,c/2,d/2);//改变量和时间都除以2
}
}

jquery.easing.js里面一样是经过代码优化的,不会那么好理解。这里这样写只是为了便于理解,其它的原理都差不多。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 滚动到顶部的缓冲效果
function backToTop() {
const start = window.pageYOffset;
let backPosition = 0;
let duration = 500;
let fps = 16.7;
let i = 0;
let interval = setInterval(() => {
const next = Math.floor(easeInOutQuad(fps * i, start, -start, duration));
if (next <= backPosition || fps * i > duration) {
window.scrollTo(0, backPosition);
clearInterval(interval);
} else {
window.scrollTo(0, next);
}
i++;
}, fps);
}