画一条 canvas 线,最后一个点
Make a canvas line with a point at the end
我正在尝试将 canvas 的末端变成一个点(有点像箭头,除了两侧不应该超出线的宽度......见下图有关行尾应该如何显示的示例)。
请参阅下图以了解行尾的外观示例。
我正在尝试使用行上限,但唯一可用的上限是 'round' 或 'square' (http://www.w3schools.com/tags/canvas_linecap.asp)。
下面的fiddle 是我要给末尾打分的那一行。
代码也在下面:
<body>
<canvas id="myCanvas" width="300" height="150" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.</canvas>
<script>
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.beginPath();
ctx.moveTo(20, 20);
ctx.bezierCurveTo(20, 100, 200, 100, 200, 20);
ctx.lineWidth=10
ctx.stroke();
</script>
</body>
不幸的是,canvas 中没有这样的功能。您将必须手动计算线的结果角度(这意味着您需要实施贝塞尔数学)。
然后用线的宽度根据那个角度自己画帽子。
第 1 步 - 寻找方向
让我们使用不是 90° 向上或侧面的一端使其更具挑战性:
var ctx = document.querySelector("canvas").getContext("2d");
// draw as normal
ctx.beginPath();
ctx.moveTo(20, 20);
ctx.bezierCurveTo(20, 100, 170, 70, 200, 20);
ctx.lineWidth=10
ctx.stroke();
// get two points from the end
var pt1 = order3(20, 20, 20, 100, 170, 70, 200, 20, 0.98);
var pt2 = order3(20, 20, 20, 100, 170, 70, 200, 20, 1);
// show direction
ctx.lineWidth = 2;
ctx.strokeStyle = "red";
ctx.beginPath();
ctx.moveTo(pt1.x, pt1.y);
ctx.lineTo(pt1.x + (pt2.x - pt1.x) * 10, pt1.y + (pt2.y - pt1.y) * 10);
ctx.stroke();
//B(t) = (1-t)^3 * z0 + 3t (1-t)^2 * c0 + 3 t^2 (1-t) * c1 + t^3 * z1 for 0 <=t <= 1
function order3(z0x, z0y, c0x, c0y, c1x, c1y, z1x, z1y, t) {
var tm1 = 1 - t, // (1 - t)
tm12 = tm1 * tm1, // (1 - t) ^ 2
tm13 = tm12 * tm1, // (1 - t) ^ 3
t2 = t * t, // t ^ 2
t3 = t2 * t, // t ^ 3
tmm3 = t * 3 * tm12, // 3 x t * (1 - t) ^ 2
tmm23 = t2 * 3 * tm1, // t ^ 2 * 3 * (1 - t)
x, y;
x = (tm13 * z0x + tmm3 * c0x + tmm23 * c1x + t3 * z1x + 0.5) | 0;
y = (tm13 * z0y + tmm3 * c0y + tmm23 * c1y + t3 * z1y + 0.5) | 0;
return {
x: x,
y: y
}
}
<canvas width=220 height=100 />
Update 或者作为 markE 点,你可以从控制点计算它(我的不好,我完全忘记了这个 - 感谢 markE) - 这可能是更好的方法大多数情况与使用“t
”方法相比。
为了完整起见,我将其包含在此处:
// calculate the ending angle from the two last nodes (cp2 and end point)
var dx = pt2.x - cp2.x; // assumes points and control points as objects
var dy = pt2.y - cp2.y;
var angle = Math.atan2(dy, dx);
更新结束
第 2 步 - 求角度和距离
我们需要计算实际角度,以便我们可以将其用于箭头的底部:
// get angle
var diffX = pt1.x - pt2.x; // see update comment above
var diffY = pt1.y - pt2.y;
var angle = Math.atan2(diffY, diffX);
var tangent = Math.atan2(diffX, -diffY);
(故意略微偏移)
第 3 步 - 画帽子
现在我们有足够的信息来画一个上限:
var ctx = document.querySelector("canvas").getContext("2d");
// draw as normal
ctx.beginPath();
ctx.moveTo(20, 20);
ctx.bezierCurveTo(20, 100, 170, 70, 200, 20);
ctx.lineWidth=40
ctx.stroke();
// get two points from the end
var pt1 = order3(20, 20, 20, 100, 170, 70, 200, 20, 0.98);
var pt2 = order3(20, 20, 20, 100, 170, 70, 200, 20, 1);
var diffX = pt1.x - pt2.x;
var diffY = pt1.y - pt2.y;
var angle = Math.atan2(diffY, diffX);
var tangent = Math.atan2(diffX, -diffY);
var lw = ctx.lineWidth * 0.5 - 0.5;
// draw cap
ctx.beginPath();
ctx.moveTo(pt2.x + lw * Math.cos(tangent), pt2.y + lw * Math.sin(tangent));
ctx.lineTo(pt2.x - lw * Math.cos(tangent), pt2.y - lw * Math.sin(tangent));
ctx.lineTo(pt1.x - lw * Math.cos(angle), pt1.y - lw * Math.sin(angle));
ctx.fill();
// due to inaccuracies, you may have to mask tiny gaps
ctx.lineWidth = 1;
ctx.stroke();
//B(t) = (1-t)^3 * z0 + 3t (1-t)^2 * c0 + 3 t^2 (1-t) * c1 + t^3 * z1 for 0 <=t <= 1
function order3(z0x, z0y, c0x, c0y, c1x, c1y, z1x, z1y, t) {
var tm1 = 1 - t, // (1 - t)
tm12 = tm1 * tm1, // (1 - t) ^ 2
tm13 = tm12 * tm1, // (1 - t) ^ 3
t2 = t * t, // t ^ 2
t3 = t2 * t, // t ^ 3
tmm3 = t * 3 * tm12, // 3 x t * (1 - t) ^ 2
tmm23 = t2 * 3 * tm1, // t ^ 2 * 3 * (1 - t)
x, y;
x = (tm13 * z0x + tmm3 * c0x + tmm23 * c1x + t3 * z1x + 0.5) | 0;
y = (tm13 * z0y + tmm3 * c0y + tmm23 * c1y + t3 * z1y + 0.5) | 0;
return {
x: x,
y: y
}
}
<canvas width=240 height=100 />
终点的实际角度取决于您选择的终点附近的点(不是实际终点,即t=1
)。您可能需要计算总行长度并将其用作 t
的基础。
您也可能 运行 遇到角度不完全正确并且出现小间隙的情况。
您可以通过应用描边来掩盖这些间隙,或者根据之前计算的 angle/direction 稍微偏移上限(使用步骤 1 中的线性插值,只是负值 t
) ,或者使其准确的唯一其他方法是手动计算线的壁等,例如将其视为多边形并将其填充为单个对象。
@KenFrystenberg 很好地回答了你的问题。
这里有一个有趣的数学笔记,可以简化计算。
三次贝塞尔曲线的结束角度(和开始角度)可以直接从控制点计算:
// define 4 cubic Bezier control points
var cp0={x:20,y:20};
var cp1={x:20,y:100};
var cp2={x:170,y:70};
var cp3={x:200,y:20};
// calculate the ending angle from cp2 & cp3
var dx=cp3.x-cp2.x;
var dy=cp3.y-cp2.y;
var angle=Math.atan2(dy,dx);
下面是有关此数学笔记的示例代码和演示:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
var cp0={x:20,y:20};
var cp1={x:20,y:100};
var cp2={x:170,y:70};
var cp3={x:200,y:20};
ctx.lineWidth=10;
ctx.beginPath();
ctx.moveTo(20, 20);
ctx.bezierCurveTo(20, 100, 170, 70, 200, 20);
ctx.lineWidth=20
ctx.stroke();
ctx.beginPath();
ctx.arc(200,20,3,0,Math.PI*2);
ctx.closePath();
ctx.fillStyle='red';
ctx.fill();
var dx=cp3.x-cp2.x;
var dy=cp3.y-cp2.y;
var angle=Math.atan2(dy,dx);
var x=cp3.x+15*Math.cos(angle);
var y=cp3.y+15*Math.sin(angle);
ctx.beginPath();
ctx.moveTo(cp3.x,cp3.y);
ctx.lineTo(x,y);
ctx.lineWidth=1;
ctx.strokeStyle='red';
ctx.stroke();
body{ background-color: ivory; }
#canvas{border:1px solid red;}
<canvas id="canvas" width=300 height=300></canvas>
我正在尝试将 canvas 的末端变成一个点(有点像箭头,除了两侧不应该超出线的宽度......见下图有关行尾应该如何显示的示例)。
请参阅下图以了解行尾的外观示例。
我正在尝试使用行上限,但唯一可用的上限是 'round' 或 'square' (http://www.w3schools.com/tags/canvas_linecap.asp)。
下面的fiddle 是我要给末尾打分的那一行。
代码也在下面:
<body>
<canvas id="myCanvas" width="300" height="150" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.</canvas>
<script>
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.beginPath();
ctx.moveTo(20, 20);
ctx.bezierCurveTo(20, 100, 200, 100, 200, 20);
ctx.lineWidth=10
ctx.stroke();
</script>
</body>
不幸的是,canvas 中没有这样的功能。您将必须手动计算线的结果角度(这意味着您需要实施贝塞尔数学)。
然后用线的宽度根据那个角度自己画帽子。
第 1 步 - 寻找方向
让我们使用不是 90° 向上或侧面的一端使其更具挑战性:
var ctx = document.querySelector("canvas").getContext("2d");
// draw as normal
ctx.beginPath();
ctx.moveTo(20, 20);
ctx.bezierCurveTo(20, 100, 170, 70, 200, 20);
ctx.lineWidth=10
ctx.stroke();
// get two points from the end
var pt1 = order3(20, 20, 20, 100, 170, 70, 200, 20, 0.98);
var pt2 = order3(20, 20, 20, 100, 170, 70, 200, 20, 1);
// show direction
ctx.lineWidth = 2;
ctx.strokeStyle = "red";
ctx.beginPath();
ctx.moveTo(pt1.x, pt1.y);
ctx.lineTo(pt1.x + (pt2.x - pt1.x) * 10, pt1.y + (pt2.y - pt1.y) * 10);
ctx.stroke();
//B(t) = (1-t)^3 * z0 + 3t (1-t)^2 * c0 + 3 t^2 (1-t) * c1 + t^3 * z1 for 0 <=t <= 1
function order3(z0x, z0y, c0x, c0y, c1x, c1y, z1x, z1y, t) {
var tm1 = 1 - t, // (1 - t)
tm12 = tm1 * tm1, // (1 - t) ^ 2
tm13 = tm12 * tm1, // (1 - t) ^ 3
t2 = t * t, // t ^ 2
t3 = t2 * t, // t ^ 3
tmm3 = t * 3 * tm12, // 3 x t * (1 - t) ^ 2
tmm23 = t2 * 3 * tm1, // t ^ 2 * 3 * (1 - t)
x, y;
x = (tm13 * z0x + tmm3 * c0x + tmm23 * c1x + t3 * z1x + 0.5) | 0;
y = (tm13 * z0y + tmm3 * c0y + tmm23 * c1y + t3 * z1y + 0.5) | 0;
return {
x: x,
y: y
}
}
<canvas width=220 height=100 />
Update 或者作为 markE 点,你可以从控制点计算它(我的不好,我完全忘记了这个 - 感谢 markE) - 这可能是更好的方法大多数情况与使用“t
”方法相比。
为了完整起见,我将其包含在此处:
// calculate the ending angle from the two last nodes (cp2 and end point)
var dx = pt2.x - cp2.x; // assumes points and control points as objects
var dy = pt2.y - cp2.y;
var angle = Math.atan2(dy, dx);
更新结束
第 2 步 - 求角度和距离
我们需要计算实际角度,以便我们可以将其用于箭头的底部:
// get angle
var diffX = pt1.x - pt2.x; // see update comment above
var diffY = pt1.y - pt2.y;
var angle = Math.atan2(diffY, diffX);
var tangent = Math.atan2(diffX, -diffY);
(故意略微偏移)
第 3 步 - 画帽子
现在我们有足够的信息来画一个上限:
var ctx = document.querySelector("canvas").getContext("2d");
// draw as normal
ctx.beginPath();
ctx.moveTo(20, 20);
ctx.bezierCurveTo(20, 100, 170, 70, 200, 20);
ctx.lineWidth=40
ctx.stroke();
// get two points from the end
var pt1 = order3(20, 20, 20, 100, 170, 70, 200, 20, 0.98);
var pt2 = order3(20, 20, 20, 100, 170, 70, 200, 20, 1);
var diffX = pt1.x - pt2.x;
var diffY = pt1.y - pt2.y;
var angle = Math.atan2(diffY, diffX);
var tangent = Math.atan2(diffX, -diffY);
var lw = ctx.lineWidth * 0.5 - 0.5;
// draw cap
ctx.beginPath();
ctx.moveTo(pt2.x + lw * Math.cos(tangent), pt2.y + lw * Math.sin(tangent));
ctx.lineTo(pt2.x - lw * Math.cos(tangent), pt2.y - lw * Math.sin(tangent));
ctx.lineTo(pt1.x - lw * Math.cos(angle), pt1.y - lw * Math.sin(angle));
ctx.fill();
// due to inaccuracies, you may have to mask tiny gaps
ctx.lineWidth = 1;
ctx.stroke();
//B(t) = (1-t)^3 * z0 + 3t (1-t)^2 * c0 + 3 t^2 (1-t) * c1 + t^3 * z1 for 0 <=t <= 1
function order3(z0x, z0y, c0x, c0y, c1x, c1y, z1x, z1y, t) {
var tm1 = 1 - t, // (1 - t)
tm12 = tm1 * tm1, // (1 - t) ^ 2
tm13 = tm12 * tm1, // (1 - t) ^ 3
t2 = t * t, // t ^ 2
t3 = t2 * t, // t ^ 3
tmm3 = t * 3 * tm12, // 3 x t * (1 - t) ^ 2
tmm23 = t2 * 3 * tm1, // t ^ 2 * 3 * (1 - t)
x, y;
x = (tm13 * z0x + tmm3 * c0x + tmm23 * c1x + t3 * z1x + 0.5) | 0;
y = (tm13 * z0y + tmm3 * c0y + tmm23 * c1y + t3 * z1y + 0.5) | 0;
return {
x: x,
y: y
}
}
<canvas width=240 height=100 />
终点的实际角度取决于您选择的终点附近的点(不是实际终点,即t=1
)。您可能需要计算总行长度并将其用作 t
的基础。
您也可能 运行 遇到角度不完全正确并且出现小间隙的情况。
您可以通过应用描边来掩盖这些间隙,或者根据之前计算的 angle/direction 稍微偏移上限(使用步骤 1 中的线性插值,只是负值 t
) ,或者使其准确的唯一其他方法是手动计算线的壁等,例如将其视为多边形并将其填充为单个对象。
@KenFrystenberg 很好地回答了你的问题。
这里有一个有趣的数学笔记,可以简化计算。
三次贝塞尔曲线的结束角度(和开始角度)可以直接从控制点计算:
// define 4 cubic Bezier control points
var cp0={x:20,y:20};
var cp1={x:20,y:100};
var cp2={x:170,y:70};
var cp3={x:200,y:20};
// calculate the ending angle from cp2 & cp3
var dx=cp3.x-cp2.x;
var dy=cp3.y-cp2.y;
var angle=Math.atan2(dy,dx);
下面是有关此数学笔记的示例代码和演示:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
var cp0={x:20,y:20};
var cp1={x:20,y:100};
var cp2={x:170,y:70};
var cp3={x:200,y:20};
ctx.lineWidth=10;
ctx.beginPath();
ctx.moveTo(20, 20);
ctx.bezierCurveTo(20, 100, 170, 70, 200, 20);
ctx.lineWidth=20
ctx.stroke();
ctx.beginPath();
ctx.arc(200,20,3,0,Math.PI*2);
ctx.closePath();
ctx.fillStyle='red';
ctx.fill();
var dx=cp3.x-cp2.x;
var dy=cp3.y-cp2.y;
var angle=Math.atan2(dy,dx);
var x=cp3.x+15*Math.cos(angle);
var y=cp3.y+15*Math.sin(angle);
ctx.beginPath();
ctx.moveTo(cp3.x,cp3.y);
ctx.lineTo(x,y);
ctx.lineWidth=1;
ctx.strokeStyle='red';
ctx.stroke();
body{ background-color: ivory; }
#canvas{border:1px solid red;}
<canvas id="canvas" width=300 height=300></canvas>