I need to scale a text in both directions but also be able to crop it with ‘middle-right’ or ‘middle-left’ anchor.
The code is below (which is basically modified version of the official example), but there are couple of issues:
- Transform frame shakes and glitches when I try to minimize by middle anchors
- It randomly expands to thousands px width
- Text not always scales correctly
I’m looking for better way to implement it.
var width = window.innerWidth;
var height = window.innerHeight;
var stage = new Konva.Stage({
container: 'container',
width: width,
height: height,
});
var layer = new Konva.Layer();
stage.add(layer);
// Function to create text nodes
function createTextNode(text, fontSize, x, y) {
var textNode = new Konva.Text({
text: text,
fontSize: fontSize,
draggable: true,
wrap: 'none',
x: x,
y: y
});
var tr = new Konva.Transformer({
nodes: [textNode],
rotateEnabled: false,
flipEnabled: false,
anchorSize: 8,
enabledAnchors: [
'top-left',
'top-right',
'middle-right',
'middle-left',
'bottom-left',
'bottom-right'
],
// set minimum width of text
boundBoxFunc: function(oldBox, newBox) {
newBox.width = Math.max(40 * textNode.scaleX(), newBox.width);
return newBox;
}
});
layer.add(textNode);
textNode.on('transform', function(e) {
textNode.width(tr.width() / textNode.scaleY());
})
layer.add(tr);
tr.hide();
textNode.on('click', function(e) {
tr.show();
});
textNode.on('dblclick dbltap', () => {
// hide text node and transformer:
textNode.hide();
tr.hide();
// create textarea over canvas with absolute position
// first we need to find position for textarea
// how to find it?
// at first lets find position of text node relative to the stage:
var textPosition = textNode.absolutePosition();
// so position of textarea will be the sum of positions above:
var areaPosition = {
x: stage.container().offsetLeft + textPosition.x,
y: stage.container().offsetTop + textPosition.y,
};
// create textarea and style it
var textarea = document.createElement('textarea');
document.body.appendChild(textarea);
// apply many styles to match text on canvas as close as possible
// remember that text rendering on canvas and on the textarea can be different
// and sometimes it is hard to make it 100% the same. But we will try...
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() * textNode.scaleY() + '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();
textarea.style.whiteSpace = 'nowrap';
rotation = textNode.rotation();
var transform = '';
if (rotation) {
transform += 'rotateZ(' + rotation + 'deg)';
}
var px = 0;
// also we need to slightly move textarea on firefox
// because it jumps a bit
var isFirefox =
navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
if (isFirefox) {
px += 2 + Math.round(textNode.fontSize() / 20);
}
transform += 'translateY(-' + px + 'px)';
textarea.style.transform = transform;
// reset height
textarea.style.height = 'auto';
// after browsers resized it we can set actual value
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) {
// set width for placeholder
newWidth = textNode.placeholder.length * textNode.fontSize();
}
// some extra fixes on different browsers
var isSafari = /^((?!chrome|android).)*safari/i.test(
navigator.userAgent
);
var isFirefox =
navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
if (isSafari || isFirefox) {
newWidth = Math.ceil(newWidth);
}
var isEdge =
document.documentMode || /Edge/.test(navigator.userAgent);
if (isEdge) {
newWidth += 1;
}
textarea.style.width = newWidth + 'px';
}
textarea.addEventListener('keydown', function(e) {
// hide on enter and esc
if (e.keyCode === 13 || e.keyCode === 27) {
textNode.text(textarea.value.replaceAll('n', ' '));
removeTextarea();
}
});
textarea.addEventListener('keydown', function(e) {
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);
});
});
}
// Call the function to create text nodes
createTextNode('Text 1', 20, 50, 50);
createTextNode('Text 2', 20, 150, 150);
createTextNode('Text 3', 20, 250, 250);
body {
margin: 0;
padding: 0;
background-color: #f0f0f0;
}
<script src="https://unpkg.com/[email protected]/konva.min.js"></script>
<div id="container"></div>