Skip to main content

How to show rich html on canvas with Konva

How to show complex styles (like bold) and enable rich text editing features?

Canvas's text API is very limited. Konva.Text allows you to add many different styles, support multiline text, etc. But at the current moment it has limitations. You can't use different styles for different parts of Konva.Text. For that case you have to use several Konva.Text instances.

If you want show complex styles on canvas we can do a hacky workaround. The idea is simple:

  1. Create DOM element and put styled text in it
  2. Convert DOM element into image with html2canvas.
  3. Use that image for Konva.Image.

Instructions: Try to type and format text in the editor above. The formatted text will be rendered on the canvas below as an image. You can drag the rendered text around.

import Konva from 'konva';

// First we need to load Quill and html2canvas
const loadScript = (src) => {
  return new Promise((resolve, reject) => {
    const script = document.createElement('script');
    script.src = src;
    script.onload = resolve;
    script.onerror = reject;
    document.head.appendChild(script);
  });
};

const loadCSS = (href) => {
  const link = document.createElement('link');
  link.rel = 'stylesheet';
  link.href = href;
  document.head.appendChild(link);
};

// Create editor container
const editorContainer = document.createElement('div');
editorContainer.id = 'editor-container';
editorContainer.style.height = '80px';
editorContainer.innerHTML = `
  That is <u>some</u> <span style="color: red"> styled text</span> on
  <strong>canvas</strong>!
  <h2>What do you think about it?</h2>
`;
document.body.appendChild(editorContainer);

// Load dependencies
Promise.all([
  loadScript('https://cdn.quilljs.com/1.3.6/quill.js'),
  loadScript('https://html2canvas.hertzen.com/dist/html2canvas.min.js'),
]).then(() => {
  loadCSS('https://cdn.quilljs.com/1.3.6/quill.snow.css');

  const quill = new Quill('#editor-container', {
    modules: {
      toolbar: [
        [{ header: [1, 2, false] }],
        ['bold', 'italic', 'underline'],
        ['image', 'code-block'],
      ],
    },
    placeholder: 'Compose an epic...',
    theme: 'snow',
  });

  const stage = new Konva.Stage({
    container: 'container',
    width: window.innerWidth,
    height: 200,
  });

  const layer = new Konva.Layer();
  stage.add(layer);

  const shape = new Konva.Image({
    x: 10,
    y: 10,
    draggable: true,
    stroke: 'red',
    scaleX: 1 / window.devicePixelRatio,
    scaleY: 1 / window.devicePixelRatio,
  });
  layer.add(shape);

  function renderText() {
    // convert DOM into image
    html2canvas(document.querySelector('.ql-editor'), {
      backgroundColor: 'rgba(0,0,0,0)',
    }).then((canvas) => {
      // show it inside Konva.Image
      shape.image(canvas);
    });
  }

  // batch updates, so we don't render text too frequently
  let timeout = null;
  function requestTextUpdate() {
    if (timeout) {
      return;
    }
    timeout = setTimeout(function () {
      timeout = null;
      renderText();
    }, 500);
  }

  // render text on all changes
  quill.on('text-change', requestTextUpdate);
  // make initial rendering
  renderText();
});