In front-end development, Bezier curves are everywhere:

  • It can be used to draw curves. In SVG and Canvas, curve drawing is provided natively using Bezier curves
  • It can also be used to describe a slow algorithm that sets CSStransition-timing-functionBessel curves can be used to describe the slow calculation of transitions
  • Bezier curves are used by almost all front-end 2D or 3D graphics library (Echarts, D3, three.js)

In this article I’m going to start by implementing a very simple curve animation to help you understand once and for all what a bezier curve is and what its properties are. There’s a little bit of math in this article, but it’s very simple :).

Implement a curve animation

You can view the online demo here

Before we start coding, let’s understand what a Bezier curve is.


Bessel curve

Bezier curve is an important parameter curve in computer graphics. It describes a curve through an equation. According to the highest order of the equation, it can be divided into linear Bezier curve, quadratic Bezier curve, cubic Bezier curve and higher order Bezier curve.

Here are the more commonly used quadratic and cubic Bezier curves

Quadratic Bessel curve

The quadratic Bezier curve is defined by three points P0,P1, and P2, also known as the control points. The equation of the curve is:

This equation actually has a geometric meaning. It means that a curve can be drawn by following these steps:

  • The selected a0-1thetvalue
  • throughP0andP1Calculate the pointQ0.Q0inP0 P1On a straight line, andlength( P0, Q0 ) = length( P0, P1 ) * t
  • Again, throughP1andP2To calculate theQ1,length( P1, Q1 ) = length( P1, P2 ) * t
  • Repeat this step again. PassQ1andQ2To calculate theB,length( Q0, Q1 ) = length( Q0, B ) * t.BThat’s the point on the current curve

Note: The length above represents the length between two points

So if we have the equation of the curve, we can just plug in the t value and we can figure out point B.

If we transition the value of t from 0 to 1 and keep counting point B, we can get a quadratic Bessel curve:

In canvas, the method of drawing quadratic Bessel curve is

ctx.quadraticCurveTo( p1x, p1y, p2x, p2y )
Copy the code

P1x, p1y, p2x, and p2y are the horizontal and vertical coordinates of the last two control points (P1 and P2). By default, the starting point of the current path is used as a control point (P0).

Cubic Bezier curves

The cubic Bezier curve requires four points P0,P1,P2, and P3 to be determined by the equation

Similarly, a cubic Bezier curve can be drawn by transitioning the value of t from 0 to 1:

In canvas, the method of drawing the Bessel curve three times is

ctx.bezierCurveTo( p1x, p1y, p2x, p2y, p3x, p3y )
Copy the code

P1x, p1y, p2x, p2y, p3X, and p3y are the horizontal and vertical coordinates of the last three control points (P1,P2, and P3). By default, the starting point of the current path is used as a control point (P0).

The characteristics of Bezier curves

Behind the cubic Bezier curves, there are higher order Bezier curves, which are also more complicated to draw

Four bezier curves

Five Bezier curves

We can conclude that the Bessel curve has several important characteristics:

  1. The Bessel curve of order n requires n+1 points
  2. Bezier curves are smooth
  3. The starting and ending points of bezier curves are tangent to the corresponding control points

Plot bezier curves

Now that we’ve reviewed the basic concepts, we’re going to talk about how to draw bezier curves

For simplicity, we choose to use quadratic Bezier curves.

Instead of worrying about animation, let’s simplify the problem to this: given a starting point and an ending point, we need to implement a function that can draw a curve.

In other words, we need to implement a function called drawCurvePath, which takes three parameters, namely three control points of the quadratic Bezier curve, in addition to the rendering context CTX. We move style control out of the function and drawCurvePath is used only to draw paths.

* @param {Object} CTX * @param {Array
      
       } p0 * @param {Array
       
        } p1 * @param {Array
        
         }  p2 */
        
       
      
function drawCurvePath( ctx, p0, p1, p2 ) {
    // ...
}
Copy the code

As mentioned earlier, the method to draw a quadratic bezier curve in canvas is quadraticCurveTo, so it only takes two lines to do this.

@param {Array
      
       } p0 * @param {Array
       
        } p1 * @param {Array
        
         } p2 */
        
       
      
function drawCurvePath( ctx, p0, p1, p2 ) {
    ctx.moveTo( p0[ 0 ], p0[ 1]); ctx.quadraticCurveTo( p1[0 ], p1[ 1 ],
        p2[ 0 ], p2[ 1]); }Copy the code

This completes the basic method of plotting quadratic Bessel curves.

But there’s a little bit of a problem with this function

If we are making a graphics library, we want to give the user a way to draw curves.

For the user, he only wants to draw a curve between a given starting point and an end point. He wants the curve to be as beautiful as possible, but he doesn’t want to worry about the implementation details. If he needs to give a third point, there is some learning cost (at least to figure out what a Bessel curve is).

If you look at this and you wonder, even quadratic Bezier curves require three control points, how do you plot a curve with only a starting point and an ending point.

We can choose a point on the vertical bisector between the beginning and the end as a third control point, and we can give the user a parameter to control how much the curve bends, and now the function looks like this

@param {Array
      
       } CTX * @param {Array
       
        } start * @param {Array
        
         } end * @param {number} curP curve (0-1) */
        
       
      
function drawCurvePath( ctx, start, end, curveness ) {
    // ...
}
Copy the code

We use curVENESS to describe how curved the curve is, which is how far the third control point deviates. So it’s pretty easy to figure out the middle point. Now the full function looks like this:

@param {Array
      
       } start start * @param {Array
       
        } end end * @param {number} curP curve (0-1) */
       
      
function drawCurvePath( ctx, start, end, curveness ) {
    // Calculate the intermediate control point
    var cp = [
         ( start[ 0 ] + end[ 0) /2 - ( start[ 1 ] - end[ 1 ] ) * curveness,
         ( start[ 1 ] + end[ 1) /2 - ( end[ 0 ] - start[ 0 ] ) * curveness
    ];
    ctx.moveTo( start[ 0 ], start[ 1]); ctx.quadraticCurveTo( cp[0 ], cp[ 1 ],
        end[ 0 ], end[ 1]); }Copy the code

Yeah, it’s just a few lines, and then we can use it to draw a curve, like this


      
<html lang="en">
    <head>
        <title>draw curve</title>
    </head>
    <body>
        <canvas id="canvas" width="800" height="800"></canvas>
        <script>
            var canvas = document.getElementById( 'canvas' );
            var ctx = canvas.getContext( '2d' );
            
            ctx.lineWidth = 2;
            ctx.strokeStyle = '#ff0000';
            ctx.beginPath();
    
            drawCurvePath( 
                ctx,
                [ 100.100 ],
                [ 200.300].0.2
            );
            
            ctx.stroke();
            
            function drawCurvePath( ctx, start, end, curveness ) {
                // ...
            }
        </script>
    </body>
</html>
Copy the code

Animate bezier curves

Finally come to the body of the article, our goal is not to draw a static curve, we want to draw a curve with a transition effect.

To simplify things, we want the function that draws the curve to take another parameter, which is the percentage of the curve that we draw. We’re going to call this function periodically, increment the percentage, and we’re going to animate it.

We added a parameter percent to represent the percentage, and now the function looks like this:

@param {Array
      
       } start start * @param {Array
       
        } end end * @param {number} CurP curve (0-1) * @Param {number} percent Drawing percentage (0-100) */
       
      
function drawCurvePath( ctx, start, end, curveness, percent ) {
    // ...
}
Copy the code

But canvas’s quadraticCurveTo method can only draw a complete quadratic Bezier curve, there is no way to control it to draw only part of it.

When you’re done, clearRect and erase some of it, okay? This is not feasible because it is difficult to determine the scope of erasure. If the line width of the curve is relatively wide, you also need to make sure that the erased boundary is perpendicular to the end of the curve, and the problem becomes very complicated.

Now look at the picture again

Can we understand the parameter “percent” as the value of T, and then calculate all the points in the middle through the equation of Bezier curve, and connect them with straight lines, so as to simulate drawing part of Bezier curve?

Methods a

Instead of using canvas’s quadatic curveto to draw a curve, we use bezier’s equations to calculate a series of points and multiple straight lines to simulate the curve.

The advantage of this is that we can easily control the scope of the drawing.

The function implementation then looks like this:

@param {Array
      
       } start start * @param {Array
       
        } end end * @param {number} CurP curve (0-1) * @Param {number} percent Drawing percentage (0-100) */
       
      
function drawCurvePath( ctx, start, end, curveness, percent ) {

    var cp = [
         ( start[ 0 ] + end[ 0) /2 - ( start[ 1 ] - end[ 1 ] ) * curveness,
         ( start[ 1 ] + end[ 1) /2 - ( end[ 0 ] - start[ 0 ] ) * curveness
    ];
    
    ctx.moveTo( start[ 0 ], start[ 1]);for ( var t = 0; t <= percent / 100; t += 0.01 ) {

        var x = quadraticBezier( start[ 0 ], cp[ 0 ], end[ 0 ], t );
        var y = quadraticBezier( start[ 1 ], cp[ 1 ], end[ 1], t ); ctx.lineTo( x, y ); }}function quadraticBezier( p0, p1, p2, t ) {
    var k = 1 - t;
    return k * k * p0 + 2 * ( 1 - t ) * t * p1 + t * t * p2;    // This equation is the quadratic Bezier curve equation
}
Copy the code

You can then set the timer to call this method every once in a while and increment percent

For smoother animation, we use requestAnimationFrame instead of timer


      
<html lang="en">
    <head>
        <title>draw curve</title>
    </head>
    <body>
        <canvas id="canvas" width="800" height="800"></canvas>
        <script>
            var canvas = document.getElementById( 'canvas' );
            var ctx = canvas.getContext( '2d' );
            
            ctx.lineWidth = 2;
            ctx.strokeStyle = '# 000';
            
            var percent = 0;
            
            function animate() {
                
                ctx.clearRect( 0.0.800.800 );
                ctx.beginPath();

                drawCurvePath( 
                    ctx,
                    [ 100.100 ],
                    [ 200.300].0.2,
                    percent
                );
    
                ctx.stroke();
    
                percent = ( percent + 1 ) % 100;
                
                requestAnimationFrame( animate );
                
            }
            
            animate();
            
            function drawCurvePath( ctx, start, end, curveness, percent ) {
                // ...
            }
        </script>
    </body>
</html>
Copy the code

Results obtained:

This basically fulfilled our requirements, but it had a problem:

Tests found that it took about the same amount of time to make a lineTo as it did to make a quadraticCurveTo curve, but it took only one time to draw a curve, compared with dozens of times to use the lineTo.

In other words, by plotting the curve this way, performance drops by a factor of ten compared to our previous implementation. You might not feel the difference when drawing a single curve, but if you need to draw thousands of curves at the same time, performance will suffer significantly.

Method 2

Is there a way to draw part of a complete curve using quadraticCurveTo?

Let’s go back to this picture again

At some point in the middle, such as t=0.25, it looks like this:

Notice that this part of the curve P0 minus B also seems to be a Bezier curve, where the control points become P0, Q0, B.

Now the problem is solved, we just need to calculate Q0, B each time, to get the control point of one of the bezier curves, and then we can plot it using the quadraticCurveTo curve.

The code is as follows:

@param {Array
      
       } start start * @param {Array
       
        } end end * @param {number} CurP curve (0-1) * @Param {number} percent Drawing percentage (0-100) */
       
      
function drawCurvePath( ctx, start, end, curveness, percent ) {

    var cp = [
         ( start[ 0 ] + end[ 0) /2 - ( start[ 1 ] - end[ 1 ] ) * curveness,
         ( start[ 1 ] + end[ 1) /2 - ( end[ 0 ] - start[ 0 ] ) * curveness
    ];
    
    var t = percent / 100;
    
    var p0 = start;
    var p1 = cp;
    var p2 = end;
    
    var v01 = [ p1[ 0 ] - p0[ 0 ], p1[ 1 ] - p0[ 1]].// vector 
      ,>
    var v12 = [ p2[ 0 ] - p1[ 0 ], p2[ 1 ] - p1[ 1]].// 
      ,>

    var q0 = [ p0[ 0 ] + v01[ 0 ] * t, p0[ 1 ] + v01[ 1 ] * t ];
    var q1 = [ p1[ 0 ] + v12[ 0 ] * t, p1[ 1 ] + v12[ 1 ] * t ];
    
    var v = [ q1[ 0 ] - q0[ 0 ], q1[ 1 ] - q0[ 1]].
      ,>

    var b = [ q0[ 0 ] + v[ 0 ] * t, q0[ 1 ] + v[ 1 ] * t ];
    
    ctx.moveTo( p0[ 0 ], p0[ 1]); ctx.quadraticCurveTo( q0[0 ], q0[ 1 ],
        b[ 0 ], b[ 1]); }Copy the code

Replace the previous page with the above code and you can see that the result is the same:

Draw the animation

Now that we’ve solved the most critical problem, we can animate. But this part is not important, so I won’t post the code.

The full code can be seen here

The end of the

So that’s the end of this blog post, we talked about how canvas draws curves and how to draw curve transitions. In my next blog post, I plan to write about how to simulate the effect of light shining on surfaces in nature in canvas.

My blog address: github.com/hujiulong/b…

I will share my learning achievements and experience here, especially canvas/WebGL/ SVG technology. If you are interested in front-end graphics drawing, you can pay attention to my blog, bookmark star and subscribe watch.

I just recently moved my blog to Github, so there are not many articles, I will keep writing!