HTML5 Canvas Force Update Tutorial
Konva.Transformer automatically tracks properties of attached nodes.
So it will adopt its own properties automatically.
Since Konva 10.7.1, the transformer also follows changes inside an attached Konva.Group: a child that changes size, or a child that is added or removed. In older versions, you had to call forceUpdate for these changes, as the demo below does.
But Konva.Transformer can not detect a change that sets no attribute. For example, a Konva.Line points array that you mutate in place, or a custom getSelfRect() that depends on outside state. After such a change, call the forceUpdate method to reset the transforming tools.
Instructions: Click the button. See how transformer is changed.
- Vanilla
- React
- Vue
import Konva from 'konva';
const width = window.innerWidth;
const height = window.innerHeight;
const stage = new Konva.Stage({
container: 'container',
width: width,
height: height,
});
const layer = new Konva.Layer();
stage.add(layer);
const group = new Konva.Group({
x: 50,
y: 50,
draggable: true,
});
layer.add(group);
const text = new Konva.Text({
text: 'Some text here',
fontSize: 24,
});
group.add(text);
const rect = new Konva.Rect({
width: text.width(),
height: text.height(),
fill: 'yellow',
});
group.add(rect);
// add the shape to the layer
rect.moveToBottom();
const tr = new Konva.Transformer({
nodes: [group],
padding: 5,
// enable only one anchor
enabledAnchors: ['middle-left', 'middle-right'],
});
layer.add(tr);
const button = document.createElement('button');
button.innerHTML = 'Change text';
document.body.appendChild(button);
button.addEventListener('click', () => {
text.text('Something else is here');
rect.width(text.width());
// we need to update transformer manually
tr.forceUpdate();
});
import { Stage, Layer, Text, Rect, Group, Transformer } from 'react-konva';
import { useState, useRef, useLayoutEffect } from 'react';
const App = () => {
const [text, setText] = useState('Some text here');
const [groupPosition, setGroupPosition] = useState({ x: 50, y: 50 });
const groupRef = useRef();
const textRef = useRef();
const rectRef = useRef();
const trRef = useRef();
const handleClick = () => {
setText('Something else is here');
};
useLayoutEffect(() => {
const transformer = trRef.current;
const label = textRef.current;
const background = rectRef.current;
if (!transformer || !label || !background) return;
background.size({ width: label.width(), height: label.height() });
transformer.nodes([groupRef.current]);
transformer.forceUpdate();
}, [text]);
return (
<>
<button onClick={handleClick}>Change text</button>
<Stage width={window.innerWidth} height={window.innerHeight}>
<Layer>
<Group
x={groupPosition.x}
y={groupPosition.y}
draggable
ref={groupRef}
onDragEnd={(event) => setGroupPosition(event.target.position())}
onTransformEnd={(event) => setGroupPosition(event.target.position())}
>
<Rect
ref={rectRef}
width={0}
height={0}
fill="yellow"
/>
<Text
ref={textRef}
text={text}
fontSize={24}
/>
</Group>
<Transformer
ref={trRef}
padding={5}
enabledAnchors={['middle-left', 'middle-right']}
/>
</Layer>
</Stage>
</>
);
};
export default App;
<template>
<div>
<button @click="changeText">Change text</button>
<v-stage :config="stageSize">
<v-layer>
<v-group :config="groupConfig" ref="groupRef">
<v-rect :config="rectConfig" ref="rectRef" />
<v-text :config="textConfig" ref="textRef" />
</v-group>
<v-transformer :config="transformerConfig" ref="transformerRef" />
</v-layer>
</v-stage>
</div>
</template>
<script setup>
import { ref, computed, nextTick, onMounted } from 'vue';
const stageSize = {
width: window.innerWidth,
height: window.innerHeight
};
const text = ref('Some text here');
const groupRef = ref(null);
const textRef = ref(null);
const rectRef = ref(null);
const transformerRef = ref(null);
const groupConfig = {
x: 50,
y: 50,
draggable: true
};
const textConfig = computed(() => ({
text: text.value,
fontSize: 24
}));
const rectConfig = {
width: 0,
height: 0,
fill: 'yellow'
};
const transformerConfig = {
padding: 5,
enabledAnchors: ['middle-left', 'middle-right']
};
const updateTransformer = () => {
const label = textRef.value.getNode();
const background = rectRef.value.getNode();
const transformer = transformerRef.value.getNode();
background.size({ width: label.width(), height: label.height() });
transformer.forceUpdate();
};
const changeText = async () => {
text.value = 'Something else is here';
await nextTick();
updateTransformer();
};
onMounted(() => {
if (transformerRef.value && groupRef.value) {
transformerRef.value.getNode().nodes([groupRef.value.getNode()]);
updateTransformer();
}
});
</script>