Cubic Bezier Curves with SVG Paths
For those who don’t know about SVGs, they use XML markup to describe a vector image. Unlike traditional images, vector graphics are lossless which means that they can be scaled to any size and always have crisp edges.
One great use case of vector images is in web development since they can be embedded into a webpage using the <svg> tag. This allows you to place these lossless images into your own webpage allowing for many possibilities from extremely crisp images, to customizable animations and effects. Additionally since it is not an image it doesn’t slow down webpage load time nearly as much as a raster image would since the svg is defined as text much like any other html element and rendered by the browser.
One of the easiest ways to create an svg image is to use a vector graphics editor such as Adobe Illustrator or Inkscape. While these editors do a great job of creating a vector graphic for you, their output can be a bit bulky and often has far more data in the svg tag than is really required (although the newest release of Illustrator has improved this quite considerably).
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" id="svg8" version="1.1" viewBox="0 0 26.458333 13.229167" height="500" width="250"> <defs id="defs2" /> <metadata id="metadata5"> <rdf:RDF> <cc:Work rdf:about=""> <dc:format>image/svg+xml</dc:format> <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> <dc:title></dc:title> </cc:Work> </rdf:RDF> </metadata> <g transform="translate(0,-283.77083)" id="layer1"> <path id="path817" d="m 3.0735996,291.55438 c 0,0 -0.4009043,-6.34765 7.0826424,-4.34313 7.483547,2.00452 -4.4433559,3.90882 0.968852,5.07812 5.412208,1.1693 13.330068,0.33409 10.99146,-2.57247" style="fill:none;stroke:#000000;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> </g></svg>
The SVG above is exported from Inkscape, while the code below is all you need to create the exact same simple line. You can remove a lot of unnecessary metadata by creating the path yourself.
<svg viewBox="0 0 26.458333 13.229167" height="500" width="250"><g transform="translate(0,-283.77083)"><path d="m 3.1,291.6 c 0,0 -0.4,-6.3 7.1,-4.3 7.5,2 -4.4,3.9 1,5.1 5.4,1.2 13.3,0.3 10.1,-2.5" style="fill:none;stroke:#000;stroke-width:0.26px;"/></g></svg>
Cubic Bezier Curves
There are many types of paths that can be defined with the SVG tag but as suggested in the title this article will be focused on what I found to be the most challenging curve to create, the Cubic Bezier.
Cubic bezier curves are defined by four points, two endpoints and two “handles”. The endpoints, as the name suggests, define where your curve will start and end. The interesting points are the handles as these points define the shape of the line between the two endpoints.
As seen above the handles can be oriented in such a way that it stretches the curve on its way between the endpoints. This allows for the creation of many shapes that are otherwise very difficult to define.
Additionally you can “chain” them together to create longer paths that can resemble any line or shape. This ability allows you to create very abstract lines that you otherwise cannot define.
I will first describe how they are defined in the XML markup through HTML and will then get into the math behind how these lines are actually graphed.
Cubic Bezier Paths in HTML
As mentioned in the introduction of this article you can easily embed vector graphics into your webpages using the <svg> tag. For the duration of this section I will use the following HTML code as a container for our path. For more information about how to define these <svg> tags look towards the bottom of the article for the references and further reading sections.
<html>
<svg viewBox="0 0 1000 500">
<g>
<path
d=" [ OUR PATH WILL GO HERE ] "
style="fill:none; stroke:#000; stroke-width:5px;" />
</g>
</svg>
</html>
So we will define the “path string” and this can be interpreted as what we would put into the “d” attribute of our path tag.
First a quick note, we will prepend our string with a “M 50, 250” as this will simply move our starting point (can be thought of as the origin) to the point (50, 250) on our viewbox.
There are two types of segments that are important when creating a Cubic Bezier Curve. The ‘c’ and ‘s’ segments (or alternatively the ‘C’ and ‘S’). We will be using the lowercase versions of the segments, the difference being that the lowercase ones use relative coordinates whereas the uppercase ones use absolute coordinates. Both have their own use case and are up to personal preference.
The c curve has the following “parameters”:
- c dx1, dy1 dx2, dy2 dx, dy
We first begin where the path last left off (this is our start point), we then have (dx1, dy1) and (dx2, dy2) which are the coordinate points for the two handles of the curve and finally we have (dx, dy) which is the coordinate of the end point of the curve. (Once again when using lowercase notation, the “coordinates” are relative, so the point (50, 50) defines a point 50 units right and 50 units down from the current start point)
Above is a diagram which shows a labelled example of what is meant by each of the handle points and the end point. So defining one ‘c’ segment in our path we would get something like “M 50, 250 c 50, -50 100, 50 150, 25” which gives us the following when rendering the HTML:
Lets take a deeper look as too why the line looks this way.
By labelling the points we can see how our handle choices have defined the curve of the path. Also take notice that our starting point (50, 250) is the origin from the “M 50, 250” and it is an absolute coordinate unlike the other coordinates on the diagram which are in relative coordinates (i.e the first handle is 50 units to the right and 50 units above the starting point of (50, 250)). If we were to define the same curve in absolute coordinates we would get “M 50, 250 C 100, 200 150, 300 200, 275”.
Now the ‘c’ tag is all you really need to create a cubic bezier curve but one thing that is a bit annoying is when chaining ‘c’ tags together. Notice in the following diagram how we can chain the paths together but they don’t quite fit together “smoothly”.
You can easily tell where the two curves connect together because they dont line up correctly. This is more evident with the handles visible.
The two handles don’t exactly line up and therefore it doesn’t connect in a smooth way. One way to do this would be to calculate where the first handle should be in order to perfectly line up the slope of the previous handle and try to match the magnitude of the two handles. This will work but becomes a bit of a hassle to manage especially if you are moving around the points on your line.
For this exact reason, the ‘s’ tag exists. This tag takes exactly two parameters.
- s dx2, dy2 dx, dy
This tag acts almost identically to the ‘c’ tag but the first handle point is no longer required. Instead (dx1, dy1) is calculated by the browser renderer to perfectly mirror the second handle of the previous segment. For this reason if your goal is to make a smooth curve you can normally begin with a single ‘c’ tag and then chain together ‘s’ tags to extend your path in a smooth line.
Adding an “s 100, 100 150, -15” to the previous path string gives us “M 50, 250 c 50, -50 100, 50 150, 25 s 100, 100 150, -15” which looks like the following when rendered:
As you can see the curves have a gentle and smooth connection in the middle of the path as the ‘s’ tag creates the first handle of our second curve for us.
Using both ‘c’ and ‘s’ tags you can create any path you would like by simply chaining several together and adjusting where all of the points are. I used this technique to a great effect when creating the waves for the header of emotivmusic.com. Using the knowledge in this article and some python I wrote a simple script that given some parameters was able to give me a wave that I could add to the site.
Now that we have taken a look at how these paths can be used to create lines in HTML, we will take a look into how the lines are actually rendered from the four points given.
The Mathematics of Cubic Bezier Curves
A cubic bezier curve is a function f, which takes four points as an input and outputs two functions. These two functions are the parametric equations for the bezier curve defined by the four input points where 0 ≤ t ≤ 1.
These parametric equations are defined by the following equations:
Where (x0, y0), (x3, y3) are your end points and (x1, y1), (x2, y2) are the two handles.
With these equations you can easily graph the bezier curve. But to save you some time I have already done so and in fact if you have been reading along this whole article you have seen the images from the graphed curve from my saved desmos graph. This graph allows you to play around with the points by dragging them around to see how it affects the curve and can help understand not only the math behind these curves but also how to manipulate the handles to give you the desired path.
This desmos graph gives all of the details into how to graph these curves from the parametric equations.
Another interesting detail about the bezier curves is that they are also the result of linear interpolation of the lines between each of the four points as demonstrated in the animation below.
It is from this linear interpolation that the parametric equations can be derived as described in this great video explanation of Bezier Curves.
In addition to these curves you can also construct higher order curves in similar ways as mentioned in the Bezier Curve wikipedia article.
References and Further Reading
- Mozilla SVG Paths Documentation for HTML
- My Interactive Demo of Cubic Bezier Curves
- Video Explanation and Visualization Part 1
- Video Explanation and Visualization Part 2
- Wikipedia (some great animations and information of higher order bezier curves)
- Great Overall Tutorial on How HTML SVGs Work