Creating a Curvey Arrow


Recently for one of my blog posts I wanted to create an interesting arrow that curved between two elements on the page. I have never worked with anything like this before, but I feel like it is something that could easily become a default option in my UI kit for this site.

The result I ended up with is the arrow between two tables below:

User
PK id
name
Address
FK user
street
postcode

I think it looks pretty cool and gives some flair to an otherwise boring layout.

How it is made

I chose to use an SVG element to create the arrow due to the ability to create curves easily with a path element. If you are new to SVG paths, they are an element that lets you define a shape through a string of commands.

These paths have a large list of options that let you move, draw straight or curved lines, and what style of curve to use. I won’t go into detail here, but you can find detailed information on MDN.

The two I chose to make this arrow M and C. Note the capitalisation is important.

M

The M command stands for “move to” and it simply moves the drawing cursor to a specific point on the canvas. The uppercase version uses absolute coordinates, whereas the lowercase version uses relative coordinates.

For instance, the below line was drawn through M10 10 to move to the top left corner of the SVG canvas, and then L90,90 to draw a straight line to the bottom right corner.

You can move these anchors about to change where the line starts and ends, and see what the path command looks like.

d="M10 10L90 90"
Example of the M and L commands moving the cursor to create a line.

C

The C command is a little more complex. It allows a Cubic Bezier curve to be drawn through defining three parameters. The first two are what are called control points that define the shape of the curve, and the last point is the end point of the curve.

Control points are extra X,Y coordinates that sort of pull the line towards them, changing the shape of the curve. The further away the control points are from the start and end points, the more extreme the curve will be.

Here is an example of the C command in action. You can move the control points around to see how they affect the curve.

d="M10 10L90 90"
Example of the C command creating a cubic bezier curve.

The ranges allowed for this example were limited to valid values within the canvas, but it is also valid to choose values that go beyond the borders. This feature is often used for animation curves (normal value between 0 and 1) where the element goes past the final location before settling back.

The arrow head

Creating the arrow head untilises another svg element called marker. This is a special definition that can be added to multiple parts of a path to create shapes like arrow heads.

It has some inbuilt features such as orienting automatically based on the direction of the path, and scaling to fit the stroke width of the line.

There are a lot of properties that can be set on a marker that you can find on MDN. The ones that are imported for the arrow are:

  1. markerWidth and markerHeight - defines the size of the marker.
  2. refX and refY - defines the point in the marker that is placed on the path.
  3. orient - defines how the marker is rotated. Setting to auto makes it rotate based on the path direction.

Here is an example of a marker being used to create an arrow head at the end of a line. Try changing the settings to see how it affects the arrow head.

6
6
0
3
d="M10 50C50 90,90 50,90 50"
Example of a marker creating an arrow head at the end of a line.

Placing the arrow

So with the above svg creation done, the last step is defining where the real start and end location need to be. For my case I chose to position the SVG over the common parent element of the two tables. This allows me to use the tables position relative to that parent to define where the arrow should start and end.

Lets take a look of an example with two boxes inside a parent container. The aim is to draw an arrow from the center right of the left box to the center left of the right box.

Two boxes to draw a line between.

So how do we find those positions? In my case I have made sure the elements have a common parent and have given that parent a relative position. This allows me to use the offset properties of the child elements to find their position within the parent.

const box1 = document.getElementById('box-1');
const box2 = document.getElementById('box-2');

// Start position is center right of box 1
const startPosition = {
    // Right edge
    x: box1.offsetLeft + box1.offsetWidth,

    // Vertical center
    y: box1.offsetTop + (box1.offsetHeight / 2),
};

// End position is center left of box 2
const endPosition = {
    // Left edge
    x: box2.offsetLeft,
    // Vertical center
    y: box2.offsetTop + (box2.offsetHeight / 2),
};

Using these positions the start and end are defined. As there are no control points defined yet the line will be straight. The result looks like this:

Example of positioning an arrow between two elements.

Defining the curve

The straight line looks alright, but I think a curve is always a bit more interesting to look at. To define the curve there needs to be a location for the two control points.

This part would usually be done manually per example as having unique curves gives a better feeling.

In this example I chose the X positions for both controls to be the centre of the arrow, and the Y positions to be offset above and below the start and end points respectively.

Curvey result.

The full code

Here is the full code for the arrow between boxes component used in this example. You can edit anything you like to see what it does



Want to read more? Check out more posts below!