Creating Interactive React Components with SVGs

Nasrullah

Posted by Nasrullah

SVGs are great for making components that have complex graphics that can't be shown with plain HTML, that would have once required plugins like flash. They also work really well with React because they are just DOM elements, so in React, only virtual representations of the SVG elements are kept, and React makes updates to the SVG very efficiently when the elements' virtual representations are modified. They are also very easy to write by hand and compatible with most browsers and don't require any plugins.

Lets make a simple square that can be dragged around in a canvas to demonstrate how easy this is to do with React and SVGs. We'll start by creating a component with a simple SVG that only contains a rectangle:

1
2
3
4
5
6
7
8
9
class Rect extends React.Component {
  render() {
    return (
      <svg viewBox="0 0 100 100">
         <rect x="1" y="1" width="20" height="20" />
      </svg>
    );
  }
}

We will want the position of the rectangle to be a variable so lets add the rectangle's position to the component's state and position it using that. We will also need refs to the svg element and the rectangle element.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
class Rect extends React.Component {
  constructor() {
    super();
    this.state = {rect: {x: 1, y: 1}};
  }
   render() {
    return (
      <svg viewBox="0 0 100 100" ref={(svg) => this.svg = svg}>
        <rect
          x={this.state.rect.x}
          y={this.state.rect.y}
          width="20"
          height="20"
          ref={(e) => this.svgRectElem = e}
        />
      </svg>
    );
  }
}

To position the rectangle, we need to get the coordinates of the mouse in the svg's coordinate system. We can do this using the getScreenCTM method of the svg element, which returns the transformation matrix from the svg's coordinates to screen coordinates. The inverse of that matrix will transform screen coordinates to the svg coordinate system, and we can apply that transformation matrix to the mouse coordinates like this:

1
2
3
4
let point = this.svg.createSVGPoint();
point.x = event.clientX;
point.y = event.clientY;
let cursor = point.matrixTransform(this.svg.getScreenCTM().inverse());

Great! The rectangle can now be dragged around by clicking on it, but there is still one problem. The rectangle gets positioned with its top left corner at the cursor instead of being relative to the point that was clicked. This is simple to fix, we just need to get the offset of the point on the rectangle that was clicked, and subtract it from the position of the cursor to get the position of the rectangle. Now we put everything together and here's how it looks:

Here's another, more complex, example which has a cubic bezier curve that can be manipulated by moving its control points:

Return to Articles & Guides