Chapter2 - Canvas动画(II)用国中数学拆解Ease-out和Ease-in

如何计算每一侦的位移

首先我们改写一下昨天的格式,还记得昨天我们用到的是这样的写法:

cursorX+= distanceX / period;
cursorY+= distanceY / period;

当时的想法也很简单,因为总共有90侦,因此将座标加上distance / period共90次,就会是完整的位移,那麽,换句话说,其实就是,每1侦加上1/90的距离,可以写成这样:

let linear = 1 / period;
cursorX+= linear * distanceX;
cursorY+= linear * distanceY;
// 位移 = 线性常数 * 距离

其实,这个线性常数linear等同时间函数的微分,将它积分後会刚好等於1阿 突然讲了很难懂的话

在上面的公式中,输入值为距离和总侦数(distance, period),输出值则是每一侦要进行的位移,在这个例子中,因为共有90侦,linear * 90就好刚好会等於1,也就是说,实际上我们想控制这个参数,让它经过90侦後刚好等於1,因而从原点转移到目的地,所以我们要做的是便是「操纵0到1的过程」。

操纵0到1的过程

昨天留的思考题,不知道有没有人解出来了呢?有的扣个1留言一下XD,现在就让我们解答吧,首先我们先从简单的开始,以前国中学到一元二次方程序,最先学的那个不外乎就是Y=X^2吧?也就是最简单的,这个方程序有一个特性,就是当X在0~1之间时,Y也是0~1之间,刚才说我们希望「输出值的总和」等於1,因此只要控制X在0~1之间就可以了!

  • X = (1 - timer/period)
  • Y = X^2
  • 总位移 = Y * distance

开始困惑的朋友们莫慌!让我们直接代数字进去看看,在第一侦的时候timer为90,随着timer的减少

period timer X Y 总位移量
90 90 0 0 0
90 45 0.5 0.25 总距离的1/4倍
90 30 0.666 0.444 总距离的4/9倍
90 0 1 1 总距离的1倍

这样是不是就很好懂了呢?从一开始几乎不动,到後面越来越快,最後到达目标位置。

Ease-in & Ease-out

如果没看过这两个名词的话,可以参考这个里面的文件附图说明

其实,我们刚刚做的就是ease-in函数,不过跟典型的不太一样,只是很相似的地步,但也是最好理解的,不过,现在的问题是,我们已经拿到这个方程序,也会算总位移了,那要怎麽取得「每一侦的位移」呢?答案很简单,就是「拿下一侦的位移减这一侦的位移」:

  • X(timer) = (1 - timer/period)
  • Y(X) = X^2
  • 每一侦的位移 = (Y[X(timer-1)] - Y[X(timer)]) * distance

写成程序码就是:

let easein = Math.pow(1 - (timer-1) / period, 2) - Math.pow(1 - timer / period, 2);
let cursorX+= easein * distanceX;
let cursorY+= easein * distanceY;

如何?换上这行代码後,动画效果是不是多了几番风味呢?

那反过来说ease-out要怎麽做呢?其实有个偷吃步的做法,就是把ease-in颠倒过来,慢的变快的,快的变慢的,拿1去减掉这个范围0~1的参数,就会从「0到1」反过来「1到0」,公式:

  • X = 1 - (1 - timer/period) = timer/period

也就是:

let easeout = Math.pow(timer / period, 2) - Math.pow((timer-1) / period, 2);
let cursorX+= easeout * distanceX;
let cursorY+= easeout * distanceY;

那麽,我们就完成很基本的ease-in和ease-out拉!洒花~

混和方程

接着我们可以进一步混和不同方程序,写成像这样的格式:

cursorX+= (0.5 * linear + 0.5 * easein) * distanceX;
cursorY+= (0.5 * linear + 0.5 * easein) * distanceY;

这样可以使得easein的效果比较缓和,看起来不会那麽做作,不过要注意参数总和必须为1

function MouseAnime(){
    if(!isPathing){
        if(timer > 0){
            let linear = 1/period;
            let easeout = Math.pow(timer / period, 2)
                          - Math.pow((timer-1) / period, 2);
            let easein = Math.pow(1 - (timer-1) / period, 2)
                         - Math.pow(1 - timer / period, 2);
            let a = input.linear;
            let b = input.easein;
            let c = input.easeout;
            cursorX+= (a * linear + b * easein + c * easeout) * distanceX;
            cursorY+= (a * linear + b * easein + c * easeout) * distanceY;
            timer--;
        }
        else{
            cursorX+= (mouseX - cursorX) / 5;
            cursorY+= (mouseY - cursorY) / 5;
        }
    }
    let size = WIDTH * 0.1;
    if(MouseImg.complete) context.drawImage(MouseImg,
                                            cursorX-size/2, cursorY-size/2,
                                            size, size);
}

透过abc参数,给予不同的权重,设计上希望a+b+c=1,不过为了方便直接设计权重比例,最後共同除以(a+b+c)就可以了
这样混和的方法,也可以创造出新的方程序,比方说回弹机制等等的,

甚至XY可以分别采用不同的动画模式,例如:X是纯Linear、Y是纯Ease-in,玩起来还是有Ease-in的味道在,只是相比之下没有那麽浮夸;混合的方法则可以设计出回旋镖等等的骚操作。

我就把这份快乐留给大家试试看吧!可以拿这份架构去修改参数,或者加入更多其他的方程序

Animation Demo

後记

今天花了四个小时坐客运,时间被压缩了不少,文章也都是亲笔撰写,也花了点时间设计架构,故内容写少一点请见谅拉,但还是推荐大家可以去玩一下demo,会越来越精彩~明天要开始引入物件的观念!


<<:  【D15】当大盘涨的时候,跟台积电有关系吗?

>>:  成员 3 人:别让人落单,就成功一半

MLOps在金融产业:常见案例与工作流程

在金融产业的ML 在algorithmia的2021 年企业机器学习趋势调查显示,关於客户体验跟流程...

【Day26】Git 版本控制 - merge 发生冲突

在上一篇文章中提到碰撞(collision),让我突然想到!还有这个主题可以拿来撰写,就是:当你在 ...

Day.12 「来为网页添加动画吧!」 —— CSS 动画(animation)

现在我们会使用具有互动性的简单渐变效果了,接着要来试着让网页能增添更多活力,不需要我们操作,就会自...

tensorflow.python.framework.errors_impl.InternalError: Cannot dlopen all CUDA libraries.

输入:pip list 发现少了tensorflow-gpu 输入: pip install ten...

[Day-30] 最後一天的小练习

首先要庆祝一下~ 终於撑到30天了 今天要来练习的是利用switch 来做一个选择的模式 模式有三种...