Bezier Curves
The path
element features quite a number of these commands. There are two that are relevant for our purposes:
Q
, which instructs the path to create a quadratic Bézier curve.C
, which instructs the path to create a cubic Bézier curve.
Intro to Bézier Curves
Bézier curves are surprisingly common. Due to their versatility, they're a staple in most graphics software like Photoshop or Figma, but they're also used as timing functions: if you've ever used non-linear CSS transitions (like the default "ease"), you've already worked with Bézier curves!
But what are they, and how do they work?
A Bézier curve is essentially a line from a start point to an end point that is acted upon by one or more control points. A control point curves the line towards it, as if the control point was pulling it in its direction.
The following line looks like a straight line, but check out what happens when you move the points around—try dragging the middle control point up and down.
The line above is a quadratic Bézier curve; this means that it has a single control point. I'm guessing it gets its name from the fact that you can create parabola-like shapes with it:
A cubic Bézier curve, in contrast, has two control points. This allows for much more interesting curves:
The syntax for Bézier curves in SVG path
definitions is a little counter-intuitive, but it looks like this:
The thing that makes this counter-intuitive, to me at least, is that the startPoint
is inferred in the Q
command; while there are 3 points needed for a quadratic Bézier curve, only 2 points are passed as arguments to Q
.
Similarly, for a cubic Bézier curve, only the control points and the end point are provided to the C
command.
This syntax does mean that curves can conveniently be chained together, as one curve starts where the last one ends:
OK, I think that's enough playing with vanilla SVGs. Let's see how we can leverage React to make these curves dynamic!
Bézier Curves in React
Up to this point, we've been looking at static SVGs. How do we make them change, over time or based on user input?
Well, in keeping with the "meta" theme of this blog post, why not examine the draggable-with-lines Bézier curves from earlier in this post?
There's a fair bit of code to manage this, even in this slightly-simplified snippet. I've annotated it heavily, which hopefully makes things easier to parse.
The full version, with support for touch events, can be found on GitHub.
To summarize how this works:
- React holds variables in component state for
startPoint
,controlPoint
, andendPoint
. - In the
render
method, we build the instructions for thepath
using these state variables. - When the user clicks or taps on one of the points, we update the state to keep track of which point is moving with
draggingPointId
. - As the user moves the mouse (or finger) across the SVG's surface, we do some calculations to figure out where the currently-dragging point needs to move to. This is made complex by the fact that SVGs have their own internal coordinate system (
viewBox
), and so we have to translate the on-screen pixels to this system. - Once we have the new X/Y coordinate for the active point,
setState
lets React know about this state change, and the component re-renders, which causes thepath
to be re-calculated.