1、vue实现动态绘制贝塞尔曲线
- 效果图:
- index.vue界面代码
<template>
<body>
<div>
<el-button type="primary" @click="readJson()">读取JSON</el-button>
</div>
<div class="canvasBox">
<v-stage ref="stage" :config="{width: 1000, height: 600}">
<v-layer ref="layer">
<v-shape v-for="(el,index) in shapes" :config="el" :key="index + 'shape'"/> <!--绘制曲线-->
<v-line v-for="(item,index) in lines" :config="item" :key="index + 'line'"/><!--绘制辅助线-->
<v-circle @dragmove="dragmove(el)" @mouseout="mouseout(el)" @mouseover="mouseover(el)" v-for="(el,index) in anchors" :config="el" :key="index+ 'circle'"/> <!--绘制实际路径-->
</v-layer>
</v-stage>
</div>
</body>
</template>
<script>
export default {
name: "konva",
data() {
return {
liness:[ //定义曲线
{ id:1, key:"PointLine04422", lineId:"4422", strokeWidth:2, stroke:'#2973FF' },
{ id:2, key:"PointLine14422", lineId:"44122", strokeWidth:2, stroke:'#2973FF' },
{ id:3, key:"PointLine24422", lineId:"44222", strokeWidth:2, stroke:'#2973FF' }],
locations:[ //定义点位
{ id:1, order:1, x:50, y:50, index:1, line:1, link:0, dot:0, key:"1", remarks:"", },
{ id:2, order:2, x:50, y:300, index:2, line:1, link:0, dot:1, key:"2", remarks:"" },
{ id:3, order:3, x:212, y:256, index:3, line:1, link:0, dot:1, key:"3", remarks:"" },
{ id:4, order:4, x:350, y:160, index:4, line:1, link:1, dot:2, key:"4", remarks:"" },
{ id:5, order:4, x:350, y:160, index:1, line:2, link:1, dot:2, key:"4", remarks:"" },
{ id:6, order:5, x:450, y:100, index:2, line:2, link:0, dot:1, key:"5", remarks:"" },
{ id:7, order:6, x:600, y:100, index:3, line:2, link:0, dot:1, key:"6", remarks:"" },
{ id:8, order:7, x:455, y:366, index:4, line:2, link:1, dot:2, key:"7", remarks:"" },
{ id:9, order:7, x:455, y:366, index:1, line:3, link:1, dot:2, key:"7", remarks:"" },
{ id:10, order:8, x:564, y:243, index:2, line:3, link:0, dot:1, key:"8", remarks:"" },
{ id:11, order:9, x:650, y:300, index:3, line:3, link:0, dot:1, key:"9", remarks:"" },
{ id:12, order:10, x:800, y:400, index:4, line:3, link:0, dot:0, key:"10", remarks:"" }],
anchors: [],
shapes: [],
points:[],
lines: [],
stageSize: {
width: 1000,
height: 600
}
}
},
created(){
this.creatPoint_n(); //界面加载时执行
},
methods: {
orderby(x,y){ //排序(升序ASC)
return x.order-y.order
},
readJson() //此方法用于读取点位移动变化后的json值
{
this.$message({
message: JSON.stringify(this.locations),
type: "success"
});
console.log(this.locations);
},
dragmove(el){ //移动光标时的方法
this.updateDottedLines(); //辅助线跟随点动
},
mouseover(el){ //鼠标移动到此点位的事件方法
document.body.style.cursor = 'pointer'; //鼠标小手指
el.strokeWidth = 4; //鼠标停留在中间几个定位点的光圈大小
},
mouseout(el){ //鼠标离开时的方法
document.body.style.cursor = 'default'; //普通鼠标光标
el.strokeWidth = 2; //鼠标停留在起始点或者末尾点的光标状态
},
//根据入口,出口点位创建n个等间距的点位
creatPoint_n(){
let num =2;
let line = {
endId:"22",
startId:"44",
linePoints:[[0,100],[1000,100]]
};
//生成每段的辅助点位
this.createAuxiliaryPoint()
},
createAuxiliaryPoint(){ //生产每段的辅助点位
let lines = []; //辅助线集合
let i=0,b=1;
//生成点位 //"#Point"+i+"anchor1"+ startId
var wholestory = this.locations.filter(function(item){ //始末点
return item.dot == 0;
}).sort(this.orderby); //排序
var auxiliary = this.locations.filter(function(item){ //辅助点
return item.dot == 1;
}).sort(this.orderby); //排序
var linking = this.locations.filter(function(item){ //连接点
return item.dot == 2;
}).sort(this.orderby); //排序
for (let i = 0; i < linking.length; i++) { //去除重复数据
for (let j = i + 1; j < linking.length;) {
if (linking[i].key == linking[j].key) {
linking.splice(j, 1)
} else j++
}
}
for (let i = 0; i < wholestory.length; i++) { //创建始末点
this.buildAnchor(wholestory[i].x, wholestory[i].y, wholestory[i].key, "A"+wholestory[i].line);
}
for (let i = 0; i < auxiliary.length; i++) { //创建辅助点
this.buildControl(auxiliary[i].x, auxiliary[i].y, auxiliary[i].key, "A"+auxiliary[i].line);
}
for (let i = 0; i < linking.length; i++) { //创建连接点
this.buildAnchorMerge(linking[i].x, linking[i].y, linking[i].key, "A"+linking[i].line);
}
//画贝塞尔曲线
this.shapes.push({
stroke: '#f6d87e', //曲线颜色
strokeWidth: 2, //曲线宽度
lineId:"AAA", //曲线Id
sceneFunc: (ctx, shape) => {
let layer = this.$refs.layer.getNode();
ctx.beginPath();
//{ id:1, order:1, x:50, y:50, line:1, link:0, key:"Build"+0+"anchor" + "4422", remarks:"", },
ctx.moveTo(layer.findOne("#"+this.locations[0].key).x(), layer.findOne("#"+this.locations[0].key).y());
debugger;
for (let i = 0; i < this.liness.length; i++) { //创建始末点
let lineid=this.liness[i].id;
var locat= this.locations.filter(function(item){ //根据获取到的id得到集合
return item.line == lineid;
});
ctx.bezierCurveTo(
layer.findOne("#"+locat[1].key).x(), layer.findOne("#"+locat[1].key).y(),
layer.findOne("#"+locat[2].key).x(), layer.findOne("#"+locat[2].key).y(),
layer.findOne("#"+locat[3].key).x(), layer.findOne("#"+locat[3].key).y(),
);
}
ctx.fillStrokeShape(shape);
ctx.stroke();
ctx.closePath();
}
});
for (let i = 0; i < this.liness.length; i++) { //创建辅助线
this.lines.push({
dash: [10, 10, 0, 10], //虚线的样式
strokeWidth: 2, //虚线的宽度
stroke: '#2973FF', //虚线的颜色
lineCap: 'round',
id: this.liness[i].key,
lineId: this.liness[i].lineId,
points: [0, 0],
});
}
this.$nextTick(()=>{
this.updateDottedLines()
})
},
updateDottedLines(){ //创建辅助线
//let startId = line.startId+ "" + line.endId;
//let endId = line.startId+ "1" + line.endId;
let layer = this.$refs.layer.getNode();
this.lines.map((item,i) => {
let bezierLinePath = layer.findOne("#" + item.id); //根据Id来获取这个贝塞尔曲线
var codeinfo = this.liness.find((element) => (element.key == item.id)); //根据key值获取id
var codeinfos= this.locations.filter(function(item){ //根据获取到的id得到集合
return item.line == codeinfo.id;
});
let point = [
layer.findOne("#"+codeinfos[0].key).x(), layer.findOne("#"+codeinfos[0].key).y(),
layer.findOne("#"+codeinfos[1].key).x(), layer.findOne("#"+codeinfos[1].key).y(),
layer.findOne("#"+codeinfos[2].key).x(), layer.findOne("#"+codeinfos[2].key).y(),
layer.findOne("#"+codeinfos[3].key).x(), layer.findOne("#"+codeinfos[3].key).y(),
];
bezierLinePath.points(point); //绘制1号辅助线
for (let index = 0; index < this.locations.length; index++) { //赋值
this.locations[index].x = layer.findOne("#"+this.locations[index].key).x();
this.locations[index].y = layer.findOne("#"+this.locations[index].key).y();
}
/*
.catch(ex => {
this.$message({
message: ex.message,
type: "error"
});
});
*/
});
},
//创建轨迹出入口的点
buildAnchor(x, y,id,lineId) {
this.anchors.push({
x: x,
y: y,
id:id,
lineId:lineId,
radius: 5, //点的大小
stroke: '#b5d4ff', //点的环线颜色
fill: '#63d12b',
strokeWidth: 0,
draggable: true, //是否可以鼠标拖动
});
},
//创建合并的点,即中间共用点位
buildAnchorMerge(x, y,id,lineId) {
this.anchors.push({
x: x,
y: y,
id:id,
lineId:lineId,
radius: 5,
stroke: 'green',
fill: 'green',
strokeWidth: 2,
draggable: true,
});
},
//创建贝塞尔曲线辅助移动点位
buildControl(x, y,id,lineId) {
this.anchors.push({
x: x,
y: y,
id:id,
lineId:lineId,
radius: 5,
stroke: '#2973FF',
fill: '#2973FF',
strokeWidth: 2,
draggable: true,
});
},
}
}
</script>
<style scoped>
html,body{
width: 100%;
height: 100%;
}
.canvasBox {
width: 1000px;
height: 600px;
margin: 0 auto;
border: 1px solid #ccc;
background-size: 100% 100%;
position: relative;
}
</style>
3、参考示例,vue绘制连续有规律的曲线,可拖动。
- 效果图:
- index.vue源代码:
<template>
<div class="canvasBox">
<v-stage ref="stage" :config="{width: 1000, height: 600}">
<v-layer ref="layer">
<v-shape v-for="(el,index) in shapes" :config="el" :key="index + 'shape'"/>
<v-line v-for="(item,index) in lines" :config="item" :key="index + 'line'"/>
<v-circle @dragmove="dragmove(el)" @mouseout="mouseout(el)" @mouseover="mouseover(el)" v-for="(el,index) in anchors" :config="el" :key="index+ 'circle'" />
</v-layer>
</v-stage>
</div>
</template>
<script>
export default {
name: "konva",
data() {
return {
anchors: [],
shapes: [],
points:[],
lines: [],
stageSize: {
width: 1000,
height: 600
}
}
},
created(){
this.creatPoint_n({endId:"22",startId:"44",linePoints:[[50,400],[800,400]]},5)
},
methods: {
dragmove(el){
this.updateDottedLines({endId:"22",startId:"44"},true);
},
mouseover(el){
document.body.style.cursor = 'pointer';
el.strokeWidth = 4;
},
mouseout(el){
document.body.style.cursor = 'default';
el.strokeWidth = 2;
},
//根据入口,出口点位创建n个等间距的点位
creatPoint_n(line,num){
let start = line.linePoints[0];
let end = line.linePoints[1];
const pointNum = num;
if(!num){num = 2}
let range_row = parseInt((end[0] - start[0])/(num-1));
let range_col = parseInt((end[1] - start[1])/(num-1));
while (num > 2) {
num--;
let newPoint = [start[0] + range_row*(pointNum - num),start[1] + range_col*(pointNum - num)];
line.linePoints.splice(line.linePoints.length - 1,0,newPoint);
}
//生成每段的辅助点位
this.createAuxiliaryPoint(line)
},
createAuxiliaryPoint(line){
let startId = line.startId+ "" + line.endId;
let _this = this,lines = [];
for (let i=0;i<line.linePoints.length-1;i++){
let point = line.linePoints[i];
let point_next = line.linePoints[i+1];
let center = [(point[0] + point_next[0])/2,(point[1] + point_next[1])/2];
//生成点位
if(i === 0){
this.buildAnchor(point[0],point[1],"Build"+i+"anchor" + startId,startId);
}
if(i === line.linePoints.length-2){
this.buildAnchor(point_next[0],point_next[1],"Build"+i+"anchor_end"+ startId,startId);
}else{
this.buildAnchorMerge(point_next[0],point_next[1],"Build"+i+"anchor_end"+ startId,startId);
}
this.buildControl(center[0]-20,center[1]-30,"Point"+i+"anchor1"+ startId,startId);
this.buildControl(center[0]+20,center[1] + 30,"Point"+i+"anchor2"+ startId,startId);
//画贝塞尔曲线
this.shapes.push({
stroke: 'red',
strokeWidth: 2,
lineId:startId,
sceneFunc: (ctx, shape) => {
let layer = _this.$refs.layer.getNode();
ctx.beginPath();
if(i === 0){
ctx.moveTo(layer.findOne("#Build"+i+"anchor"+ startId).x(), layer.findOne("#Build"+i+"anchor"+ startId).y());
}else{
ctx.moveTo(layer.findOne("#Build"+(i-1)+"anchor_end"+ startId).x(), layer.findOne("#Build"+(i-1)+"anchor_end"+ startId).y());
}
ctx.bezierCurveTo(
layer.findOne("#Point"+i+"anchor1"+ startId).x(),
layer.findOne("#Point"+i+"anchor1"+ startId).y(),
layer.findOne("#Point"+i+"anchor2"+ startId).x(),
layer.findOne("#Point"+i+"anchor2"+ startId).y(),
layer.findOne("#Build"+i+"anchor_end"+ startId).x(),
layer.findOne("#Build"+i+"anchor_end"+ startId).y(),
);
ctx.fillStrokeShape(shape);
}
});
//创建辅助线
this.lines.push({
dash: [10, 10, 0, 10],
strokeWidth: 2,
stroke: '#2973FF',
lineCap: 'round',
id: 'PointLine' + i + "" + startId,
lineId:startId,
points: [0, 0],
});
}
this.$nextTick(()=>{
this.updateDottedLines(line)
})
},
updateDottedLines(line,flag){
let startId = line.startId+ "" + line.endId;
let layer = this.$refs.layer.getNode();
let startIndex = -1;
this.lines.map((item,i) => {
if( startIndex === -1){startIndex = i}
let bezierLinePath = layer.findOne("#" + item.id);
let start = [];
let index = i-startIndex;
if(index === 0){
start.push(layer.findOne("#Build"+ index +"anchor"+ startId).x());
start.push(layer.findOne("#Build"+ index +"anchor"+ startId).y())
}else{
start.push(layer.findOne("#Build"+ (index-1) +"anchor_end"+ startId).x());
start.push(layer.findOne("#Build"+ (index-1) +"anchor_end"+ startId).y())
}
let point = [
start[0],start[1],
layer.findOne("#Point"+index+"anchor1"+ startId).x(),
layer.findOne("#Point"+index+"anchor1"+ startId).y(),
layer.findOne("#Point"+index+"anchor2"+ startId).x(),
layer.findOne("#Point"+index+"anchor2"+ startId).y(),
layer.findOne("#Build"+index+"anchor_end"+ startId).x(),
layer.findOne("#Build"+index+"anchor_end"+ startId).y(),
];
bezierLinePath.points(point);
});
},
//创建轨迹出入口的点
buildAnchor(x, y,id,lineId) {
this.anchors.push({
x: x,
y: y,
id:id,
lineId:lineId,
radius: 5,
stroke: '#b5d4ff',
fill: '#388aff',
strokeWidth: 0,
draggable: true,
});
},
//创建合并的点,即中间共用点位
buildAnchorMerge(x, y,id,lineId) {
this.anchors.push({
x: x,
y: y,
id:id,
lineId:lineId,
radius: 5,
stroke: 'green',
fill: 'green',
strokeWidth: 2,
draggable: true,
});
},
//创建贝塞尔曲线辅助移动点位
buildControl(x, y,id,lineId) {
this.anchors.push({
x: x,
y: y,
id:id,
lineId:lineId,
radius: 5,
stroke: '#2973FF',
fill: '#2973FF',
strokeWidth: 2,
draggable: true,
});
},
}
}
</script>
<style scoped>
html,body{
width: 100%;
height: 100%;
}
.canvasBox {
width: 1000px;
height: 600px;
margin: 0 auto;
border: 1px solid #ccc;
background-size: 100% 100%;
position: relative;
}
</style>
4、html实现多阶贝塞尔曲线,先看一下效果图,本小段结尾处有原文件链接
5、贝塞尔曲线路径算法,js贝塞尔曲线路径点【】