SVG Bezier Curves in JavaScript

A lesser known feature of SVG is the ability to create Bezier curves. These are more complex than the more common lines, circles and rectangles but pretty simple once you get your head round them. In this article I'll show how to create Bezier curves of any complexity in a web page using JavaScript.

Pierre Bézier and his Eponymous Curves

The Bezier curve was invented by and named after French engineer Pierre Bézier. He spent most of his career working for Renault and his curves were used in the styling of many of the company's cars . . .

Image: Wikimedia Commons

. . . but not this one. Renault 9/11 with not a single Bezier curve. Or any sort of curve really. How sad.

Specifying a Bezier curve is very straightforward: all you need is a list of coordinates or x,y points. These points are either on the actual line itself or away from the line but effectively "pulling" it towards them, causing the otherwise straight line to curve in one or more directions. This is best illustrated by a few examples.

Let's start by looking at the simplest possible Bezier curve consisting of three points.

Each square is 100 pixels, and this is the Bezier curve drawn for the points 50,50, 150,550 and 750,50. The first and last points are the beginning and end of the curve, with the middle point at the bottom left "pulling" the curve towards it.

This is a 4-point curve, with the two middle coordinates in the list stretching the line downwards and across.

Finally a six-point curve. There is no limit to the number of points but there must be either 3 or an even number more than 3.

If you want to read up on the mathematics behind Bezier curves check out the Wikipedia article.

Bézier Curves in SVG

Bezier curves are one of the standard SVG shapes but are a bit more complex than, say, lines, circles or rectangles.

These are the SVG strings for the three Bezier curves shown above.

M 50,50 Q 150,550 750,50
M 50,50 C 50,550 750,550 750,150
M 50,50 C 150,500 500,400 400,200 S 650,150 750,50

The comma-separated number pairs are of course the x,y coordinates of each point. We also use the following four commands. (I have used upper case letters which are absolute coordinates. You can also use lower case which are relative to the current position.)

  • M - move to the specified coordinate. This is the starting point of the curve

  • Q - quadratic curve with 3 points, the previous point and the 2 after the Q

  • C - cubic curve consisting of 4 points, the previous point and the 3 after the C

  • S - string curves together into a single curve. The last point in the previous curve is the first in the one after S

The Source Code

This project consists of an HTML file and a JavaScript file which you can download as a ZIP file or from the GitHub repository.

Source Code Links

ZIP File
GitHub

The HTML

Before drawing any Bezier curves we need an HTML page with an SVG element such as this.

svgbezier.htm (partial)

<svg id="svg" height="600" width="800"></svg>

The JavaScript

This is the entire JavaScript file.

svgbezier.js

const APP = {};


window.onload = function()
{
    APP.svgelement = document.getElementById("svg");

    gridLines();

    threePoint();
    fourPoint();
    sixPoint();
    eightPoint();
}


function gridLines()
{
    const width = 800;
    const height = 600;
    const spacing = 100;

    for(let x = 0; x <= width; x += spacing)
    {
        const line = document.createElementNS("http://www.w3.org/2000/svg", "line");

        line.setAttributeNS(null, "x1", x);
        line.setAttributeNS(null, "y1", 0);
        line.setAttributeNS(null, "x2", x);
        line.setAttributeNS(null, "y2", 600);

        line.setAttributeNS(null, "stroke", "#D0D0D0");

        APP.svgelement.appendChild(line);
    }

    for(let y = 0; y <= height; y += spacing)
    {
        const line = document.createElementNS("http://www.w3.org/2000/svg", "line");

        line.setAttributeNS(null, "x1", 0);
        line.setAttributeNS(null, "y1", y);
        line.setAttributeNS(null, "x2", 800);
        line.setAttributeNS(null, "y2", y);

        line.setAttributeNS(null, "stroke", "#D0D0D0");

        APP.svgelement.appendChild(line);
    }
}


function drawPoints(points, colour)
{
    for(const point of points)
    {
        const circle = document.createElementNS("http://www.w3.org/2000/svg", "circle");

        circle.setAttributeNS(null, "cx", point[0]);
        circle.setAttributeNS(null, "cy", point[1]);
        circle.setAttributeNS(null, "r", 2);

        circle.setAttributeNS(null, "fill", colour);

        APP.svgelement.appendChild(circle);
    }
}


function generatePath(points, relative)
{
    let type = null;

    if(points.length === 3)
    {
        type = "Q";
    }
    else if(points.length === 4)
    {
        type = "C";
    }
    else if(points.length % 2 === 0)
    {
        type = "C";
    }
    else
    {
        throw 'Number of points must be 3 or an even number more than 3';
    }

    const pathPoints = ["M ", points[0][0], ",", points[0][1], type];

    for(let p = 1, l = points.length; p < l; p++)
    {
        if(p >= 4 && p % 2 === 0)
            pathPoints.push("S");

        pathPoints.push(points[p][0]);
        pathPoints.push(",");
        pathPoints.push(points[p][1]);
    }

    return pathPoints.join(" ");
}


function drawBezier(pathString, stroke)
{
    const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
    path.setAttributeNS(null, "d", pathString);
    path.setAttributeNS(null, "stroke", stroke);
    path.setAttributeNS(null, "fill", "transparent");
    path.setAttributeNS(null, "stroke-width", "1px");
    document.getElementById("svg").appendChild(path);
}


function threePoint()
{
    const points = [[50,50],[150,550],[750,50]];

    drawPoints(points, "#FF0000");

    const pathString = generatePath(points, false);

    drawBezier(pathString, "#FF0000");
}


function fourPoint()
{
    const points = [[50,50],[50,550],[750,550],[750,150]];

    drawPoints(points, "#00C000");

    const pathString = generatePath(points, false);

    drawBezier(pathString, "#00C000");
}


function sixPoint()
{
    const points = [[50,50],[150,500],[500,400],[400,200],[650,150],[750,50]];

    drawPoints(points, "#0000FF");

    const pathString = generatePath(points, false);

    drawBezier(pathString, "#0000FF");
}


function eightPoint()
{
    const points = [[50,50],[50,350],[250,200],[400,300],[200,450],[500,500],[650,200],[750,550]];

    drawPoints(points, "#FF8000");

    const pathString = generatePath(points, false);

    drawBezier(pathString, "#FF8000");
}

window.onload

This is all pretty simple. First we pick up the SVG element and keep it for later use before calling functions to draw the gridlines and a variety of curves.

gridLines

A quick function to draw the vertical and horizontal grid lines within a couple of loops.

drawPoints

Here we just iterate the supplied list of points and draw a small circle at each one.

generatePath

This function is at the heart of this project as it takes an array of points and generates the entire SVG string to be added to the SVG element.

Firstly we need to work out the type, quadratic or cubic, depending on the number of points. If the number of points is not valid an error is thrown.

Next we create an array to contain the various bits and pieces of the string, initialized with the values which are the same for any type.

We then iterate the rest of the points array (note we start at 1, not 0). If there are more than 4 points we need to add an S to string the curves together, and then add the point to the array.

Finally we just return the array joined into a string.

drawBezier

This function creates a path, sets the relevant attributes, and appends it to the SVG element.

Drawing the Curves

There are four functions which create arrays of 3, 4, 6 and 8 coordinates and then call drawPoints, generatePath and drawBezier.

Trying it Out

Open svgbezier.htm in your browser and you'll see this, four Bezier curves with 3, 4 6 and 8 points respectively. You might like to try changing the points or adding further curves with more points.