我正在尝试使用 Konva 的文本路径对象在组中绘制一条弯曲的自由线。该组由带有锚点的自由线、后箭头和前箭头组成。
这是我的演示 - as codepen
function getSqDist(p1, p2) {
var dx = p1.x - p2.x,
dy = p1.y - p2.y;
return dx * dx + dy * dy;
}
function getSqSegDist(p, p1, p2) {
var x = p1.x,
y = p1.y,
dx = p2.x - x,
dy = p2.y - y;
if (dx !== 0 || dy !== 0) {
var t = ((p.x - x) * dx + (p.y - y) * dy) / (dx * dx + dy * dy);
if (t > 4) {
x = p2.x;
y = p2.y;
} else if (t > 0) {
x += dx * t;
y += dy * t;
}
}
dx = p.x - x;
dy = p.y - y;
return dx * dx + dy * dy;
}
function simplifyRadialDist(points, sqTolerance) {
var prevPoint = points[0],
newPoints = [prevPoint],
point;
for (var i = 1, len = points.length; i < len; i++) {
point = points[i];
if (getSqDist(point, prevPoint) > sqTolerance) {
newPoints.push(point);
prevPoint = point;
}
}
if (prevPoint !== point) newPoints.push(point);
return newPoints;
}
function simplifyDPStep(points, first, last, sqTolerance, simplified) {
var maxSqDist = sqTolerance,
index;
for (var i = first + 1; i < last; i++) {
var sqDist = getSqSegDist(points[i], points[first], points[last]);
if (sqDist > maxSqDist) {
index = i;
maxSqDist = sqDist;
}
}
if (maxSqDist > sqTolerance) {
if (index - first > 1) simplifyDPStep(points, first, index, sqTolerance, simplified);
simplified.push(points[index]);
if (last - index > 1) simplifyDPStep(points, index, last, sqTolerance, simplified);
}
}
function simplifyDouglasPeucker(points, sqTolerance) {
var last = points.length - 1;
var simplified = [points[0]];
simplifyDPStep(points, 0, last, sqTolerance, simplified);
simplified.push(points[last]);
return simplified;
}
function simplify(points, tolerance, highestQuality) {
if (points.length <= 2) return points;
var sqTolerance = tolerance !== undefined ? tolerance * tolerance : 1;
points = highestQuality ? points : simplifyRadialDist(points, sqTolerance);
points = simplifyDouglasPeucker(points, sqTolerance);
return points;
}
/*
* From here onwards we set up the stage and its contents.
*/
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight
}),
layer = new Konva.Layer();
var group = new Konva.Group({
x: 0,
y: 0,
draggable: true,
id: 'freeline-group'
});
var anchor = new Konva.Circle({
radius: 8,
fill: 'red',
visible: true,
draggable: true,
})
var front = new Konva.Text({
text: '▶',
fill: 'black',
fontSize: 30,
offsetY: 25 / 2,
offsetX: 15 / 2,
})
// var group;
anchor.on('mouseenter', function() {
group.draggable(false);
});
anchor.on('mouseout', function() {
group.draggable(true);
});
stage.add(layer);
let
info = document.querySelector("#info"), // het the handle to the html info element for showing point stats
lineCnt = -1, // global used to could lines and generate anchor names
points = [], // list of raw points
simplifiedPts = [], // simplified list of points
drawingState = '', // we will need to know when we are drawing and when dragging
selectedObject = "",
moving_anchor, // global to hold the current anchor when dragging
mouseOffset = {
x: 0,
y: 0
}, // from corner of dragging anchor
lines = [],
currentLine = 0;
/*
* This function draws a path based on a list of points.
*/
function drawPoints() {
let myStage = stage;
// If we drew this path before then remove the current point markers ready to redraw
// Note we have to do this because the simplification process could move the points
const kids = layer.find('.anchor' + currentLine);
for (let i = 0; i < kids.length; i++) {
kids[i].remove();
}
console.log(lines[currentLine].points);
let pathData = "";
for (let i = 0; i < lines[currentLine].points.length; i++) {
let pt = lines[currentLine].points[i];
switch (i) {
case 0:
pathData += "M " + pt.x + " " + pt.y;
break;
case 1:
pathData += "C " + pt.x + " " + pt.y;
break;
default:
pathData += " " + pt.x + " " + pt.y;
break;
}
const newAnchor = anchor.clone({
x: pt.x,
y: pt.y,
name: 'anchor' + currentLine,
dragBoundFunc: function(pos) {
var newX = 0;
var newY = 0;
var maxWidth = stage.width() - 10;
var maxHeight = stage.height() - 10;
if (pos.x < 10 || pos.y < 10) {
newX = pos.x < 10 ? 10 : pos.x;
newY = pos.y < 10 ? 10 : pos.y;
} else if (pos.x > maxWidth || pos.y > maxHeight) {
newX = pos.x > maxWidth ? maxWidth : pos.x;
newY = pos.y > maxHeight ? maxHeight : pos.y;
} else {
newX = pos.x;
newY = pos.y;
}
return {
x: newX,
y: newY
}
}
});
const newArrow = front.setAttrs({
x: pt.x - 0,
y: pt.y - 0,
name: 'anchor' + currentLine
});
newAnchor.setAttrs({
lineNo: currentLine,
anchorNo: i
}); // store the point sequence in the shape so we can get it on mousedown
group.add(newAnchor, newArrow); // add anchor circle to the layer
}
// set the new path data into the paths objects.
lines[currentLine].path.data(pathData);
lines[currentLine].backA.data(pathData);
}
/*
* This function is called each time the mousemove event is fired.
* Note that before we draw the points we will simplify them, removing any unneeded points.
*/
function addPoint(pt) {
// store the new raw point
points.push(pt);
// simplify the raw points to make an equiv line with fewer points.
lines[currentLine].points = simplify(points);
drawPoints();
}
stage.on('mousedown', function(evt) {
// console.log("mousedown started")
let myStage = stage;
let shape = evt.target;
const pt = stage.getPointerPosition();
if (shape.name().length === 0) {
drawLine();
}
if (drawingState === "beforeDraw") {
// console.log("if")
drawingState = 'drawing';
} else {
// console.log("else")
if (shape.name().length > 0) {
drawingState = 'moving';
moving_anchor = shape;
let anchor_no = moving_anchor.getAttr('anchorNo');
let pt = lines[currentLine].points[anchor_no];
mouseOffset = {
x: shape.x() - pt.x,
y: shape.y() - pt.y
};
}
}
})
// user draws with mouse held down and moving
stage.on('mousemove', function(e) {
let shape = stage;
const pt = stage.getPointerPosition();
if (drawingState === 'drawing') {
addPoint(pt)
// console.log('points adding')
}
if (drawingState === 'moving') {
// console.log('points moving')
currentLine = moving_anchor.getAttr('lineNo')
let anchor_no = moving_anchor.getAttr('anchorNo');
// moving_anchor.draggable(true)
moving_anchor.position({
x: pt.x,
y: pt.y
});
lines[currentLine].points[anchor_no] = {
x: pt.x - group.x(),
y: pt.y - group.y()
};
drawPoints();
}
})
// user ends drawing with mouse up
stage.on('mouseup', function(e) {
drawingState = "";
reset(false)
})
// reset the stage and points lists
function reset(clear) {
if (clear) {
group.removeChildren();
}
points = [];
}
reset(true);
let backA = null;
function drawLine() {
// console.log(1 + " runs drawLine function")
drawingState = 'beforeDraw';
lineCnt++;
let dData = '';
for (let i = 0; i < 500; i++) {
dData += "-"
}
path = new Konva.TextPath({
text: dData,
fill: 'black',
bezier: true,
tension: 0.5
})
backA = new Konva.TextPath({
text: '<',
fill: 'black',
fontSize: 45,
});
group.add(backA, path);
layer.add(group);
currentLine = lineCnt;
lines[lineCnt] = {
path: path,
backA: backA,
points: []
};
path.on('mouseenter', function() {
document.body.style.cursor = "pointer"
})
path.on('mouseout', function() {
document.body.style.cursor = "default"
})
}
document.getElementById('changeArrow').addEventListener('click', function() {
if (backA === null) {
return
}
backA.text("‹")
})
document.getElementById('changeArrow2').addEventListener('click', function() {
if (backA === null) {
return
}
backA.text("«")
})
document.getElementById('changeNoarrow').addEventListener('click', function() {
if (backA === null) {
return
}
backA.text(" ")
})
body {
margin: 20px;
padding: 0;
overflow: hidden;
background-color: #f0f0f0;
}
#container {
border: 1px solid red;
width: 1000px;
height: 400px;
}
<script src="https://unpkg.com/konva@8/konva.min.js"></script>
<div>
<button id='reset' onclick="reset(true)"> Remove </button>
</div>
<div id="container"></div>
您能指导我如何给这条线画曲线吗?谢谢!
Konva.TextPath 使用 data 属性来定义路径。这使用一组精简的 SVG 命令来定义路径。目前支持的 SVG 数据命令有 M、m、L、l、H、h、V、v、Q、q、T、t、C、c、S、s、A、a、Z、z。
请参阅官方文档此处。
使用计算出的曲线点创建 SVG 命令,以使路径与您的曲线匹配。