Introduction to the

Bessel curves are applied in many places in front-end work. For example, in the recent annual drawing, bezier curves were used for the acceleration at the start of the roll and the slow stop at the winning number at the end of the roll. So I’m going to introduce you to bezier curves and simple applications. Its general parameter formula is:

The curve starts at P0 and ends at Pn, and there are n minus 2 points in between that control the curve. According to the different number of control points, different special curve formulas are obtained.

In practice, quadratic Bessel curve and cubic Bessel curve are commonly used. A Bezier curve, it’s a straight line. The following are the introductions and applications of several common Bessel curves.

Common curve

The linear equation

n=1, the number of control points:0, only two points, the beginning and the end, you get a straight line.

Quadratic formula

n=2, the number of control points:1

Cubic formula

n=3, the number of control points:2

formula

For example, the quadratic formula is converted to the following function, where P0 is the starting point, P2 is the ending point, and P1 is the control point. We get a quadratic function of t from P0 to P2, the range of t [0, 1].

function QuadraticBezier(P0, P1, P2){
  return (t) = > (1 - t) * (1 - t) * P0 + 2 * t * (1 - t) * P1 + t * t * P2;
}
Copy the code

application

The application of SVG

In SVG, quadratic Bezier curves can be drawn using q or q (q refers to relative position, q refers to absolute position). For SVG, the critical code , the starting point P0(100 350), the ending point P2(400 350), and the control point P1(250 50). The specific coordinates of a moment can be obtained by substituting the X and Y coordinates into the QuadraticBezier function.

// Compute X and Y respectively
const getX = new QuadraticBezier(100.250.400);
const getY = new QuadraticBezier(350.50.350);

const point = document.getElementById("point");  // Points that move dynamically with t
let t = 0;  // The initial value is 0 and increments to 1
function setXY () {
  point.style.top = getY(t) + "px";
  point.style.left = getX(t) + "px";
  if (t >= 1) {
    cancelAnimationFrame(frame);
  } else {
    t = t + 0.005; frame = requestAnimationFrame(setXY); }}let frame = requestAnimationFrame(setXY);
Copy the code

With the increase of t, the top and left of point are calculated respectively, and the following animation can be obtained:

The apiquadraticCurveTo, bezierCurveTo in canvas are similar to drawing SVG.

CSS animation-timing-function

Animation-timing-function specifies the speed curve of the CSS animation. Several values are preset: Linear, ease, ease-in, ease-out, ease-in-out, cubic- Bezier () is used for flexible control of customized speed. You can modify the numbers in this example to see what happens.

Used in JS

The normal range of the value received by cubic bezier is [0, 1], that is, in the cubic Bezier curve, the default starting point P0(0, 0) and the end point P3(1, 1), the formula can be derived

const CubicBezier = P0 * (1-t)^3 + 3 * P1 * t * (1-t)^2 + 3 * P2 * t^2 * (1-t) + P3 * t^3
/ / make P0 (0, 0), P3 (1, 1), P1 (x1, y1), P2 (x2, y2), plug in
x = 3 * x1 * t * (1-t)^2 + 3 * x2 * t^2 * (1-t) + t^3;
  = (3 * x1 - 3 * x2 + 1) * t^3 + (3 * x2 - 6 * x1) * t^2 + 3 * x1 * t;
y = (3 * y1 - 3 * y2 + 1) * t^3 + (3 * y2 - 6 * x1) * t^2 + 3* y1 * t; namelyfunction CubicBezier(x1, y1, x2, y2) {
  this.x1 = x1;
  this.y1 = y1;
  this.x2 = x2;
  this.y2 = y2;
}
CubicBezier.prototype.sampleCurveX = function(t){
  const x1 = this.x1, x2 = this.x2;
  return (3 * x1 - 3 * x2 + 1) * t^3 + (3 * x2 - 6 * x1) * t^2 + 3 * x1 * t;
}
CubicBezier.prototype.sampleCurveY = function(t){
  const y1 = this.y1, y2 = this.y2;
  return (3 * y1 - 3 * y2 + 1) * t^3 + (3 * y2 - 6 * x1) * t^2 + 3 * y1 * t;
}

Copy the code

The value of time t can be calculated by combining the functions of X and Y:

CubicBezier.prototype.solve = function(t){
  return this.sampleCurveY(this.sampleCurveX(t))
}
Copy the code

Simple combination at this time, the error is large. As can be seen from the example of drawing SVG, the functions sampleCurveX and sampleCurveY calculate the coordinates x and y at time T and can draw the cubic Bessel curve. However, when applied to the change of attribute values, the tangent line at time t on the curve represents the speed of the change of attribute values at time T, which is not directly related to the coordinates x and y at time T. So you have to reprocess the function. Newton iteration method:

CubicBezier.prototype.solve = function(x){
  if (x === 0 || x === 1) {             // Do not calculate t 0 and t 1
    return this.sampleCurveY(x);
  } 
  let t = x
  for (let i = 0; i < 8; i++) {         // Do 8 iterations
    const g = this.sampleCurveX(t) - x
    if (Math.abs(g) < this.epsilon) {   // Check error to acceptable range, e.g. This. epsilon = 1e-7;
      return this.sampleCurveY(t)
    }
    const d = 3 * (3 * x1 - 3 * x2 + 1) * t^2 + 2 * (3 * x2 - 6 * x1) * t + 3 * x1;  // The derivative with respect to x
    if (Math.abs(d) < 1e-6) {           // If the gradient is too low, Newton's iterative method cannot achieve higher accuracy
      break
    }
    t = t - g / d;
  }
  return this.sampleCurveY(t)                   // Find y for the resulting approximate t
}
Copy the code

Within the acceptable range of error, there is a slight gap between the iterative effect and CSS animation. The test results are as follows:

Applications in slot machines

In the annual lottery page, three Bezier curves are also used, such as: when rolling, the rolling speed should be accelerated from 0 to the specified speed maxSpeed, need to pass in: initial speed 0, maximum speed maxSpeed, current time, the time needed to accelerate to maxSpeed and control point. The following functions can be obtained:

function initCubicBezier(startTime, totalTime, startValue, targetValue, controlArr){
  let cubic = newCubicBezier(... controlArr);function getValue(time){
    let progress = (time - startTime) / totalTime;
    if (progress >= 1) {
      progress = 1
    }
    const value = cubic.solve(progress);
    return value * (targetValue - startValue) + startValue;
  }
  return getValue;
}
Copy the code
  1. Start rolling

    1. Create a function that gets the speed of the current timegetSpeed
    2. According to thetopValue offsets the image to achieve the effect of movement
    3. top = top + speed, modifyspeedThe value of is changed incrementally to achieve the effect of acceleration
    4. When the speed does not reach the maximum speed,getSpeedGet a new speed
    5. Repeat 2-4, the main code is as follows
function start() {
  let top = 0; // The current image moving position
  let speed = 0;  // Initial speed
  const targetSpeed = 100;  // Maximum speed
  const totalTime = 3000;   // Accelerate to maximum speed at 3000 ms
  const getSpeed = initCubicBezier(date.now(), totalTime, speed, targetSpeed, [. 5.. 5.. 5.. 5]);
  
  function run () {
    if (speed < targetSpeed) {
      speed = getSpeed(Date.now());
    }
    top = top + speed;
    drawImage(top);  // Redraw the image
    requestAnimationFrame(run);
  }
  requestAnimationFrame(run); 
}
Copy the code
  1. End of the scroll

    1. Need to end according totopComputes the number currently displayedshowNum
    2. According to the winning numberstargetNumandshowNumCalculate the value when you stop scrollingtargetTop
    3. Create the top function that gets the timegetTop
    4. Calculate the offset of the picture at the momenttop
    5. judgetop < targetTop“, indicating that the winning number has not been rolled, continue to roll
    6. judgetop >= targetTopWhen, stop scrolling.
function end(targetNum) {
  const targetTop = getTargetTop(top, targetNum);  // According to the leading position, and the winning number, get the stop position
  const getTop = initCubicBezier(date.now(), 3000, top, targetTop, [. 39.61..74..99.]);
  function run () {
    if (top >= targetTop) {
      cancelAnimationFrame(frame);
    } else {
      top = getTop(Date.now());
      drawImage(top);  // Draw a new imageframe = requestAnimationFrame(run); }}let frame = requestAnimationFrame(run);
}
Copy the code

The final effect is as follows:

annotation

Because the MP4 format can not be directly referenced, so the animation in the article are converted to GIF format, the effect is much worse, the original video address please check the corresponding link. The picture is stored in Github. If you can’t open it, please check the corresponding link. If you have any suggestions about video storage, please contact me. Thank you

[1] raw.githubusercontent.com/zhuchuanlei…
[2] raw.githubusercontent.com/zhuchuanlei…
[3] raw.githubusercontent.com/zhuchuanlei…
[4] raw.githubusercontent.com/zhuchuanlei…
[5] www.aliyundrive.com/s/7ogwjxLUd…
[6] www.aliyundrive.com/s/ad2EELBWu…
[7] www.aliyundrive.com/s/Kmm9uob6A…