We can make working with the HTML canvas easier in Vue apps with the Vue Konva library.
In this article, we’ll take a look at how to use Vue Konva to make working with the HTML canvas easier in a Vue app.
Saving and Loading Canvas
We can save the canvas content easily so that it’ll be kept when we reload the page.
For example, we can write:
<template>
<div>
<a href=".">Reload the page</a>.
<v-stage ref="stage" :config="stageSize" @click="handleClick">
<v-layer ref="layer">
<v-circle
v-for="item in list"
:key="item.id"
:config="{
x: item.x,
y: item.y,
radius: 50,
fill: 'green',
}"
></v-circle>
</v-layer>
<v-layer ref="dragLayer"></v-layer>
</v-stage>
</div>
</template>
<script>
const width = window.innerWidth;
const height = window.innerHeight;
export default {
data() {
return {
list: [{ x: 100, y: 100, radius: 50, fill: "blue" }],
stageSize: {
width: width,
height: height,
},
};
},
methods: {
handleClick(evt) {
const stage = evt.target.getStage();
const pos = stage.getPointerPosition();
this.list.push(pos);
this.save();
},
load() {
const data = localStorage.getItem("storage") || "[]";
this.list = JSON.parse(data);
},
save() {
localStorage.setItem("storage", JSON.stringify(this.list));
},
},
mounted() {
this.load();
},
};
</script>
We have the handleClick
method that adds a circle when we click on the stage.
The list
reactive property has a list of circles to render.
Then in the save
method, we saver the canvas by saving the list
value into local storage.
Then we can load that in the load
method by parsing it.
The list
reactive property is loaded with v-for
in the template to recreate the circles.
Drag and Drop
We can add drag and drop easily with Vue Konva.
All we have to do is listen to the dragstart
and dragend
events in our shapes.
For example, we can write:
<template>
<v-stage ref="stage" :config="stageSize">
<v-layer ref="layer">
<v-circle
@dragstart="handleDragStart"
@dragend="handleDragEnd"
:config="{
x: 200,
y: 200,
radius: 70,
draggable: true,
fill: isDragging ? 'green' : 'black',
}"
/>
</v-layer>
</v-stage>
</template>
<script>
const width = window.innerWidth;
const height = window.innerHeight;
export default {
data() {
return {
stageSize: {
width,
height,
},
isDragging: false,
};
},
methods: {
handleDragStart() {
this.isDragging = true;
},
handleDragEnd() {
this.isDragging = false;
},
},
};
</script>
We set the isDragging
reactive property when we’re dragging.
This changes the fill.
Also, we set the draggable
property to true
to make the circle draggable.
Resizing Shapes
We can resize shapes easily with the v-transformer
component.
For example, we can write:
<template>
<v-stage
ref="stage"
:config="stageSize"
@mousedown="handleStageMouseDown"
@touchstart="handleStageMouseDown"
>
<v-layer ref="layer">
<v-circle
v-for="item in circles"
:key="item.id"
:config="item"
@transformend="handleTransformEnd"
/>
<v-transformer ref="transformer" />
</v-layer>
</v-stage>
</template>
<script>
import Konva from "konva";
const width = window.innerWidth;
const height = window.innerHeight;
export default {
data() {
return {
stageSize: {
width: width,
height: height,
},
circles: [
{
rotation: 0,
x: 60,
y: 60,
width: 100,
height: 100,
scaleX: 1,
scaleY: 1,
fill: "red",
name: "circ1",
draggable: true,
},
{
rotation: 0,
x: 150,
y: 150,
width: 100,
height: 100,
scaleX: 1,
scaleY: 1,
fill: "green",
name: "circ2",
draggable: true,
},
],
selectedShapeName: "",
};
},
methods: {
handleTransformEnd(e) {
const rect = this.circles.find((r) => r.name === this.selectedShapeName);
rect.x = e.target.x();
rect.y = e.target.y();
rect.rotation = e.target.rotation();
rect.scaleX = e.target.scaleX();
rect.scaleY = e.target.scaleY();
rect.fill = Konva.Util.getRandomColor();
},
handleStageMouseDown(e) {
if (e.target === e.target.getStage()) {
this.selectedShapeName = "";
this.updateTransformer();
return;
}
const clickedOnTransformer =
e.target.getParent().className === "Transformer";
if (clickedOnTransformer) {
return;
}
const name = e.target.name();
const rect = this.circles.find((r) => r.name === name);
if (rect) {
this.selectedShapeName = name;
} else {
this.selectedShapeName = "";
}
this.updateTransformer();
},
updateTransformer() {
const transformerNode = this.$refs.transformer.getNode();
const stage = transformerNode.getStage();
const { selectedShapeName } = this;
const selectedNode = stage.findOne(`.${selectedShapeName}`);
if (selectedNode === transformerNode.node()) {
return;
}
if (selectedNode) {
transformerNode.nodes([selectedNode]);
} else {
transformerNode.nodes([]);
}
transformerNode.getLayer().batchDraw();
},
},
};
</script>
We start with the mousedown
handler, which is called when we click on the shape.
The handleStageMouseDown
method is the mousedown
handler.
We get the state with the getStage
method.
If we found the stage, then we call updateTransformer
to remove any selections
After that, we get the state by the name.
And then we call updateTransformer
to select the shape.
Then when we drag the handles, the handleTransformEnd
method is called.
We get all the event data from the e
parameter and update the object that’s found by its name.
Conclusion
We can save and load canvas and transform shapes with Vue Konva.