Vue Konva是一个JavaScript库,用于使用Vue绘制复杂的画布图形。
它提供了对Konva框架的声明性和反应性绑定。
所有vue-konva组件都对应于同名的KONVA组件,前缀为“ V-”。 KONVA对象可用的所有参数都可以添加为相应的VUE-KONVA组件的配置中的配置。
官网链接konva
中文链接:konva
【v矩形、v圆形、v椭圆、v线、v图像、v文本、v文本路径、v星形、v标签、v路径、
v规则多边形】。
v-rect, v-circle, v-ellipse, v-line, v-image, v-text, v-text-path, v-star, v-label, v-path, v-regular-polygon.
您也可以创建自定义形状。
pnpm install vue-konva konva --save
import { createApp } from 'vue';
import App from './App.vue';
import VueKonva from 'vue-konva';
const app = createApp(App);
app.use(VueKonva);
app.mount('#app');
<template>
<v-stage :config="configKonva">
<v-layer>
<v-circle :config="configCircle"></v-circle>
</v-layer>
</v-stage>
</template>
<script>
export default {
data() {
return {
configKonva: {
width: 200,
height: 200
},
configCircle: {
x: 100,
y: 100,
radius: 70,
fill: "red",
stroke: "black",
strokeWidth: 4
}
};
}
};
<template>
<div>
<v-stage
ref="stage"
:config="configKonva"
@dragstart="handleDragstart"
@dragend="handleDragend"
>
<v-layer ref="layer">
<v-star
v-for="item in list"
:key="item.id"
:config="{
x: item.x,
y: item.y,
rotation: item.rotation,
id: item.id,
numPoints: 5,
innerRadius: 30,
outerRadius: 50, fill: '#89b717',
opacity: 0.8,
draggable: true,
scaleX: dragItemId === item.id ? item.scale * 1.2 : item.scale,
scaleY: dragItemId === item.id ? item.scale * 1.2 : item.scale,
shadowColor: 'black',
shadowBlur: 10,
shadowOffsetX: dragItemId === item.id ? 15 : 5,
shadowOffsetY: dragItemId === item.id ? 15 : 5,
shadowOpacity: 0.6
}"
></v-star>
</v-layer>
</v-stage>
</div>
</template>
<script>
const width = window.innerWidth;
const height = window.innerHeight;
export default {
data() {
return {
list: [],
dragItemId: null,
configKonva: {
width: width,
height: height
}
};
},
methods: {
handleDragstart(e) {
// save drag element:
this.dragItemId = e.target.id();
// move current element to the top:
const item = this.list.find(i => i.id === this.dragItemId);
const index = this.list.indexOf(item);
this.list.splice(index, 1);
this.list.push(item);
},
handleDragend(e) {
this.dragItemId = null;
}
},
mounted() {
for (let n = 0; n < 30; n++) {
this.list.push({
id: Math.round(Math.random() * 10000).toString(),
x: Math.random() * width,
y: Math.random() * height,
rotation: Math.random() * 180,
scale: Math.random()
});
}
}
};
</script>
<style>
body {
margin: 0;
padding: 0;
}
</style>
组件模板中的引用使用vue-konva,您可以轻松监听用户输入事件(click, dblclick, mouseover, tap, dbltap, touchstart, etc…)和拖放事件(dragstart、dragmove、dragend)。
1.鼠标悬停 mouseover
2.触摸启动 etc
3.双击事件 dbclick
4.键盘事件 tab
5.自定义事件 emit(“change”) 也就是子组件给父组件传递一个事件。
有关事件的完整列表,请查看on()方法文档。
<template>
<v-stage ref="stage" :config="stageSize">
<v-layer ref="layer">
<v-regular-polygon
@mousemove="handleMouseMove"
@mouseout="handleMouseOut"
:config="{
x: 80,
y: 120,
sides: 3,
radius: 80,
fill: '#00D2FF',
stroke: 'black',
strokeWidth: 4
}"
/>
<v-text ref="text" :config="{
x: 10,
y: 10,
fontFamily: 'Calibri',
fontSize: 24,
text: text,
fill: 'black'
}" />
</v-layer>
</v-stage>
</template>
<script>
const width = window.innerWidth;
const height = window.innerHeight;
export default {
data() {
return {
stageSize: {
width: width,
height: height
},
text: ''
};
},
methods: {
writeMessage(message) {
this.text = message;
},
handleMouseOut(event) {
this.writeMessage('Mouseout triangle');
},
handleMouseMove(event) {
const mousePos = this.$refs.stage.getNode().getPointerPosition();
const x = mousePos.x - 190;
const y = mousePos.y - 40;
this.writeMessage('x: ' + x + ', y: ' + y);
}
}
};
</script>
对于图像,您需要手动创建原生window.Image实例或画布元素,并将其用作v-Image组件的图像属性。
<template>
<v-stage ref="stage" :config="stageSize">
<v-layer ref="layer">
<v-image :config="{
image: image
}"/>
</v-layer>
</v-stage>
</template>
<script>
const width = window.innerWidth;
const height = window.innerHeight;
export default {
data() {
return {
stageSize: {
width: width,
height: height
},
image: null
};
},
created() {
const image = new window.Image();
image.src = "https://konvajs.org/assets/yoda.jpg";
image.onload = () => {
// set image only when it is loaded
this.image = image;
};
}
};
</script>
要应用筛选器,您需要手动缓存Konva.Node。您可以通过created()方法来完成。
每次在updated()中更新节点的样式时,可能需要重新缓存节点。
说明:点击矩形查看更改
<template>
<v-stage ref="stage" :config="stageSize">
<v-layer ref="layer">
<v-rect
ref="rect"
@mousemove="handleMouseMove"
:config="{
filters: filters,
noise: 1,
x: 10,
y: 10,
width: 50,
height: 50,
fill: color,
shadowBlur: 10
}"
/>
</v-layer>
</v-stage>
</template>
<script>
const width = window.innerWidth;
const height = window.innerHeight;
import Konva from 'konva';
export default {
data() {
return {
stageSize: {
width: width,
height: height
},
color: 'green',
filters: [Konva.Filters.Noise]
};
},
methods: {
handleMouseMove() {
this.color = Konva.Util.getRandomColor();
}
},
mounted() {
const rectNode = this.$refs.rect.getNode();
rectNode.cache();
},
updated() {
// recache
const rectNode = this.$refs.rect.getNode();
// may need to redraw layer manually
rectNode.cache();
}
};
</script>
Pure Konva具有特殊的mechanizm,可以使用node.toJSON()和node.create(json)函数保存/加载整个画布阶段。
请参阅演示。
但是,如果您正在使用vue-konva,我们不建议使用这些方法。在vue-konva中,你应该在你的vue组件中定义应用程序的状态。该状态映射到具有模板的节点中。要保存/加载整个阶段,您只需要保存/加载应用程序的状态,而不需要保存Konva内部和节点。
<template>
<div>
Click on canvas to create a cirlce.
<a href=".">Reload the page</a>. Circles should stay here.
<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: 'red',
}"></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>
要对画布上的任何节点启用拖放,您只需要将draggable:true属性传递到组件中。
当你拖放形状时,建议将其位置保存到你的应用商店中。为此,您可以使用dragmove和dragend事件。
<template>
<v-stage ref="stage" :config="stageSize">
<v-layer ref="layer">
<v-text
@dragstart="handleDragStart"
@dragend="handleDragEnd"
:config="{
text: 'Draggable Text',
x: 50,
y: 50,
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: width,
height: height
},
isDragging: false
};
},
methods: {
handleDragStart() {
this.isDragging = true;
},
handleDragEnd() {
this.isDragging = false;
}
}
};
</script>
目前还没有好的纯声明性“vue方式”来使用Transformer工具。
但是,您仍然可以将它与一些对Konva节点的小型手动请求一起使用。
它会很好地工作。
想法:您需要创建Konva.Transformer节点,并手动将其附加到所需的节点中。
说明:点击形状进行选择。
<template>
<v-stage
ref="stage"
:config="stageSize"
@mousedown="handleStageMouseDown"
@touchstart="handleStageMouseDown"
>
<v-layer ref="layer">
<v-rect
v-for="item in rectangles"
: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,
},
rectangles: [
{
rotation: 0,
x: 10,
y: 10,
width: 100,
height: 100,
scaleX: 1,
scaleY: 1,
fill: 'red',
name: 'rect1',
draggable: true,
},
{
rotation: 0,
x: 150,
y: 150,
width: 100,
height: 100,
scaleX: 1,
scaleY: 1,
fill: 'green',
name: 'rect2',
draggable: true,
},
],
selectedShapeName: '',
};
},
methods: {
handleTransformEnd(e) {
// shape is transformed, let us save new attrs back to the node
// find element in our state
const rect = this.rectangles.find(
(r) => r.name === this.selectedShapeName
);
// update the state
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();
// change fill
rect.fill = Konva.Util.getRandomColor();
},
handleStageMouseDown(e) {
// clicked on stage - clear selection
if (e.target === e.target.getStage()) {
this.selectedShapeName = '';
this.updateTransformer();
return;
}
// clicked on transformer - do nothing
const clickedOnTransformer =
e.target.getParent().className === 'Transformer';
if (clickedOnTransformer) {
return;
}
// find clicked rect by its name
const name = e.target.name();
const rect = this.rectangles.find((r) => r.name === name);
if (rect) {
this.selectedShapeName = name;
} else {
this.selectedShapeName = '';
}
this.updateTransformer();
},
updateTransformer() {
// here we need to manually attach or detach Transformer node
const transformerNode = this.$refs.transformer.getNode();
const stage = transformerNode.getStage();
const { selectedShapeName } = this;
const selectedNode = stage.findOne('.' + selectedShapeName);
// do nothing if selected node is already attached
if (selectedNode === transformerNode.node()) {
return;
}
if (selectedNode) {
// attach to another node
transformerNode.nodes([selectedNode]);
} else {
// remove transformer
transformerNode.nodes([]);
}
},
},
};
</script>
Konva本身有两种动画方法Tween和Animation。您可以手动将这两种方法应用于节点。
对于简单的用例,我们建议使用node.to()方法。
说明:试着移动一个矩形。
<template>
<v-stage ref="stage" :config="stageSize">
<v-layer ref="layer">
<v-rect
ref="rect"
@dragstart="changeSize"
@dragend="changeSize"
:config="{
width: 50,
height: 50,
fill: 'green',
draggable: true
}"
/>
<v-regular-polygon
ref="hexagon"
:config="{
x: 200,
y: 200,
sides: 6,
radius: 20,
fill: 'red',
stroke: 'black',
strokeWidth: 4
}"
/>
</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
}
};
},
methods: {
changeSize(e) {
// to() is a method of `Konva.Node` instances
e.target.to({
scaleX: Math.random() + 0.8,
scaleY: Math.random() + 0.8,
duration: 0.2
});
}
},
mounted() {
const vm = this;
const amplitude = 100;
const period = 5000;
// in ms
const centerX = vm.$refs.stage.getNode().getWidth() / 2;
const hexagon = this.$refs.hexagon.getNode();
// example of Konva.Animation
const anim = new Konva.Animation(function(frame) {
hexagon.setX(
amplitude * Math.sin((frame.time * 2 * Math.PI) / period) + centerX
);
}, hexagon.getLayer());
anim.start();
}
};
</script>
如果你想在vue应用程序中缓存一个节点,你需要访问Konva节点并使用node.cache()函数。
要访问节点,可以使用references和component.getNode()方法:
说明:试着拖动整个舞台。然后使用缓存的组重试。
你应该看到更好的表现。
<template>
<v-stage ref="stage" :config="stageSize">
<v-layer ref="layer">
<v-rect
ref="rect"
@dragstart="changeSize"
@dragend="changeSize"
:config="{
width: 50,
height: 50,
fill: 'green',
draggable: true
}"
/>
<v-regular-polygon
ref="hexagon"
:config="{
x: 200,
y: 200,
sides: 6,
radius: 20,
fill: 'red',
stroke: 'black',
strokeWidth: 4
}"
/>
</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
}
};
},
methods: {
changeSize(e) {
// to() is a method of `Konva.Node` instances
e.target.to({
scaleX: Math.random() + 0.8,
scaleY: Math.random() + 0.8,
duration: 0.2
});
}
},
mounted() {
const vm = this;
const amplitude = 100;
const period = 5000;
// in ms
const centerX = vm.$refs.stage.getNode().getWidth() / 2;
const hexagon = this.$refs.hexagon.getNode();
// example of Konva.Animation
const anim = new Konva.Animation(function(frame) {
hexagon.setX(
amplitude * Math.sin((frame.time * 2 * Math.PI) / period) + centerX
);
}, hexagon.getLayer());
anim.start();
}
};
</script>
当你直接使用Konva时,你有很多方法可以改变节点的顺序,比如node.zIndex(5)、node.moveToTop()等。教程。
但在使用vue框架时,不建议使用这些方法。
vue-konva正试图按照您在<template>中描述的节点顺序进行操作。因此,您只需要正确更新应用程序的数据,就可以使中的组件保持正确的顺序,而无需手动更改zIndex。
不要将zIndex用于画布组件。
说明:试着拖动一个圆圈。看看它是如何到达顶端的。我们通过操纵应用程序的数据来做到这一点,以便中的圆圈保持正确的顺序。
<template>
<div>
<v-stage ref="stage" :config="configKonva">
<v-layer ref="layer">
<v-circle
v-for="item in items"
:key="item.id"
:config="item"
@dragstart="handleDragstart"
@dragend="handleDragend"
></v-circle>
</v-layer>
</v-stage>
</div>
</template>
<script>
import Konva from "konva";
const width = window.innerWidth;
const height = window.innerHeight;
function generateItems() {
const items = [];
for (let i = 0; i < 10; i++) {
items.push({
x: Math.random() * width,
y: Math.random() * height,
radius: 50,
id: "node-" + i,
fill: Konva.Util.getRandomColor(),
draggable: true,
});
}
return items;
}
export default {
data() {
return {
items: [],
dragItemId: null,
configKonva: {
width: width,
height: height,
},
};
},
methods: {
handleDragstart(e) {
// save drag element:
this.dragItemId = e.target.id();
// move current element to the top, by rearranging the items array:
const item = this.items.find((i) => i.id === this.dragItemId);
const index = this.items.indexOf(item);
this.items.splice(index, 1);
this.items.push(item);
},
handleDragend(e) {
this.dragItemId = null;
},
},
mounted() {
this.items = generateItems();
},
};
</script>
<style>
body {
margin: 0;
padding: 0;
}
</style>