HTML5 Canvas How to avoid Memory leaks Tip
Deleting shapes
There are two very close methods remove()
and destroy()
. If you need to completely delete a node you should destroy()
it. The destroy()
method deletes all references to node from the KonvaJS engine. If you are going to reuse a node you should remove()
it then later you can add it again to any container.
Tweening
When you are using Konva.Tween
instance you have to destroy it after usage.
Here's a demo showing proper memory management:
- Vanilla
- React
- Vue
import Konva from 'konva'; const stage = new Konva.Stage({ container: 'container', width: window.innerWidth, height: window.innerHeight, }); const layer = new Konva.Layer(); stage.add(layer); // Create circle const circle = new Konva.Circle({ x: 100, y: 100, radius: 30, fill: 'red' }); layer.add(circle); // Add buttons const addButton = document.createElement('button'); addButton.textContent = 'Add Circle'; document.body.appendChild(addButton); const removeButton = document.createElement('button'); removeButton.textContent = 'Remove Circle'; document.body.appendChild(removeButton); const animateButton = document.createElement('button'); animateButton.textContent = 'Animate'; document.body.appendChild(animateButton); // Handle adding/removing addButton.addEventListener('click', () => { layer.add(circle); layer.draw(); }); removeButton.addEventListener('click', () => { // Just remove from layer, can be added back circle.remove(); layer.draw(); }); animateButton.addEventListener('click', () => { // Using to() method which auto-destroys the tween circle.to({ x: Math.random() * stage.width(), y: Math.random() * stage.height(), duration: 1 }); // If using Tween directly, make sure to destroy it const tween = new Konva.Tween({ node: circle, rotation: 360, duration: 1, onFinish: function() { // Clean up the tween tween.destroy(); } }).play(); });
import { Stage, Layer, Circle } from 'react-konva'; import { useState } from 'react'; const App = () => { const [isVisible, setIsVisible] = useState(true); const [position, setPosition] = useState({ x: 100, y: 100 }); const [rotation, setRotation] = useState(0); const handleAdd = () => { setIsVisible(true); }; const handleRemove = () => { setIsVisible(false); }; const handleAnimate = () => { // Update position setPosition({ x: Math.random() * window.innerWidth, y: Math.random() * window.innerHeight }); // Update rotation setRotation(rotation + 360); }; return ( <div> <button onClick={handleAdd} style={{ marginRight: '10px' }}>Add Circle</button> <button onClick={handleRemove} style={{ marginRight: '10px' }}>Remove Circle</button> <button onClick={handleAnimate}>Animate</button> <Stage width={window.innerWidth} height={window.innerHeight}> <Layer> {isVisible && ( <Circle x={position.x} y={position.y} radius={30} fill="red" rotation={rotation} /> )} </Layer> </Stage> </div> ); }; export default App;
<template> <div> <button @click="handleAdd" style="margin-right: 10px">Add Circle</button> <button @click="handleRemove" style="margin-right: 10px">Remove Circle</button> <button @click="handleAnimate">Animate</button> <v-stage :config="stageSize"> <v-layer> <v-circle v-if="isVisible" :config="circleConfig" /> </v-layer> </v-stage> </div> </template> <script setup> import { ref } from 'vue'; const stageSize = { width: window.innerWidth, height: window.innerHeight }; const isVisible = ref(true); const position = ref({ x: 100, y: 100 }); const rotation = ref(0); const circleConfig = ref({ x: position.value.x, y: position.value.y, radius: 30, fill: 'red', rotation: rotation.value }); const handleAdd = () => { isVisible.value = true; }; const handleRemove = () => { isVisible.value = false; }; const handleAnimate = () => { // Update position position.value = { x: Math.random() * window.innerWidth, y: Math.random() * window.innerHeight }; // Update rotation rotation.value += 360; // Update config circleConfig.value = { ...circleConfig.value, x: position.value.x, y: position.value.y, rotation: rotation.value }; }; </script>