【bezier】贝塞尔计算公式及补间动画

贝塞尔计算公式:

一阶:

B(t) = P_{0} + (P_{1} - P_{0})t = P_{0}(1-t)+P_{1}t,t\in[0,1]

  function bezier1(t: number, x0: number, x1: number) {
    return x0 + (x1 - x0) * t
  }

二阶:

B(t)=P_{0}(1-t)^{2} + 2P_{1}(1-t)t + P_{2}t^{2},t\in [0,1]

 

  function bezier2(t: number, x0: number, x1: number, x2: number) {
    const rt = 1 - t
    const p = Math.pow
    return x0 * p(rt, 2) + 2 * x1 * rt * t + x2 * p(t, 2)
  }

三阶:

B(t)= P_{0}(1-t)^{3} + 3P_{1}(1-t)^{2}t + 3P_{2}(1-t)t^{2} + P_{3}t^{3},t\in [0,1]

 

  function bezier3(t: number, x0: number, x1: number, x2: number, x3: number) {
    const rt = 1 - t
    const p = Math.pow
    return x0 * p(rt, 3) + 3 * x1 * p(rt, 2) * t + 3 * x2 * rt * p(t, 2) + x3 * p(t, 3)
  }

n阶:

B(t) = \sum_{i=0}^{n} \binom{n}{i} P_{i}(1-t)^{n-i}t^{i} = \binom{n}{0}P_{0}(1-t)^{n}t^{0} + \binom{n}{1}P_{1}(1-t)^{n-1}t^{1} + ... + \binom{n}{n-1}P_{n-1}(1-t)^{1}t^{n-1} + \binom{n}{n}P_{n}(1-t)^{0}t^{n} , t\in [0,1]

  function bezierN(t: number, xArr: number[]) {
    const rt = 1 - t
    const p = Math.pow
    const n = xArr.length - 1
    return xArr.reduce((sum, x, i) => sum + c(n, i) * x * p(rt, n - i) * p(t, i), 0)

    // 组合C(5 2)
    function c(n: number, i: number) {
      if (n / 2 < i) i = n - i
      let cs = 1, bcs = 1
      while (i > 0) {
        cs *= n--
        bcs *= i--
      }
      return cs / bcs
    }
  }

贝塞尔补间动画:

三阶贝塞尔动画曲线:

动画曲线为ease: cubic-bezier(0.25,0.1,0.25,1)

P0与P3为固定值,P1与P2为曲线控制点

 X轴TIME为动画时间,Y轴PROGRESSION为动画进度

 type controlPoint = [number, number, number, number]

 /**
   * 贝塞尔补间动画
   * @param onFrame 动画帧回调方法,process为当前动画的进程百分比
   * @param duration 动画所需时间,毫秒
   * @param controlPoint 贝塞尔的两个控制点
   * @return animate 返回对象,cancel方法取消动画,finished结合await等待动画结束
   */
  function tween(onFrame: (process: number) => {}, duration?: number, controlPoint?: controlPoint) {
    const [x1, y1, x2, y2] = controlPoint ?? Bezier.ease
    const startT = Date.now()
    let animateId

    function cancel() {
      if (animateId) {
        cancelAnimationFrame(animateId)
        animateId = undefined
      }
    }

    let finish
    const finished = new Promise((resolve) => {
      finish = () => {
        animateId = undefined
        resolve()
      }
    })

    function loop() {
      const diff = Date.now() - startT
      // 时间作为x
      const x = diff / (duration ?? 300)
      // 以x求得其对应贝塞尔公式的t
      const t = solveCureX(Math.min(x, 1))
      // t代入贝塞尔公式求得y方向上的动画进程
      // process进程百分比
      const process = sampleCurveY(t)
      onFrame(process)

      if (x < 1) animateId = requestAnimationFrame(loop)
      else finish()
    }

    const ax = 3 * x1 - 3 * x2 + 1
    const bx = 3 * x2 - 6 * x1
    const cx = 3 * x1
    /**
     * 三阶贝塞尔简化公式,已知t求x方向的值
     * p0固定为0,0
     * p1为x1,y1
     * p2为x2,y2
     * p3固定为1,1
     */
    function sampleCurveX(t: number) {
      return ((ax * t + bx) * t + cx) * t
    }
    function sampleCurveDerivativeX(t: number) {
      // 霍纳规则
      return (3*ax*t + 2*bx)*t + cx
    }
    /**
     * 三阶贝塞尔简化公式,已知t求y方向的值
     */
    const ay = 3 * y1 - 3 * y2 + 1
    const by = 3 * y2 - 6 * y1
    const cy = 3 * y1
    function sampleCurveY(t: number) {
      return ((ay * t + by) * t + cy) * t
    }

    const ZERO_LIMIT = 1e-6
    /**
     * 已知x求t
     */
    function solveCureX(x: number) {
      let t2 = x
      let derivative
      let x2
      // 首先尝试使用牛顿迭代法对 x 求 t 的近似解
      for (let i = 0; i < 8; i++) {
        x2 = sampleCurveX(t2) - x
        if (Math.abs(x2) < ZERO_LIMIT) {
          return t2
        }
        derivative = sampleCurveDerivativeX(t2)
        if (Math.abs(derivative) < ZERO_LIMIT) {
          break
        }
        t2 -= x2 / derivative
      }
      // 如果精度不满足,再用二分法逼近求得 t
      let t0 = 0
      let t1 = 1
      t2 = x
      while (t0 < t1) {
        x2 = sampleCurveX(t2)
        if (Math.abs(x2 - x) < ZERO_LIMIT) {
          return t2
        }
        if (x > x2) {
          t0 = t2
        } else {
          t1 = t2
        }
        t2 = (t1 - t0) / 2 + t0
      }

      return t2
    }

    loop()

    return {
      cancel,
      finished
    }
  }

参考

试试看 - JS 动画与贝塞尔缓动函数的实现 - 知乎

用 JavaScript 实现三次贝塞尔动画库 - 前端组件化_三次贝塞尔函数_三钻的博客-CSDN博客

js+贝塞尔曲线+animate动画_js 坐标轴 贝塞尔_后端常规开发人员的博客-CSDN博客

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值