Drawing on canvas inside a Draft js document

Fri, Apr 5, 2019 - 2 minute read

Let's experiment with canvas and draft js and make a note-taking/drawing app

This post is part of a series:
  1. Getting started with draft.js
  2. Getting started with draft.js plugins
  3. Draft-js - Saving data to the server
  4. Draft-js and Redux - the perfect couple
  5. Draft-js building search and replace functionality
  6. Best practices when working with draft js
  7. Drawing on canvas inside a Draft js document (current)

Say we want to add drawings into our rich text document, right there and then, with our mouse. Yep, that’s totally possible, and here’s how:

Our canvas will be a custom block. It needs to be of type 'atomic' since it isn’t text-based.

First we need a button or something that triggers the insertion of the canvas. When clicking said button we trigger a callback that inserts a custom block into our editor state object, let’s call this method insertCanvas. Since we’ll be inserting a block that isn’t text, we’ll need the insertAtomicBlock method.. Don’t worry, this’ll all make sense soon (hopefully)

I’m assuming here that we’re in a component where editorState is bound to this.state, and insertCanvas is a class method.

insertCanvas = () => {
  const { editorState } = this.state;
  let content = editorState.getCurrentContent();

  // Create IMMUTABLE entity of type CANVAS, content is a string that contains the data for our drawing, for starters this is empty
  content = content.createEntity(
    'CANVAS',
    'IMMUTABLE',
    { content: '' }
  )

  const entityKey = content.getLastCreatedEntityKey();

  this.setState({
    editorState: AtomicBlockUtils.insertAtomicBlock(
      editorState,
      entityKey,
      ' ',
    ),
  });
}

So in this method we need to add an atomic block, with an entity that describes our custom block (I’m calling the entity type 'CANVAS but you can call it anything).

In our component we’ll have a button that calls insertCanvas when clicked like so <button onClick={this.insertCanvas} />, the only thing that’s left to do now is to render the thing, we do that by using the blockRendererFn prop like this:

<Editor
...
blockRendererFn={(block) => {
    const { editorState } = this.state;
    const content = editorState.getCurrentContent();

    if (block.getType() === 'atomic') {
      const entityKey = block.getEntityAt(0);
      const entity = content.getEntity(entityKey);

      if (entity != null && entity.getType() === 'CANVAS') {
        return {
          component: () => <Canvas />,
        }
      }
    }
  }
}
/>

We first check if the block type is atomic and then if the entity type is 'CANVAS'. and then we render the canvas component, easy peasy, you could use a component like this one or write your own.

Finally, you might want to also save the drawings you’ve made, I’ve added that extra little bit of code and made it available here, you can also try this in a sandbox here

Wow you reached the end of the page! Could that mean that you're interested in working together? Please get in touch early to avoid disappointment! My availabilty tends to fill up quickly.

Chat with me here or hit me up on Twitter