let isDrawing = false;
// Set up a stage
stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight
}),
// add a layer to draw on
layer = new Konva.Layer(),
mode = 'draw', // state control, draw = drawing line, measuring = finding nearest point
lineShape = null, // the line shape that we draw
connectorLine = null, // link between mouse and nearest point
pathShape = null; // path element
// Add the layer to the stage
stage.add(layer);
// On this event, add a line shape to the canvas - we will extend the points of the line as the mouse moves.
stage.on('mousedown touchstart', function (e) {
reset();
var pos = stage.getPointerPosition();
if (mode === 'draw'){ // add the line that follows the mouse
lineShape = new Konva.Line({
stroke: 'magenta',
strokeWidth: 5,
points: [pos.x, pos.y],
draggable: true
});
layer.add(lineShape);
}
});
// when we finish drawing switch mode to measuring
stage.on('mouseup touchend', function () {
// use the pointsToPath fn to prepare a path from the line points.
thePath = pointsToPath(lineShape.points());
// Make a path shape tracking the lineShape because path has more options for measuring.
pathShape = new Konva.Path({
stroke: 'cyan',
strokeWidth: 5,
data: thePath
});
layer.add(pathShape);
lineShape.destroy(); // remove the path shape from the canvas as we are done with it
layer.batchDraw();
mode='measuring'; // switch the mode
});
// As the mouse is moved we aer concerned first with drawing the line, then measuring the nearest point from the mouse pointer on the line
stage.on('mousemove touchmove', function (e) {
// get position of mouse pointer
const mousePos = stage.getPointerPosition();
if (mode === 'draw' ){
if (lineShape) { // on first move we will not yet have this shape!
// drawing the line - extend the line shape by adding the mouse pointer position to the line points array
const newPoints = lineShape.points().concat([mousePos.x, mousePos.y]);
lineShape.points(newPoints); // update the line points array
}
}
else {
// showing nearest point - link mouse pointer to the closest point on the line
const closestPt = closestPoint(pathShape, {x: mousePos.x, y: mousePos.y});
connectorLine.points([closestPt.x, closestPt.y, mousePos.x, mousePos.y]);
}
layer.batchDraw();
});
// Function to make a Konva path from the points array of a Konva.Line shape.
// Returns a path that can be given to a Konva.Path as the .data() value.
// Points array is as [x1, y1, x2, y2, ... xn, yn]
// Path is a string as "M x1, y1 L x2, y2...L xn, yn"
var pointsToPath = function(points){
let path = '';
for (var i = 0; i < points.length; i = i + 2){
switch (i){
case 0: // move to
path = path + 'M ' + points[i] + ',' + points[i + 1] + ' ';
break;
default:
path = path + 'L ' + points[i] + ',' + points[i + 1] + ' ';
break;
}
}
return path;
}
// reset the canvas & shapes as needed for a clean restart
function reset() {
mode = 'draw';
layer.destroyChildren();
layer.draw();
connectorLine = new Konva.Line({
stroke: 'red',
strokeWidth: 1,
points: [0,0, -100, -100]
})
layer.add(connectorLine);
}
// reset when the user asks
$('#reset').on('click', function(){
reset();
})
reset(); // reset at startup to prepare state
// From article by https://bl.ocks.org/mbostock at https://bl.ocks.org/mbostock/8027637
// modified as prefixes (VW)
function closestPoint(pathNode, point) {
var pathLength = pathNode.getLength(), // (VW) replaces pathNode.getTotalLength(),
precision = 8,
best,
bestLength,
bestDistance = Infinity;
// linear scan for coarse approximation
for (var scan, scanLength = 0, scanDistance; scanLength <= pathLength; scanLength += precision) {
if ((scanDistance = distance2(scan = pathNode.getPointAtLength(scanLength))) < bestDistance) {
best = scan, bestLength = scanLength, bestDistance = scanDistance;
}
}
// binary search for precise estimate
precision /= 2;
while (precision > 0.5) {
var before,
after,
beforeLength,
afterLength,
beforeDistance,
afterDistance;
if ((beforeLength = bestLength - precision) >= 0 && (beforeDistance = distance2(before = pathNode.getPointAtLength(beforeLength))) < bestDistance) {
best = before, bestLength = beforeLength, bestDistance = beforeDistance;
} else if ((afterLength = bestLength + precision) <= pathLength && (afterDistance = distance2(after = pathNode.getPointAtLength(afterLength))) < bestDistance) {
best = after, bestLength = afterLength, bestDistance = afterDistance;
} else {
precision /= 2;
}
}
best = {x: best.x, y: best.y}; // (VW) converted to object instead of array, personal choice
best.distance = Math.sqrt(bestDistance);
return best;
function distance2(p) {
var dx = p.x - point.x, // (VW) converter to object from array
dy = p.y - point.y;
return dx * dx + dy * dy;
}
}
body {
margin: 10;
padding: 10;
overflow: hidden;
background-color: #f0f0f0;
}
#container {
width: 600px;
height: 400px;
border: 1px solid silver;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://unpkg.com/konva@^3/konva.min.js"></script>
<p>Draw a line by click + drag. Move mouse to show nearest point on line function. </p>
<p>
<button id = 'reset'>Reset</button></span>
</p>
<div id="container"></div>