我正在尝试将画布的末端变成一个点(有点像箭头,只是侧面不应该超出线的宽度...请参见下图,以了解线末端应如何使用的示例看)。

请参见下图,以获取线端外观的示例。


我正在尝试线上限,但唯一可用的上限是“圆形”或“正方形”(http://www.w3schools.com/tags/canvas_linecap.asp)。

下面的小提琴是我试图指出的终点。

http://jsfiddle.net/699ktkv8/

代码如下:

<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>

最佳答案

不幸的是,画布上没有这样的功能。您将必须手动计算直线的输出角度(这意味着您需要实现Bezier数学)。

然后使用该线的宽度根据该角度自己绘制盖帽。

第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 />





更新或作为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的基础。

您还可能遇到角度不完全正确且出现小的间隙的情况。

您可以通过应用笔划来掩盖这些间隙,也可以根据之前计算出的角度/方向(在步骤1中使用线性插值,仅对负t使用线性插值)来稍微偏移盖帽,或者采用其他唯一方法来获得盖帽准确的是手动计算线的壁等,例如将其视为多边形并将其填充为单个对象。

10-04 22:59