将任意二维矢量旋转 90 度
如果您还记得二维向量的旋转 90 度规则,那么垂直线很容易计算。
矢量{x,y} 可以顺时针{-y,x} 或逆时针{y,-x} 旋转90 度。交换 x 和 y 并否定 y 为顺时针方向或 x 逆时针方向
所以对于线段 x1,y1 到 x2,y2 转换为向量,对该向量进行归一化并旋转 90 度,如下所示
function getPerpOfLine(x1,y1,x2,y2){ // the two points can not be the same
var nx = x2 - x1; // as vector
var ny = y2 - y1;
const len = Math.sqrt(nx * nx + ny * ny); // length of line
nx /= len; // make one unit long
ny /= len; // which we call normalising a vector
return [-ny, nx]; // return the normal rotated 90 deg
}
然后说你想在线段的末端画一条10像素的线
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
document.body.append(canvas);
ctx.strokeStyle = "black";
ctx.lineJoin = ctx.lineCap = "round";
ctx.lineWidth = 3;
// the line segment
const x1 = 40, y1 = 40, x2 = 260, y2 = 110;
const endLen = 10; // length of end lines
var px = y1 - y2; // as vector at 90 deg to the line
var py = x2 - x1;
const len = endLen / Math.hypot(px, py);
px *= len; // make leng 10 pixels
py *= len;
// draw line the start cap and end cap.
ctx.beginPath();
ctx.lineTo(x1, y1); // the line start
ctx.lineTo(x2, y2);
ctx.moveTo(x1 + px, y1 + py); // the start perp line
ctx.lineTo(x1 - px, y1 - py);
ctx.moveTo(x2 + px, y2 + py); // the end perp line
ctx.lineTo(x2 - px, y2 - py);
ctx.stroke();
更新
沿线呈现内容的简单解决方案,使用相同的 90 度规则。
还有另一种使用相同矢量旋转进行渲染的方法,但不是通过矢量乘法设置垂直轴,而是将变换的 y 轴设置为与沿线的 x 轴成 90 度。将原点设置为行的开头,您只需相对于行进行渲染。
setTransformToLine(x1, y1, x2, y2)
以下函数将画布变换设置为沿线
// Set 2D context current transform along the line x1,y1,x2,y2 and origin to
// start of line. y Axis is rotated clockwise 90 from the line.
// Returns the line length as that is most frequently required when
// using the method saving some time.
function setTransformToLine(x1, y1, x2, y2) {
const vx = x2 - x1; // get the line as vector
const vy = y2 - y1;
const len = Math.hypot(vx, vy); // For <= IE11 use Math.sqrt(vx * vx + vy * vy)
const nx = vx / len; // Normalise the line vector. Making it one
const ny = vy / len; // pixel long. This sets the scale
// The transform is the normalised line vector for x axis, y at 90 deg
// and origin at line start
ctx.setTransform(nx, ny, -ny, nx, x1, y1); // set transform
return len;
}
如何使用
这个例子展示了如何使用变换来做同样的行,但也添加了注释的长度。
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
document.body.append(canvas);
ctx.strokeStyle = "black";
ctx.lineJoin = ctx.lineCap = "round";
ctx.lineWidth = 3;
ctx.font = "16px arial";
ctx.textBaseline = "middle";
ctx.textAlign = "center";
const x1 = 40, y1 = 40, x2 = 260, y2 = 110;
const endLen = 10;
function setTransformToLine(x1, y1, x2, y2) {
const vx = x2 - x1;
const vy = y2 - y1;
const len = Math.hypot(vx, vy);
const nx = vx / len;
const ny = vy / len;
ctx.setTransform(nx, ny, -ny, nx, x1, y1);
return len;
}
// Set the transform along the line. Keep the line length
// line len is need to get the x coord of the end of the line
const lineLen = setTransformToLine(x1, y1, x2, y2);
const lineLenStr = Math.round(lineLen) + "px";
const textWidth = ctx.measureText(lineLenStr).width;
const rlen = lineLen - textWidth - 16; // find the remaining line len after removing space for text
// Rendering is done in line local coordinates
// line is from (0,0) to (lineLen,0)
// Now draw the line the ends first and then along the line leaving gap for text
ctx.beginPath();
ctx.lineTo(0, -endLen); // start perp line
ctx.lineTo(0, endLen);
ctx.moveTo(lineLen, -endLen); // end of line is at lineLen
ctx.lineTo(lineLen, endLen);
ctx.moveTo(0,0); // line start segment
ctx.lineTo(rlen / 2, 0);
ctx.moveTo(lineLen - rlen / 2,0); // line end segment
ctx.lineTo(lineLen, 0);
ctx.stroke(); // render it.
// now add text at the line center
ctx.fillText(lineLenStr, lineLen / 2, 0);
// To restore the transform to its default use identity matrix
ctx.setTransform(1, 0, 0, 1, 0, 0);