前端js深入理解贝塞尔曲线原理附代码

来源:程序思维浏览:55次
什么是贝塞尔曲线?

贝塞尔曲线于 1962 年,由法国工程师皮埃尔·贝济埃(Pierre Bézier)所广泛发表,他运用贝塞尔曲线来为汽车的主体进行设计。

贝塞尔曲线主要用于二维图形应用程序中的数学曲线,曲线由起始点,终止点(也称锚点)和控制点组成,通过调整控制点,通过一定方式绘制的贝塞尔曲线形状会发生变化。后面会具体介绍绘制的方法。

在计算机图形学中贝赛尔曲线的运用很广泛,例如Photoshop中的钢笔效果,Flash5的贝塞尔曲线工具,在软件GUI开发中一般也会提供对应的方法来实现贝赛尔曲线,我们熟知的CSS动画过渡时间函数也是通过贝塞尔曲线(三阶贝塞尔曲线)获取的。


贝塞尔曲线分为哪些类型?

贝塞尔曲线根据控制点的数量分为:

一阶贝塞尔曲线(2 个控制点)
二阶贝塞尔曲线(3 个控制点)
三阶贝塞尔曲线(4 个控制点)
n阶贝塞尔曲线(n+1个控制点)

贝塞尔曲线是如何绘制出来的?

下图为一个三阶的贝塞尔曲线,包括四个控制点,分别为P0,P1,P2,P3。



通过上图的三阶贝塞尔曲线举例,基本的步骤如下:

四个控制点通过先后顺序进行连接,形成了三条线段,也就是上图中的,然后通过一个参数T,该参数的值等于线段上某一个点距离起点的长度除以线段长度。就比如段上有一个点,此时的值就是,其中位置如下图所示。


2.接下来对每一条线段做同样的操作,得到三个控制点,如下图所示。


3.然后对这三个控制点重复第1步操作,得出两个控制点,如下图所示。


4. 最后再使用同样的方法可以得到,最终的一个点,如下图所示,此时这个点就是贝塞尔曲线上的一个点。


通过控制的值,由 0 增加至 1,就绘制出了一条由起点至终点的贝塞尔曲线。

你可以通过下面这个动画直观感受一下绘制的过程:




如何求贝塞尔曲线上的点坐标?


1、一阶贝塞尔曲线



对于一阶贝塞尔曲线,我们可以通过几何知识,很容易根据的值得出线段上那个点的坐标:



然后可以得出:



2、二阶贝塞尔曲线



对于二阶贝塞尔曲线,其实你可以理解为:在上利用一阶公式求出点,然后在上利用一阶公式求出点,最后在上再利用一阶公式就可以求出最终贝塞尔曲线上的点。具体推导过程如下:

先求出线段上的控制点。



将上面的公式带入至下列公式中:




得出以下公式:

3、三阶贝塞尔曲线



与二阶贝塞尔曲线类似,可以通过相同的方法得出以下坐标公式:



4、多阶贝塞尔曲线

这里我就直接把阶贝塞尔曲线公式给出来了,有兴趣的同学可以自行研究一下。


5、如何实现一个类似CSS中easing属性的三阶贝塞尔曲线构造函数?

如果要实现一个这样的三阶贝塞尔曲线,我们需要不仅需要获取到一些曲线上的点,还需要通过x轴获取y轴坐标。

CSS中的easing贝塞尔曲线有一个特点,那就是起点和终点是固定的,也就是分别是[0,0],[1,1]。所以未知的点就只有两个,也就是需要传入四个值,并且这四个值的范围需要在[0,1]内。
通过上述代码初始化以后,我们还需要根据t(取值范围为[0,1])值获取坐标,以及一个曲线上坐标集合的数组。另外还需要使用三阶贝塞尔公式:



因为点坐标为[0, 0],点坐标为为所以公式进而可以写成:



所以我们需要创建一个类CubicBezier,它拥有属性controlPoints:
class CubicBezier {
  constructor(x1, y1, x2, y2) {
    this.controlPoints = [x1, y1, x2, y2];
  }
}


class CubicBezier {
  constructor(x1, y1, x2, y2) {
    this.controlPoints = [x1, y1, x2, y2];
  }

  getCoord(t) {
    // 如果t取值不在0到1之间,则终止操作
    if (t > 1 || t < 0) return;
    const _t = 1 - t;
    const [ x1, y1, x2, y2 ] = this.controlPoints;
    const coefficient1 = 3 * t * Math.pow(_t, 2);
    const coefficient2 = 3 * _t * Math.pow(t, 2);
    const coefficient3 = Math.pow(t, 3);
    const px = coefficient1 * x1 + coefficient2 * x2 + coefficient3;
    const py = coefficient1 * y1 + coefficient2 * y2 + coefficient3;
    // 结果只保留三位有效数字
    return [parseFloat(px.toFixed(3)), parseFloat(py.toFixed(3))];
  }
}
利用上述的Bezier类,我们就可以根据两个控制点构建Bezier实例,通过这个实例我们可以根据t值,获取点上的近似值。
那么如果我们想要根据x轴坐标值,来获取y轴坐标时,我们该怎么做呢?
这里我使用了一个近似处理的办法,具体如下:
1.先获取离需要求值点最近的两个点。
2.然后通过这两个点可以得到一个直线方程。
3.最后通过将x轴坐标传入直线方程中,就可以近似求得y轴坐标值了。
所以我们需要进一步改造Bezier构造函数,需要缓存固定数量坐标数组的属性coords,以及获取coords的方法getCoordsArray,最后还有获取y轴坐标的方法getY,具体的实现方法如下:
class CubicBezier {
  constructor(x1, y1, x2, y2) {
    const precision = 100;
    this.controlPoints = [x1, y1, x2, y2];
    this.coords = this.getCoordsArray(precision);
  }
 
  getCoord(t) {
    // 如果t取值不在0到1之间,则终止操作
    if (t > 1 || t < 0) return;
    const _t = 1 - t;
    const [ x1, y1, x2, y2 ] = this.controlPoints;
    const coefficient1 = 3 * t * Math.pow(_t, 2);
    const coefficient2 = 3 * _t * Math.pow(t, 2);
    const coefficient3 = Math.pow(t, 3);
    const px = coefficient1 * x1 + coefficient2 * x2 + coefficient3;
    const py = coefficient1 * y1 + coefficient2 * y2 + coefficient3;
    // 结果只保留三位有效数字
    return [parseFloat(px.toFixed(3)), parseFloat(py.toFixed(3))];
  }
 
  getCoordsArray(precision) {
    const step = 1 / (precision + 1);
    const result = [];
    for (let t = 0; t <= precision + 1; t++) {
      result.push(this.getCoord(t * step));
    }
    this.coords = result;
    return result;
  }
 
  getY(x) {
    if (x >= 1) return 1;
    if (x <= 0) return 0;
    let startX = 0;
    for (let i = 0; i < this.coords.length; i++) {
      if (this.coords[i][0] >= x) {
        startX = i;
        break;
      }
    }
    const axis1 = this.coords[startX];
    const axis2 = this.coords[startX - 1];
    const k = (axis2[1] - axis1[1]) / (axis2[0] - axis1[0]);
    const b = axis1[1] - k * axis1[0];
    // 结果也只保留三位有效数字
    return parseFloat((k * x + b).toFixed(3));
  }
}
然后通过下述方式就可以使用我们的CubicBezier了:
const cubicBezier = new CubicBezier(0.3, 0.1, 0.3, 1);
cubicBezier.getY(0.1); // 0.072
cubicBezier.getY(0.7); // 0.931复制代码
我写了一个应用这个CubicBezier构造函数的库Animate-Scroll,有兴趣的可以去看一下源码。
精品好课
React实战视频教程仿京东移动端电商
React是前端最火的框架之一,就业薪资很高,本课程教您如何快速学会React并应用到实战,对正在工作当中或打算学习React高薪就业的你来说,那么这门课程便是你手中的葵花宝典。
jQuery视频教程从入门到精通
jquery视频教程从入门到精通,课程主要包含:jquery选择器、jquery事件、jquery文档操作、动画、Ajax、jquery插件的制作、jquery下拉无限加载插件的制作等等......
HTML5视频播放器video开发教程
适用人群1、有html基础2、有css基础3、有javascript基础课程概述手把手教你如何开发属于自己的HTML5视频播放器,利用mp4转成m3u8格式的视频,并在移动端和PC端进行播放支持m3u8直播格式,兼容...
最新React视频教程从入门到实战附源码下载
React是前端最火的框架之一,就业薪资很高,本课程教您如何快速学会React并应用到实战,对正在工作当中或打算学习React高薪就业的你来说,那么这门课程便是你手中的葵花宝典。
HTML5基础入门视频教程易学必会
HTML5基础入门视频教程,教学思路清晰,简单易学必会。适合人群:创业者,只要会打字,对互联网编程感兴趣都可以学。课程概述:该课程主要讲解HTML(学习HTML5的必备基础语言)、CSS3、Javascript(学习...
收藏