Guide to canvas manipulation with React Konva
Tags: canvas, react, webdev
Published: 2020-03-25 16:30:33

---
title: Guide to canvas manipulation with React Konva
published: true
date: 2020-03-09 16:00:01 UTC
tags: canvas,react,webdev
canonical_url: https://blog.logrocket.com/canvas-manipulation-react-konva/
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/5dlftxapv854vfo2fjkp.jpeg
---
**Written by [John Au-Yeung](https://blog.logrocket.com/author/johnau-yeung/)**✏️

React Konva is a tool that allows us to manipulate the canvas. It lets us easily create shapes without calculating where each point will be, and it has lots of built-in shapes and animation features we can use to create more interactive canvases.

React Konva is available in the form of a Node package. We can install it by running:

```jsx
npm install react-konva konva --save
```

## Drawing basic shapes

With React Konva, we can create a canvas with its `Stage` component, which has one or more `Layer` components nested inside.

Inside each `Layer`, we can put in whatever shape we want. React Konva comes with shapes such as rectangles, circles, ellipses, lines, images, text, stars, labels, SVG, and polygons.

We can create a canvas and add a rectangle with a shadow as follows:

```jsx
import React from "react";
import { Stage, Layer, Rect } from "react-konva";

export default function App() {
  return (
    <Stage width={window.innerWidth} height={window.innerHeight}>
      <Layer>
        <Rect
          x={20}
          y={50}
          width={100}
          height={100}
          fill="red"
          shadowBlur={5}
        />
      </Layer>
    </Stage>
  );
}
```

In the code above, we referenced the `Stage` component, which creates the canvas. The `width` is set to the browser tab width, and the `height` is set to the browser tab height.

Then we put the rectangle inside the canvas by using the `Rect` component that comes with React Konva. `x` and `y` sets the position of the top left corner; `width` and `height` set the dimensions; and `fill` sets the fill color. `shadowBlur` lets us adjust the shadow’s width in number of pixels.

In contrast, to create a rectangle with plain JavaScript, we have to do more work:

```jsx
const canvas = document.querySelector("canvas");
canvas.style.width = '500';
canvas.style.height = '500';
if (canvas.getContext) {
  const ctx = canvas.getContext('2d');
  ctx.rect(20, 50, 100, 100);
  ctx.shadowColor = 'gray';
  ctx.shadowBlur = 10;
  ctx.shadowOffsetX = 10;
  ctx.shadowOffsetY = 10;
  ctx.fillStyle = "red";
  ctx.fill();
}
```

We have to set the parameters for the shadow by setting each property individually rather than just passing in one prop. Also, the canvas context only comes with methods for drawing lines and rectangles. This means that any other shapes would be very difficult to draw without some library like React Konva.

Similarly, we can draw circles by replacing `Rect` with `Circle`. Here, the `x` and `y` props are the coordinates of the center of the circle. We can write the following to create a red circle with a shadow:

```jsx
import React from "react";
import { Stage, Layer, Circle } from "react-konva";

export default function App() {
  return (
    <Stage width={window.innerWidth} height={window.innerHeight}>
      <Layer>
        <Circle
          x={100}
          y={100}
          width={100}
          height={100}
          fill="red"
          shadowBlur={5}
        />
      </Layer>
    </Stage>
  );
}
```

Regular polygons are just as easy to draw with React Konva:

```jsx
import React from "react";
import { Stage, Layer, RegularPolygon } from "react-konva";

export default function App() {
  return (
    <Stage width={window.innerWidth} height={window.innerHeight}>
      <Layer>
        <RegularPolygon
          sides={10}
          x={100}
          y={100}
          width={100}
          height={100}
          fill="red"
          shadowBlur={5}
        />
      </Layer>
    </Stage>
  );
}
```

## Drawing custom shapes

We can draw custom shapes by using the `Shape` component. Its `sceneFunc` prop takes a function with the canvas `context` object as the first parameter and the `shape` method as the second parameter. The `shape` object is a Konva-specific method used in the `fillStrokeShape` method.

For instance, we can draw our own shape as follows:

```jsx
import React from "react";
import { Stage, Layer, Shape } from "react-konva";

export default function App() {
  return (
    <Stage width={window.innerWidth} height={window.innerHeight}>
      <Layer>
        <Shape
          sceneFunc={(context, shape) => {
            context.beginPath();
            context.moveTo(0, 50);
            context.bezierCurveTo(100, 200, 100, 400, 200, 0);
            context.closePath();
            context.fillStrokeShape(shape);
          }}
          fill="#00D2FF"
          stroke="black"
          strokeWidth={4}
        />
      </Layer>
    </Stage>
  );
}
```

The `sceneFunc` props let us access the canvas directly, and we can draw whatever shape we want. Then, we call `fillStrokeSgape` on the canvas with the `shape` function passed in as a callback to fill the shape with the given color and draw the strokes. We can also pass in other props like `fill` and `stroke` to set the fill and stroke color.

[![LogRocket Free Trial Banner](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2017/03/f760c-1gpjapknnuyhu8esa3z0jga.png?resize=1200%2C280&ssl=1)](https://logrocket.com/signup/)

## Event handling

React Konva shapes can listen to events and then respond to the events accordingly. For instance, it’s very easy to make a shape draggable by adding the draggable prop to it and then attach event listeners for the `dragstart` and `dragend` events:

```jsx
import React from "react";
import { Stage, Layer, Circle } from "react-konva";

export default function App() {
  const handleDragStart = e => {
    e.target.setAttrs({
      shadowOffset: {
        x: 15,
        y: 15
      },
      scaleX: 1.1,
      scaleY: 1.1
    });
  };
  const handleDragEnd = e => {
    e.target.to({
      duration: 0.5,
      easing: Konva.Easings.ElasticEaseOut,
      scaleX: 1,
      scaleY: 1,
      shadowOffsetX: 5,
      shadowOffsetY: 5
    });
  };

  return (
    <Stage width={window.innerWidth} height={window.innerHeight}>
      <Layer>
        <Circle
          x={100}
          y={100}
          width={100}
          height={100}
          fill="red"
          shadowBlur={5}
          draggable
          onDragStart={handleDragStart}
          onDragEnd={handleDragEnd}
        />
      </Layer>
    </Stage>
  );
}
```

In the code above, we have the `handleDragStart` function to set the shadow to a new color and offset as the original circle. We also expanded the shape slightly to indicate that it’s being dragged.

We pass the `handleDragStart` function into the `onDragStart` prop, and likewise, we defined the `handleDragEnd` function to the `onDragEnd` prop. In there, we move the shape that’s being dragged to the new position by calling the `to` method of the shape object that’s being dragged, which is the value of `e.target`.

We set the easing to the built-in `Konva.Easings.ElasticEaseOut` value, which makes it look bouncy as it’s being dragged and dropped. The easing changes the speed that the shape moves as a function of time. Without it, everything would move at constant speed, which is neither natural nor interesting.

Ease out means that it moves quickly at first and then slows down afterward.

## Adding images

We can add an image to the canvas with React Konva’s `Image` component. To add an image, we create a `window.Image` instance, set the URL of our image to the `src` attribute, and then pass the image object as the value of the `image` prop to the `Image` component as follows:

```jsx
import React, { useEffect, useState } from "react";
import { Stage, Layer, Image } from "react-konva";

export default function App() {
  const [image, setImage] = useState(new window.Image());

  useEffect(() => {
    const img = new window.Image();
    img.src =
      "https://images.unsplash.com/photo-1531804055935-76f44d7c3621?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=334&q=80";
    setImage(img);
  }, []);

  return (
    <Stage width={window.innerWidth} height={window.innerHeight}>
      <Layer>
        <Image x={100} y={200} image={image} />
      </Layer>
    </Stage>
  );
}
```

In the code above, we used the `useEffect` Hook to load the image when our app first loads by passing an empty array to the second argument of `useEffect`.

After adding the image, we can use some built-in effects from React Konva to add some effects to our images. For instance, we can use the built-in blur effect to blur the image that we loaded onto the canvas as follows:

```jsx
import React, { useEffect, useState, useRef } from "react";
import { Stage, Layer, Image } from "react-konva";
import Konva from "konva";

export default function App() {
  const [image, setImage] = useState(new window.Image());
  const imageRef = useRef();

  useEffect(() => {
    const img = new window.Image();
    img.crossOrigin = "Anonymous";
    img.src =
      "https://images.unsplash.com/photo-1531804055935-76f44d7c3621?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=334&q=80";
    setImage(img);
  }, []);

  useEffect(() => {
    if (image) {
      imageRef.current.cache();
      imageRef.current.getLayer().batchDraw();
    }
  }, [image]);

  return (
    <Stage width={window.innerWidth} height={window.innerHeight}>
      <Layer>
        <Image
          blurRadius={10}
          filters={[Konva.Filters.Blur]}
          x={100}
          y={200}
          image={image}
          ref={imageRef}
        />
      </Layer>
    </Stage>
  );
}
```

In the code above, we loaded an image, then we added the `filters` prop with the `Konva.Filters.Blur` object to the array. It’s an array, which means we can apply more than one effect at once.

We need the second `useEffect` callback to apply the filter. It watches when the `image` loads, and then redraws the image with the filter applied when it’s loaded.

It references the `imageRef` that we set the image to. Then we call `imageRef.current.cache();` to cache the original image, and `imageRef.current.getLayer().batchDraw();` redraws the image with the filter applied.

## Resizing images with `Transformer`

In React Konva, transformer objects let users resize images by adding handles to the shape being transformed and resizing as we do with most photo editing apps.

Adding the transformation feature is a bit complex. We’ve to create a component that has the React Konva shape, then make it draggable. Next, we have to add the `onTransformationEnd` prop with a function that scales the shape by the factor that the user transforms the shape to and set that as the width and height.

Besides the shape component, we have to add the `Transformer` component as a sibling to add the handles to let users transform the shape. For instance, we write the following to create two circles and then show the handles to let users stretch and shrink the shapes as follows:

```jsx
import React from "react";
import { Stage, Layer, Circle, Transformer } from "react-konva";

const Circ = ({ shapeProps, isSelected, onSelect, onChange }) => {
  const shapeRef = React.useRef();
  const trRef = React.useRef();

  React.useEffect(() => {
    if (isSelected) {
      trRef.current.setNode(shapeRef.current);
      trRef.current.getLayer().batchDraw();
    }
  }, [isSelected]);

  return (
    <React.Fragment>
      <Circle
        onClick={onSelect}
        ref={shapeRef}
        {...shapeProps}
        draggable
        onDragEnd={e => {
          onChange({
            ...shapeProps,
            x: e.target.x(),
            y: e.target.y()
          });
        }}
        onTransformEnd={e => {
          const node = shapeRef.current;
          const scaleX = node.scaleX();
          const scaleY = node.scaleY();

          node.scaleX(1);
          node.scaleY(1);
          onChange({
            ...shapeProps,
            x: node.x(),
            y: node.y(),
            width: Math.max(5, node.width() * scaleX),
            height: Math.max(node.height() * scaleY)
          });
        }}
      />
      {isSelected && (
        <Transformer
          ref={trRef}
          boundBoxFunc={(oldBox, newBox) => {
            if (newBox.width < 5 || newBox.height < 5) {
              return oldBox;
            }
            return newBox;
          }}
        />
      )}
    </React.Fragment>
  );
};

const initialCircles = [
  {
    x: 100,
    y: 100,
    width: 100,
    height: 100,
    fill: "blue",
    id: "circ1"
  },
  {
    x: 150,
    y: 150,
    width: 100,
    height: 100,
    fill: "green",
    id: "circ2"
  }
];

const App = () => {
  const [circles, setCircles] = React.useState(initialCircles);
  const [selectedId, selectShape] = React.useState(null);

  return (
    <Stage
      width={window.innerWidth}
      height={window.innerHeight}
      onMouseDown={e => {
        const clickedOnEmpty = e.target === e.target.getStage();
        if (clickedOnEmpty) {
          selectShape(null);
        }
      }}
    >
      <Layer>
        {circles.map((circ, i) => {
          return (
            <Circ
              key={i}
              shapeProps={circ}
              isSelected={circ.id === selectedId}
              onSelect={() => {
                selectShape(circ.id);
              }}
              onChange={newAttrs => {
                const circs = circles.slice();
                circs[i] = newAttrs;
                setCircles(circs);
              }}
            />
          );
        })}
      </Layer>
    </Stage>
  );
};

export default App;
```

In the code above, we have the `Circ` component that has the shape, the `Circle`, and the `Transformer` component as we described above.

The `onDragEnd` handler enables the dragging, as we did in the earlier drag and drop example. The `onTransformEnd` handler has the function to resize the shape to the new width and height after the user is done dragging.

The `Transformer` component has handles for resizing the circle, which is on when the `isSelected` prop is set to `true`. We set that when we click the shape in the `onSelect` handler of `App`. `onSelect` is run when a circle is clicked, which is then used to set `isSelected` for the circle that’s clicked to `true`.

## Basic animation

We can add some basic animation effects for actions like dragging shapes around by scaling the dimensions by random factors.

For example, we can write the following code to do that:

```jsx
import React from "react";
import { Stage, Layer, Circle } from "react-konva";

export default function App() {
  const circ = React.useRef();
  const changeSize = () => {
    circ.current.to({
      scaleX: Math.random() + 0.9,
      scaleY: Math.random() + 0.8,
      duration: 0.2
    });
  };

  return (
    <Stage width={window.innerWidth} height={window.innerHeight}>
      <Layer>
        <Circle
          x={100}
          y={100}
          width={100}
          height={100}
          fill="red"
          shadowBlur={5}
          draggable
          ref={circ}
          onDragStart={changeSize}
          onDragEnd={changeSize}
        />
      </Layer>
    </Stage>
  );
}
```

In the code above, we have the `changeSize` function, which is called when either the `dragstart` or `dragend` events are triggered.

Note that we have to use use the `current` property to get the DOM object to call the `to` method.

## Conclusion

With React Konva, we can draw many kinds of shapes with much less effort than using plain JavaScript. We can also add features to let users transform shapes and drag shapes around with ease. This isn’t available in the standard JavaScript canvas library.

Even better, we can add `Transformers` to let users change the size of the shape like they do in photo editors. Adding images with effects is also easy with the built-in `Image` component and filters.

* * * 
## Full visibility into production React apps 

Debugging React applications can be difficult, especially when users experience issues that are difficult to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, [try LogRocket.](https://www2.logrocket.com/react-performance-monitoring)

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/eq752g8qhbffxt3hp9t4.png)

[LogRocket](https://www2.logrocket.com/react-performance-monitoring) is like a DVR for web apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your React apps — [start monitoring for free.](https://www2.logrocket.com/react-performance-monitoring)
* * *

The post [Guide to canvas manipulation with React Konva](https://blog.logrocket.com/canvas-manipulation-react-konva/) appeared first on [LogRocket Blog](https://blog.logrocket.com).