Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

你真的了解cubic-bezier吗? #4

Open
shirleyMHao opened this issue Jul 4, 2018 · 0 comments
Open

你真的了解cubic-bezier吗? #4

shirleyMHao opened this issue Jul 4, 2018 · 0 comments

Comments

@shirleyMHao
Copy link
Owner

shirleyMHao commented Jul 4, 2018

关于bezier curve在很多地方都有应用,无论是css的transition-timing-function、animation-timing-function 还是canvas 和js 的webAnimation。
很长时间只会用 Lea _Verou_制作的cubic-bezier网站,一通改变两个控制点来做为timing-function的 cubic_bezier的参数

原理

贝塞尔曲线的基础是伯恩斯坦多项式。早在1912年就已被提出,但直到1962年才得名于就职于雷诺的法国工程师 Pierre Bézier
Pierre Bézier 利用其只需要很少的几个控制点,就能够生成一条复杂平滑曲线的方法,应用于汽车车体的工业设计。
因为控制简便却具有极强的描述能力,贝塞尔曲线也在计算机图形学领域,尤其是矢量图形学占有重要的地位。

画曲线

以二次贝塞尔曲线为基础,我们举一个🌰,来说明怎么利用很少的几个点来画出一条平滑的曲线。
初中我们就学过线是有无数个点构成的,那么我们先画出线上的一个点。如下图 👇
pic2

  1. 在平面上任选3点,P0、P1、P2。我们称P0和P2分别为StartPoint 和 EndPoint, P1为ControlPoint
  2. 将ControlPoint分别于StartPoint 和 EndPoint连接起来
  3. 在线段P0P1,任意选取一个点D
  4. 在线段P1P2 上选取点E,使得 DP0 : P0P1 = EP1 : P1P2
  5. �连接DE, 并在线段DE上选取F点,使得DP0 : P0P1 = EP1 : P1P2 = DF : EF

最终选出的F点就是曲线上的一点
我们在P0P1上选取尽量多的点D0、D1...Dn,从而可以得到F0、F1...Fn,将这些F点连接成线,就可以得到一条近似的平滑曲线。

t = DP0 : P0P1 👇
bezier-quadratic-animation

大家可以自己尝试下在动态生成贝塞尔曲线

公式

先拉个公式出来镇楼~~
贝塞尔曲线通用公式
其中 👇

  • P0 ~ Pn是我们上面说的很少的几个控制点
  • 随着t从0到1的变化,根据曲线公式,可以将点的坐标(x,y)求解出来,进而得到一系列点的数组,点与点之间用直接连接,生成一条平滑的曲线。
  • default 是二项式公式系数,相当于C(n,k)
    C(n,k)有没有很熟悉,想当初n个不同的小球,从中取出k个,有多少种取法?还记得吗?哈哈~~ 对了,就是大家熟悉的排列组合问题(忘记的,赶紧给数学老师打电话,求补课~~ 😏)

二项式解读

好吧,我承认我第一次看到的时候也是满脑子懊悔,想当初的高考人生巅峰,看今朝,忘得渣也不剩。接下来就把我恶补的部分拿出来摆一摆

  • 最直观的方式,我们可以利用帕斯卡三角形来计算,:point_down:

default

是不是很好理解, 通过查表,可以很方便的计算C(3,1) = 3, C(3,2) =3 * 当然如果将公式转化为变成语言,我们需要更通用的公式,:point_down:

default

特别指定

default

这个公式大家就都会求解啦。

应用

bezier曲线在前端有两方面的应用,一个是用于缓动动画,另一个就是我们上面说的用于画曲线。缓动动画比较常见的用法就是cubic_bezier在css中的应用;画曲线,�大家是不是跟我一样最先想到的就是canvas啊。接下来就从CSS和JS两方面来看看如何应用。

CSS

css不具备编程的能力,我们一般用到bezier的地方大概就是transition-timing-functionanimation-timing-function了。
比如在animate.css 经常看到各种�酷炫的动画,有没有想过这些大神是怎么写出这些神奇的cubic_bezier参数的?
比如我们下面看到的bounce的keframes 👇

@keyframes bounce {
  from, 20%, 53%, 80%, to {
    animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
    transform: translate3d(0,0,0);
  }
  40%, 43% {
  animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
    transform: translate3d(0, -30px, 0);
  }
  70% {
    animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
    transform: translate3d(0, -15px, 0);
  }
  90% {
    transform: translate3d(0,-4px,0);
  }
}

两个cubic_bezier的参数,cubic-bezier(0.215, 0.610, 0.355, 1.000)cubic-bezier(0.755, 0.050, 0.855, 0.060)。之前一直就是拿来主义,但不知道这些参数这样设置有何玄机。�接下来就是见证奇迹的时刻,大家可以根据缓动函数速查表查到这里用到两种参数。
其中_cubic-bezier(0.215, 0.610, 0.355, 1.000)_ 是easeOutCubic, cubic-bezier(0.755, 0.050, 0.855, 0.060easeInQuint
也就是说其实我们在animate.css中看到的� _cubic-bezier_参数都不是胡乱邹出来的,都是有据可查的。
了解了这一点,之后大家在写参数的时候,就不至于�太迷茫啦。:smile:

JS

JS中bezier曲线的应用场景,个人用的比较多的是canvas绘制曲线。
按照我们高中试卷的套路,就是一直P0、P1...Pn ,和bezier曲线的公式,求解t∈[0,1]区间所有点的坐标集合。当然我们说所有点,有点儿妄想了,那就先氛围100份,取100个点的坐标看看。(这里100个点取的比较随意,当然点越多,我们的曲线就会越平滑)。
我们再次邀请公式出场
贝塞尔曲线通用公式

整个公式,我们分解一下,三步走:

  1. 求解阶乘函数
  2. 求解二项式洗漱
  3. �已知t,求解坐标(x,y)

那我们分步骤来看,�每一步就会变得很简单:

  1. 阶乘 :
//阶乘函数
function factor(n){
    if(n<=1){
        return 1;
    } else {
        return n * factor(n-1);
    }
}
  1. 二项式系数:
//二项式系数,看不懂的同学翻到上面的”二项式系数解读“,再复习一下哦~
function Binomial(n,index){
    return factor(n)/(factor(index) * factor(n-index));
}
  1. 已知t, 求解bezier曲线上点的坐标(x,y)
/**
   * 
   * @param pointArray: 几个控制点的数组,格式为[{x:100,y:100},{x:150,y:200},{x:300,y:100}]
   */
function getPoint(t,pointArray){
    let obj ={
            x: 0,
            y: 0
        }
    //贝塞尔曲线公式
    for(let i =0,len=pointArray.length-1; i<= len; i++){
        obj.x += pointArray[i].x * Binomial(len,i) * Math.pow((1-t),(len-i)) * Math.pow(t, i);
        obj.y += pointArray[i].y * Binomial(len,i) * Math.pow((1-t),(len-i)) * Math.pow(t, i);
    }
    return obj;
}

接下来的事情就变得很简单了,我们需要求解t�∈[0,1],多个点的坐标就大功告成啦。有没有很兴奋~~ 😜

//devide就是我们刚才说的,随意的分成100份,它的值越大, 曲线越平滑
function getAllNode(pointArray){
    let devide = 100;
    //arr用于存储曲线上的100个点的坐标
    let arr = [];
    for(let t=0;t<=1;t+=1/devide){
        let p = getPoint(t,pointArray)
        arr.push(p)
    }
}

最最最后,我们只需要将arr中的点,每两个点之间用直线连接,绘制出整个曲线

//绘制曲线
function drawBezier(ctx,pointArray){
    var arr = getAllNode(pointArray);
    for(let i= 0, len = arr.length; i< len -1; i++){
        if(i ==0 ){
            ctx.moveTo(arr[i].x, arr[i].y);
        } else{
            ctx.lineTo(arr[i].x, arr[i].y);
        }
        
    }
    ctx.stroke();
}

更加详细的代码,�请参见demo

总结: 到这里bezier曲线的基本知识,大家有没有了解的更多一点点呢?
希望本篇文章有帮助到大家,如果文中有任何错误,�也请大家指出~~

参考

贝塞尔扫盲
二项式系数
贝塞尔公式推导与物体跟随复杂曲线的轨迹运动

@shirleyMHao shirleyMHao changed the title 关于cubic-bezier我所了解的一切 你真的了解cubic-bezier吗? Jul 5, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant