Vue与Konva:解锁Canvas绘图的无限可能
前言
在现代Web开发中,动态、交互式的图形界面已成为提升用户体验的关键要素。Vue.js,作为一款轻量级且高效的前端框架,凭借其响应式数据绑定和组件化开发模式,赢得了众多开发者的青睐。而当Vue.js邂逅Konva.js,两者结合产生的化学反应更是令人惊叹。今天,就让我们一同深入探索如何利用Vue.js和Konva.js绘制复杂且交互丰富的Canvas图形。
一、Vue Konva简介
Vue Konva是一款基于Vue.js的JavaScript库,它为开发者提供了声明式和响应式的绑定方式,使得在Vue中使用Konva框架变得异常简单。通过Vue Konva,我们可以轻松地在Vue组件中绘制各种复杂的Canvas图形,并且能够实现图形的动态更新和交互。
二、快速上手
- 安装
首先,确保你的项目中已经安装了Vue.js 2.4+版本。接下来,通过npm安装vue-konva和konva:
对于Vue 3
npm install vue-konva konva --save
对于Vue 2
npm install vue-konva@2 konva --save
- 引入和使用VueKonva
在Vue 3中,你需要在main.js中引入VueKonva并使用它:
import { createApp } from 'vue';
import App from './App.vue';
import VueKonva from 'vue-konva';
const app = createApp(App);
app.use(VueKonva);
app.mount('#app');
在Vue 2中,引入和使用VueKonva的方式如下:
import Vue from 'vue';
import VueKonva from 'vue-konva';
Vue.use(VueKonva);
- 在组件模板中引用
在你的Vue组件中,你可以通过以下方式使用Vue Konva的组件:
<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
}
};
}
};
</script>
三、绘制基础图形
Vue Konva提供了与Konva框架相同名称的组件,前缀为v-
。你可以通过config属性传递参数来配置这些组件。以下是一些核心形状的示例:
- v-rect(矩形)
- v-circle(圆形)
- v-ellipse(椭圆)
- v-line(线条)
- v-image(图片)
- v-text(文本)
- v-text-path(文本路径)
- v-star(星形)
- v-label(标签)
- v-path(路径)
- v-regular-polygon(正多边形)
<template>
<div>
<v-stage ref="stage" :config="stageSize">
<v-layer>
<v-text :config="{text: 'Some text on canvas', fontSize: 15}"/>
<v-rect :config="{
x: 20,
y: 50,
width: 100,
height: 100,
fill: 'red',
shadowBlur: 10
}"
/>
<v-circle :config="{
x: 200,
y: 100,
radius: 50,
fill: 'green'
}"
/>
<v-line :config="{
x: 20,
y: 200,
points: [0, 0, 100, 0, 100, 100],
tension: 0.5,
closed: true,
stroke: 'black',
fillLinearGradientStartPoint: { x: -50, y: -50 },
fillLinearGradientEndPoint: { x: 50, y: 50 },
fillLinearGradientColorStops: [0, 'red', 1, 'yellow']
}"/>
</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 {
stageSize: {
width: width,
height: height
}
};
}
};
</script>
四、自定义形状
除了基础形状,Vue Konva还允许你创建自定义形状。通过使用v-shape组件,你可以定义一个绘图函数,该函数接收一个Konva.Canvas
渲染器,你可以利用它访问HTML5 Canvas上下文,并使用特殊方法如context.fillStrokeShape(shape)
来自动处理填充、描边和阴影效果。
<template>
<v-stage ref="stage" :config="stageSize">
<v-layer>
<v-shape :config="{
width: 260,
height: 170,
sceneFunc: function (context, shape) {
const width = shape.width();
const height = shape.height();
context.beginPath();
context.moveTo(0, 0);
context.lineTo(width - 40, height - 90);
context.quadraticCurveTo(width - 110, height - 70, width, height);
context.closePath();
// (!) Konva特定方法,非常重要
context.fillStrokeShape(shape);
},
fill: '#00D2FF',
stroke: 'black',
strokeWidth: 4
}"/>
</v-layer>
</v-stage>
</template>
<script>
const width = window.innerWidth;
const height = window.innerHeight;
export default {
data() {
return {
stageSize: {
width: width,
height: height
}
};
}
};
</script>
五、事件监听
Vue Konva使得监听用户输入事件(如click、dblclick、mouseover、tap、dbltap、touchstart等)和拖拽事件(如dragstart、dragmove、dragend)变得非常简单。你可以通过在组件上添加事件监听器来实现这些功能。
<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>
六、绘制图片
在Vue Konva中,绘制图片需要手动创建一个原生的window.Image实例或canvas元素,并将其作为v-image组件的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 = () => {
// 图片加载完成后设置
this.image = image;
};
}
};
</script>
七、应用滤镜
Vue Konva允许你为图形应用各种滤镜效果。你需要手动缓存Konva.Node,并在需要时重新缓存节点。
<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();
// 重新缓存
const rectNode = this.$refs.rect.getNode();
// 可能需要手动重绘图层
rectNode.cache();
}
},
mounted() {
const rectNode = this.$refs.rect.getNode();
rectNode.cache();
},
};
</script>
八、保存和加载Canvas
Vue Konva提供了简单的方法来保存和加载Canvas状态。你可以通过保存应用的状态来实现这一点,而不需要保存Konva内部的节点。
<template>
<div>
点击Canvas创建一个圆。
<a href=".">刷新页面</a>。圆应该仍然在这里。
<v-stage ref="stage"
:config="stageSize"
@click="handleClick"
>
<v-layer ref="layer">
<v-circle
v-for="item in list"
:key="item.id"
:config="item"></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({
radius: 50,
fill: 'red',
...pos
});
this.save();
},
load() {
const data = localStorage.getItem('storage');
if (data) this.list = JSON.parse(data);
},
save() {
localStorage.setItem('storage', JSON.stringify(this.list));
}
},
mounted() {
this.load();
}
};
</script>
九、拖拽功能
Vue Konva使得实现图形的拖拽功能变得非常简单。你只需要在组件中添加draggable: true属性即可。
<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 Konva没有提供纯声明式的“Vue方式”来使用Transformer工具,但你仍然可以通过手动操作Konva节点来实现这一功能。
<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) {
// 形状被变换,保存新的属性回节点
// 在我们的状态中找到元素
const rect = this.rectangles.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.rectangles.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([]);
}
},
},
};
</script>
十一、动画效果
Vue Konva允许你为图形添加动画效果。你可以使用Konva的Tween和Animation方法来实现这一点。
<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()是Konva.Node实例的方法
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();
// 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中,你可以通过访问Konva节点并使用node.cache()函数来缓存节点。
<template>
<div>
<v-stage ref="stage" :config="stageConfig">
<v-layer ref="layer">
<v-group ref="group">
<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,
shadowColor: 'black',
shadowBlur: 10,
shadowOpacity: 0.6,
scaleX: item.scale,
scaleY: item.scale,
}"
/>
</v-group>
</v-layer>
</v-stage>
<div class="cache">
<input type="checkbox" @change="handleCacheChange"> 缓存图形
</div>
</div>
</template>
<script>
const width = window.innerWidth;
const height = window.innerHeight;
export default {
data() {
return {
list: [],
dragItemId: null,
stageConfig: {
width: width,
height: height,
draggable: true
}
};
},
methods: {
handleCacheChange(e) {
const shouldCache = e.target.checked;
if (shouldCache) {
this.$refs.group.getNode().cache();
} else {
this.$refs.group.getNode().clearCache();
}
}
},
mounted() {
for (let n = 0; n < 300; 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;
}
.cache {
position: absolute;
top: 0;
left: 0;
}
</style>
十三、改变zIndex和组件排序
在Vue Konva中,不推荐直接使用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) {
// 保存拖动元素:
this.dragItemId = e.target.id();
// 通过重新排列items数组,将当前元素移动到顶部:
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>
总结
通过以上内容,我们全面地介绍了Vue Konva的各种功能和用法。从基础图形的绘制到复杂的动画效果,Vue Konva都提供了强大的支持。希望这篇博客能够帮助你更好地理解和使用Vue Konva,为你的Web应用增添更多的交互性和视觉效果。