Skip to main content

Text editing in HTML5 canvas with Konva

User can't directly edit Konva.Text content for many reasons. In fact canvas API is not designed for such purpose. It is possible to emulate text editing on canvas (by drawing blinking cursor, emulate selection, etc). Konva has not support for such case. We recommend to edit the user input outside of your canvas with native DOM elements such as input or textarea.

If you want to enable full rich text editing features see Rich Text Demo.

Instructions: Double click on text to edit it. Type something. Press Enter or click outside to save changes.

import Konva from 'konva';

Konva._fixTextRendering = true;

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

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

const textNode = new Konva.Text({
text: 'Some text here',
x: 50,
y: 80,
fontSize: 20,
draggable: true,
width: 200,
});

layer.add(textNode);

const tr = new Konva.Transformer({
node: textNode,
enabledAnchors: ['middle-left', 'middle-right'],
boundBoxFunc: function (oldBox, newBox) {
newBox.width = Math.max(30, newBox.width);
return newBox;
},
});

textNode.on('transform', function () {
textNode.setAttrs({
width: textNode.width() * textNode.scaleX(),
scaleX: 1,
});
});

layer.add(tr);

textNode.on('dblclick dbltap', () => {
textNode.hide();
tr.hide();

const textPosition = textNode.absolutePosition();
const stageBox = stage.container().getBoundingClientRect();

const areaPosition = {
x: stageBox.left + textPosition.x,
y: stageBox.top + textPosition.y,
};

const textarea = document.createElement('textarea');
document.body.appendChild(textarea);

textarea.value = textNode.text();
textarea.style.position = 'absolute';
textarea.style.top = areaPosition.y + 'px';
textarea.style.left = areaPosition.x + 'px';
textarea.style.width = textNode.width() - textNode.padding() * 2 + 'px';
textarea.style.height = textNode.height() - textNode.padding() * 2 + 5 + 'px';
textarea.style.fontSize = textNode.fontSize() + 'px';
textarea.style.border = 'none';
textarea.style.padding = '0px';
textarea.style.margin = '0px';
textarea.style.overflow = 'hidden';
textarea.style.background = 'none';
textarea.style.outline = 'none';
textarea.style.resize = 'none';
textarea.style.lineHeight = textNode.lineHeight();
textarea.style.fontFamily = textNode.fontFamily();
textarea.style.transformOrigin = 'left top';
textarea.style.textAlign = textNode.align();
textarea.style.color = textNode.fill();

const rotation = textNode.rotation();
let transform = '';
if (rotation) {
transform += 'rotateZ(' + rotation + 'deg)';
}
transform += 'translateY(-' + 2 + 'px)';
textarea.style.transform = transform;

textarea.style.height = 'auto';
textarea.style.height = textarea.scrollHeight + 3 + 'px';

textarea.focus();

function removeTextarea() {
textarea.parentNode.removeChild(textarea);
window.removeEventListener('click', handleOutsideClick);
textNode.show();
tr.show();
tr.forceUpdate();
}

function setTextareaWidth(newWidth) {
if (!newWidth) {
newWidth = textNode.placeholder.length * textNode.fontSize();
}
textarea.style.width = newWidth + 'px';
}

textarea.addEventListener('keydown', function (e) {
if (e.keyCode === 13 && !e.shiftKey) {
textNode.text(textarea.value);
removeTextarea();
}
if (e.keyCode === 27) {
removeTextarea();
}
});

textarea.addEventListener('keydown', function () {
const scale = textNode.getAbsoluteScale().x;
setTextareaWidth(textNode.width() * scale);
textarea.style.height = 'auto';
textarea.style.height = textarea.scrollHeight + textNode.fontSize() + 'px';
});

function handleOutsideClick(e) {
if (e.target !== textarea) {
textNode.text(textarea.value);
removeTextarea();
}
}
setTimeout(() => {
window.addEventListener('click', handleOutsideClick);
});
});