HTML 5画布完整箭头

4
我正在使用wPaint插件,尝试添加一些功能。我需要的是画出一个带箭头的线。我已经尝试了几乎所有我能想到的方法,但我只能得到箭头的一半(类似于 <-----,但箭头只延伸到底部或顶部,而从不同时延伸)。
这是绘制带有半箭头的线条的函数:
  drawArrowMove: function(e, _self)
  {
        var xo = _self.canvasTempLeftOriginal;
        var yo = _self.canvasTempTopOriginal;

        if(e.pageX < xo) { e.x = e.x + e.w; e.w = e.w * -1}
        if(e.pageY < yo) { e.y = e.y + e.h; e.h = e.h * -1}

        _self.ctxTemp.lineJoin = "round";
        _self.ctxTemp.beginPath();
        _self.ctxTemp.moveTo(e.x, e.y);
        _self.ctxTemp.lineTo(e.x + e.w, e.y + e.h);

        _self.ctxTemp.closePath();
        _self.ctxTemp.moveTo(e.x, e.y);

        _self.ctxTemp.lineTo(15,10);                   
        _self.ctxTemp.stroke();
  }

任何帮助/想法/提示都将有所帮助。
谢谢。

你能找到解决方案吗?我也在使用Wapint并尝试将线条变成箭头。我尝试了你的代码,它只产生了一半的箭头头,并且在左边。 - Umer Fayyaz
5个回答

14

这是创建一条线对象,在两端绘制箭头的方法

有趣的部分是像这样计算箭头角度:

var startRadians=Math.atan((this.y2-this.y1)/(this.x2-this.x1));
startRadians+=((this.x2>=this.x1)?-90:90)*Math.PI/180;

var endRadians=Math.atan((this.y2-this.y1)/(this.x2-this.x1));
endRadians+=((this.x2>=this.x1)?90:-90)*Math.PI/180;

其余部分只需绘制线条和2个三角形以表示箭头的位置,根据计算得出旋转角度。

Line.prototype.drawArrowhead=function(ctx,x,y,radians){
    ctx.save();
    ctx.beginPath();
    ctx.translate(x,y);
    ctx.rotate(radians);
    ctx.moveTo(0,0);
    ctx.lineTo(5,20);
    ctx.lineTo(-5,20);
    ctx.closePath();
    ctx.restore();
    ctx.fill();
}

这里有代码和一个Fiddle:http://jsfiddle.net/m1erickson/Sg7EZ/

<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>

<style>
    body{ background-color: ivory; }
    canvas{border:1px solid red;}
</style>

<script>
$(function(){

    var canvas=document.getElementById("canvas");
    var context=canvas.getContext("2d");

    function Line(x1,y1,x2,y2){
        this.x1=x1;
        this.y1=y1;
        this.x2=x2;
        this.y2=y2;
    }
    Line.prototype.drawWithArrowheads=function(ctx){

        // arbitrary styling
        ctx.strokeStyle="blue";
        ctx.fillStyle="blue";
        ctx.lineWidth=1;

        // draw the line
        ctx.beginPath();
        ctx.moveTo(this.x1,this.y1);
        ctx.lineTo(this.x2,this.y2);
        ctx.stroke();

        // draw the starting arrowhead
        var startRadians=Math.atan((this.y2-this.y1)/(this.x2-this.x1));
        startRadians+=((this.x2>this.x1)?-90:90)*Math.PI/180;
        this.drawArrowhead(ctx,this.x1,this.y1,startRadians);
        // draw the ending arrowhead
        var endRadians=Math.atan((this.y2-this.y1)/(this.x2-this.x1));
        endRadians+=((this.x2>this.x1)?90:-90)*Math.PI/180;
        this.drawArrowhead(ctx,this.x2,this.y2,endRadians);


    }
    Line.prototype.drawArrowhead=function(ctx,x,y,radians){
        ctx.save();
        ctx.beginPath();
        ctx.translate(x,y);
        ctx.rotate(radians);
        ctx.moveTo(0,0);
        ctx.lineTo(5,20);
        ctx.lineTo(-5,20);
        ctx.closePath();
        ctx.restore();
        ctx.fill();
    }

    // create a new line object
    var line=new Line(50,50,150,150);
    // draw the line
    line.drawWithArrowheads(context);

}); // end $(function(){});
</script>

</head>

<body>
    <canvas id="canvas" width=300 height=300></canvas>
</body>
</html>

太棒了!非常感谢! - Val Entin
1
请确保您查看了@PierreDuc的答案,并将((this.x2 > this.x1)?-90:90)*Math.PI/180;替换为((this.x2 >= this.x1)?-90:90)*Math.PI/180; - 即使用大于或等于。 - ElDog
2
只有当线条是倾斜的时候,它对我不起作用。如果线条为0或90°,则可以正常工作。我花了1个小时最终发现了这一点:https://dev59.com/nHRA5IYBdhLWcg3w1BmW#26080467 - philippe
只适用于某些角度范围,对于其他角度会出错。将“endRadians+=((this.x2>this.x1)?90:-90)*Math.PI/180;”这部分更新弧度的代码删除并修改为“ctx.lineTo(-5,20); ctx.lineTo(-5,-20);”,我成功解决了问题。 - Manik

5

如果是垂直线,就会出现问题。

尝试使用下面的代码:

var line = new Line(50, 50, 50, 275)


4

简化版

关键区别。使用Math.atan2可省去if的需求。

此方法还将箭头放置在线条的末端而非超出末端。

换句话说,这就是

此内容

start         end
    |<------->|

与此相比

   <|---------|>

function arrow(ctx, x1, y1, x2, y2, start, end) {
  var rot = -Math.atan2(x1 - x2, y1 - y2);
  ctx.beginPath();
  ctx.moveTo(x1, y1);
  ctx.lineTo(x2, y2);
  ctx.stroke();
  if (start) {
    arrowHead(x1, y1, rot);
  }
  if (end) {
    arrowHead(x2, y2, rot + Math.PI);
  }
}

function arrowHead(x, y, rot) {
  ctx.save();
  ctx.translate(x, y);
  ctx.rotate(rot);
  ctx.beginPath();
  ctx.moveTo(0, 0);
  ctx.lineTo(-5, -12);
  ctx.lineTo(5, -12);
  ctx.closePath();
  ctx.fill();
  ctx.restore();
}

// test it -------

var ctx = document.createElement("canvas").getContext("2d");
document.body.appendChild(ctx.canvas);


// draw some arrows
ctx.save();
ctx.translate(ctx.canvas.width / 2, ctx.canvas.height / 2);

for (var ii = 0; ii <= 12; ++ii) {
  var u = ii / 12;
  var color = hsl(u * 360, 1, 0.5);
  ctx.fillStyle = color;
  ctx.strokeStyle = color;
  var a = u * Math.PI;
  var x = Math.cos(a) * 120;
  var y = Math.sin(a) * 70;
  arrow(ctx, -x, -y, x, y, true, true);
  
  // draw the end points to see the arrowheads match
  ctx.fillStyle = "#000";
  ctx.fillRect(-x - 1, -y - 1, 3, 3);
  ctx.fillRect( x - 1,  y - 1, 3, 3);
}

ctx.restore();

function hsl(h, s, l) {
  return `hsl(${h},${s * 100}%,${l * 100}%)`;
}
canvas { border: 1px solid black; }

以上解决方案的一个问题是,如果你加粗了线条,它会穿过箭头。这不难修复,但你需要计算线条的像素长度,然后从两侧减去箭头的大小。

可以像这样:

function arrow(ctx, x1, y1, x2, y2, start, end) {
  var dx = x2 - x1;
  var dy = y2 - y1;
  var rot = -Math.atan2(dx, dy);
  var len = Math.sqrt(dx * dx + dy * dy);
  var arrowHeadLen = 10;
  ctx.save();
  ctx.translate(x1, y1);
  ctx.rotate(rot);
  ctx.beginPath();    
  ctx.moveTo(0, start ? arrowHeadLen : 0);
  ctx.lineTo(0, len - (end ? arrowHeadLen : 0));
  ctx.stroke();
  if (end) {
    ctx.save();
    ctx.translate(0, len);
    arrowHead(ctx);
    ctx.restore();
  }
  if (start) {
    ctx.rotate(Math.PI);
    arrowHead(ctx);
  }
  ctx.restore();
}

function arrowHead(ctx) {
  ctx.beginPath();
  ctx.moveTo(0, 0);
  ctx.lineTo(-5, -12);
  ctx.lineTo(5, -12);
  ctx.closePath();
  ctx.fill();
}

// test it -------

var ctx = document.createElement("canvas").getContext("2d");
document.body.appendChild(ctx.canvas);


// draw some arrows
ctx.save();
ctx.translate(ctx.canvas.width / 2, ctx.canvas.height / 2);

for (var ii = 0; ii < 12; ++ii) {
  var u = ii / 12;
  var color = hsl(u * 360, 1, 0.5);
  ctx.fillStyle = color;
  ctx.strokeStyle = color;
  var a = u * Math.PI;
  var x = Math.cos(a) * 120;
  var y = Math.sin(a) * 70;
  arrow(ctx, -x, -y, x, y, true, true);
  ctx.fillStyle = "#000";  // mark the ends so we can see where they are
  ctx.fillRect(-x - 1, -y - 1, 3, 3);
  ctx.fillRect( x - 1,  y - 1, 3, 3);
}

ctx.restore();

function hsl(h, s, l) {
  return `hsl(${h},${s * 100}%,${l * 100}%)`;
}
canvas { border: 1px solid black; }

换句话说,第一种解决方案绘制的箭头如下所示: top arrow 而第二种解决方案绘制的箭头如下所示: bottom arrow

4
作为对markE答案的补充,结合user1707810的评论:
两个区块的(开始/结束)弧度:
 - ((this.x2 > this.x1)?-90:90)*Math.PI/180;

应该更改为:

 - ((this.x2 >= this.x1)?-90:90)*Math.PI/180;

1

我的简单解决方案

ctx.beginPath(); 
ctx.moveTo(ax,ay);
ctx.lineTo(bx,by);
ctx.stroke();
ctx.closePath();
angle=Math.PI+Math.atan2(by-ay,bx-ax);
angle1=angle+Math.PI/6;
angle2=angle-Math.PI/6;
ctx.beginPath(); 
ctx.moveTo(bx,by);
ctx.arc(bx,by,5,angle1,angle2,true);
ctx.lineTo(bx,by);
ctx.fill();
ctx.closePath();

我在尝试将箭头头部变大,但是却无法避免底部变圆。你有什么建议吗? - skybondsor

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接