【问题标题】:Draw arrow on canvas tag在画布标签上绘制箭头
【发布时间】:2010-10-22 23:17:00
【问题描述】:

我想使用画布标签 javascript 绘制一个箭头。我已经使用二次函数完成了它,但是我在计算箭头的旋转角度时遇到了问题......

有人知道这件事吗?

谢谢

【问题讨论】:

    标签: javascript html5-canvas drawing


    【解决方案1】:

    尽可能简单。您必须自己添加 context.beginPath() 并添加 context.stroke():

    ctx = document.getElementById("c").getContext("2d");
    ctx.beginPath();
    canvas_arrow(ctx, 10, 30, 200, 150);
    canvas_arrow(ctx, 100, 200, 400, 50);
    canvas_arrow(ctx, 200, 30, 10, 150);
    canvas_arrow(ctx, 400, 200, 100, 50);
    ctx.stroke();
    
    
    function canvas_arrow(context, fromx, fromy, tox, toy) {
      var headlen = 10; // length of head in pixels
      var dx = tox - fromx;
      var dy = toy - fromy;
      var angle = Math.atan2(dy, dx);
      context.moveTo(fromx, fromy);
      context.lineTo(tox, toy);
      context.lineTo(tox - headlen * Math.cos(angle - Math.PI / 6), toy - headlen * Math.sin(angle - Math.PI / 6));
      context.moveTo(tox, toy);
      context.lineTo(tox - headlen * Math.cos(angle + Math.PI / 6), toy - headlen * Math.sin(angle + Math.PI / 6));
    }
    <html>
    
    <body>
      <canvas id="c" width="500" height="500"></canvas>
    
    
    </body>

    【讨论】:

    • 这会产生一个奇怪的形状,你想摆脱最后一个动作并在最后添加 lineTo(tox,toy)
    • lineWidth 不为 == 1 时功能无法正常工作
    • 对于更大的线宽,只需在context.lineTo(tox, toy); 之后添加context.moveTo(tox, toy);(参见:i.imgur.com/jMOsLM9.png
    • @danharper 图片是画布箭头吗?看起来很圆,如果是这样,你怎么做?
    • @raklos context.lineCap = 'round' 应该这样做。
    【解决方案2】:

    好的,所以当我试图自己解决这个问题时,这个页面上的第一个答案对我有很大帮助,尽管正如其他人已经说过的,如果你的线宽大于 1px,你会得到有趣的形状。其他人建议的修复方法几乎奏效了,但是在尝试使用更粗的箭头时我仍然遇到了一些问题。在玩了几个小时后,我能够将上述解决方案与我自己的一些修补程序结合起来,提出以下代码,可以在不扭曲箭头形状的情况下以你想要的任何厚度绘制箭头。

    function drawArrow(fromx, fromy, tox, toy){
                    //variables to be used when creating the arrow
                    var c = document.getElementById("myCanvas");
                    var ctx = c.getContext("2d");
                    const width = 22;
                    var headlen = 10;
                    // This makes it so the end of the arrow head is located at tox, toy, don't ask where 1.15 comes from
                    tox -= Math.cos(angle) * ((width*1.15));
                    toy -= Math.sin(angle) * ((width*1.15));
    
                    var angle = Math.atan2(toy-fromy,tox-fromx);
                    
                    //starting path of the arrow from the start square to the end square and drawing the stroke
                    ctx.beginPath();
                    ctx.moveTo(fromx, fromy);
                    ctx.lineTo(tox, toy);
                    ctx.strokeStyle = "#cc0000";
                    ctx.lineWidth = width;
                    ctx.stroke();
                    
                    //starting a new path from the head of the arrow to one of the sides of the point
                    ctx.beginPath();
                    ctx.moveTo(tox, toy);
                    ctx.lineTo(tox-headlen*Math.cos(angle-Math.PI/7),toy-headlen*Math.sin(angle-Math.PI/7));
                    
                    //path from the side point of the arrow, to the other side point
                    ctx.lineTo(tox-headlen*Math.cos(angle+Math.PI/7),toy-headlen*Math.sin(angle+Math.PI/7));
                    
                    //path from the side point back to the tip of the arrow, and then again to the opposite side point
                    ctx.lineTo(tox, toy);
                    ctx.lineTo(tox-headlen*Math.cos(angle-Math.PI/7),toy-headlen*Math.sin(angle-Math.PI/7));
    
                    //draws the paths created above
                    ctx.strokeStyle = "#cc0000";
                    ctx.lineWidth = width;
                    ctx.stroke();
                    ctx.fillStyle = "#cc0000";
                    ctx.fill();
                }
    

    现在这是我在程序中使用的代码。我发现消除失真问题的关键是继续从箭头的尖端到一侧点,到另一侧点,回到尖端,然后回到第一个侧点,然后做一个充满。这修正了箭头的形状。

    希望这会有所帮助!

    【讨论】:

    • 我无法在解决方案中找到箭头的确切点。你对此有什么想法吗?
    • 我从来没有用代码把它带入杂草中。我基本上只是玩尺寸,直到我画出箭头后它看起来正确为止。对不起,我没有更好的答案给你。 :\
    • 修改函数,在var angle = Math.atan2(toy-fromy,tox-fromx);tox -= Math.cos(angle) * ((width*1.15)); toy -= Math.sin(angle) * ((width*1.15)); 之后添加这两行代码,这将使箭头在 tox 和 toy 处结束。 @CodingbyRaj
    【解决方案3】:

    这是另一种绘制箭头的方法。它从这里使用三角形方法:https://*.com/a/8937325/1828637

    一个小辅助函数。

    function canvas_arrow(context, fromx, fromy, tox, toy, r){
        var x_center = tox;
        var y_center = toy;
    
        var angle;
        var x;
        var y;
    
        context.beginPath();
    
        angle = Math.atan2(toy-fromy,tox-fromx)
        x = r*Math.cos(angle) + x_center;
        y = r*Math.sin(angle) + y_center;
    
        context.moveTo(x, y);
    
        angle += (1/3)*(2*Math.PI)
        x = r*Math.cos(angle) + x_center;
        y = r*Math.sin(angle) + y_center;
    
        context.lineTo(x, y);
    
        angle += (1/3)*(2*Math.PI)
        x = r*Math.cos(angle) + x_center;
        y = r*Math.sin(angle) + y_center;
    
        context.lineTo(x, y);
    
        context.closePath();
    
        context.fill();
    }
    

    这是一个在行首和行尾绘制箭头的演示。

    var can = document.getElementById('c');
    var ctx = can.getContext('2d');
    
    ctx.lineWidth = 10;
    ctx.strokeStyle = 'steelblue';
    ctx.fillStyle = 'steelbllue'; // for the triangle fill
    ctx.lineJoin = 'butt';
    
    ctx.beginPath();
    ctx.moveTo(50, 50);
    ctx.lineTo(150, 150);
    ctx.stroke();
    
    canvas_arrow(ctx, 50, 50, 150, 150, 10);
    canvas_arrow(ctx, 150, 150, 50, 50, 10);
    
    function canvas_arrow(context, fromx, fromy, tox, toy, r){
    	var x_center = tox;
    	var y_center = toy;
    	
    	var angle;
    	var x;
    	var y;
    	
    	context.beginPath();
    	
    	angle = Math.atan2(toy-fromy,tox-fromx)
    	x = r*Math.cos(angle) + x_center;
    	y = r*Math.sin(angle) + y_center;
    
    	context.moveTo(x, y);
    	
    	angle += (1/3)*(2*Math.PI)
    	x = r*Math.cos(angle) + x_center;
    	y = r*Math.sin(angle) + y_center;
    	
    	context.lineTo(x, y);
    	
    	angle += (1/3)*(2*Math.PI)
    	x = r*Math.cos(angle) + x_center;
    	y = r*Math.sin(angle) + y_center;
    	
    	context.lineTo(x, y);
    	
    	context.closePath();
    	
    	context.fill();
    }
    &lt;canvas id="c" width=300 height=300&gt;&lt;/canvas&gt;

    【讨论】:

    • 为什么要使用 (1/3)*(2*Math.PI) 而不仅仅是 Math.PI / 1.5?它给出了完全相同的结果,但操作更少。
    • 尖端是偏移的(它以线端为中心,而不是完全指向它),但在第一个角度计算后添加x_center -= r * Math.cos(angle); y_center -= r * Math.sin(angle); 可以解决这个问题。
    【解决方案4】:

    你可以这样做:

    ctx.save();
    ctx.translate(xOrigin, yOrigin);
    ctx.rotate(angle);
     // draw your arrow, with its origin at [0, 0]
    ctx.restore();
    

    【讨论】:

      【解决方案5】:

      var canvas = document.getElementById('canvas');
      var ctx = canvas.getContext('2d');
      
      ctx.clearRect(0, 0, canvas.width, canvas.height);	
      arrow({x: 10, y: 10}, {x: 100, y: 170}, 10);
      arrow({x: 40, y: 250}, {x: 10, y: 70}, 5);
      
      
      function arrow (p1, p2, size) {
        var angle = Math.atan2((p2.y - p1.y) , (p2.x - p1.x));
        var hyp = Math.sqrt((p2.x - p1.x) * (p2.x - p1.x) + (p2.y - p1.y) * (p2.y - p1.y));
      
        ctx.save();
        ctx.translate(p1.x, p1.y);
        ctx.rotate(angle);
      
        // line
        ctx.beginPath();	
        ctx.moveTo(0, 0);
        ctx.lineTo(hyp - size, 0);
        ctx.stroke();
      
        // triangle
        ctx.fillStyle = 'blue';
        ctx.beginPath();
        ctx.lineTo(hyp - size, size);
        ctx.lineTo(hyp, 0);
        ctx.lineTo(hyp - size, -size);
        ctx.fill();
      
        ctx.restore();
      }
      &lt;canvas id = "canvas" width = "300" height = "400"&gt;&lt;/canvas&gt;

      【讨论】:

      • 谢谢你,先生,你帮了我很大的忙,我需要的东西很好
      【解决方案6】:

      Typescript 版本,当行宽 >> 1 时固定箭头提示

      function canvas_arrow( context, fromx, fromy, tox, toy ) {
          const dx = tox - fromx;
          const dy = toy - fromy;
          const headlen = Math.sqrt( dx * dx + dy * dy ) * 0.3; // length of head in pixels
          const angle = Math.atan2( dy, dx );
          context.beginPath();
          context.moveTo( fromx, fromy );
          context.lineTo( tox, toy );
          context.stroke();
          context.beginPath();
          context.moveTo( tox - headlen * Math.cos( angle - Math.PI / 6 ), toy - headlen * Math.sin( angle - Math.PI / 6 ) );
          context.lineTo( tox, toy );
          context.lineTo( tox - headlen * Math.cos( angle + Math.PI / 6 ), toy - headlen * Math.sin( angle + Math.PI / 6 ) );
          context.stroke();
      }
      

      【讨论】:

      • 最佳答案,我想。你说 TypeScript,但我没有看到任何类型声明?这在普通 JS 中无需修改即可工作。
      • @RogerDahl 我的意思是它会立即与tsc 一起编译,因此您可以立即在 .ts 文件中使用它。
      【解决方案7】:

      给定尺寸和起始位置,以下代码将为您绘制箭头。

      function draw_arrow(context, startX, startY, size) {
        var arrowX = startX + 0.75 * size;
        var arrowTopY = startY - 0.707 * (0.25 * size);
        var arrowBottomY = startY + 0.707 * (0.25 * size);
        context.moveTo(startX, startY);
        context.lineTo(startX + size, startX);
        context.lineTo(arrowX, arrowTopY);
        context.moveTo(startX + size, startX);
        context.lineTo(arrowX, arrowBottomY);
        context.stroke();
      }
      window.onload = function() {
        var canvas = document.getElementById("myCanvas");
        var context = canvas.getContext("2d");
        var startX = 50;
        var startY = 50;
        var size = 100;
        context.lineWidth = 2;
        draw_arrow(context, startX, startY, size);
      };
      body {
        margin: 0px;
        padding: 0px;
      }
      
      #myCanvas {
        border: 1px solid #9C9898;
      }
      <!DOCTYPE HTML>
      <html>
      
      <body onmousedown="return false;">
        <canvas id="myCanvas" width="578" height="200"></canvas>
      </body>
      
      </html>

      【讨论】:

        【解决方案8】:

        这段代码和 Titus Cieslewski 的解决方案类似,可能箭头更漂亮一点:

        function canvasDrawArrow(context, fromx, fromy, tox, toy) {
            var headlen = 10.0;
            var back = 4.0;
            var angle1 = Math.PI / 13.0;
            var angle2 = Math.atan2(toy - fromy, tox - fromx);
            var diff1 = angle2 - angle1;
            var diff2 = angle2 + angle1;
            var xx = getBack(back, fromx, fromy, tox, toy);
            var yy = getBack(back, fromy, fromx, toy, tox);
        
            context.moveTo(fromx, fromy);
            context.lineTo(tox, toy);
        
            context.moveTo(xx, yy);
            context.lineTo(xx - headlen * Math.cos(diff1), yy - headlen * Math.sin(diff1));
        
            context.moveTo(xx, yy);
            context.lineTo(xx - headlen * Math.cos(diff2), yy - headlen * Math.sin(diff2));
        }
        
        function getBack(len, x1, y1, x2, y2) {
            return x2 - (len * (x2 - x1) / (Math.sqrt(Math.pow(y2 - y1, 2) + Math.pow(x2 - x1, 2))));
        }
        

        这适用于lineWidth &gt; 1。在绘制xy轴时可以派上用场

        【讨论】:

          【解决方案9】:
          function RTEShape()
          {   
              this.x = 50;
            this.y = 50;
            this.w = 100; // default width and height?
            this.h = 100;
            this.fill = '#444444';
            this.text = "Test String";
            this.type;
            this.color;
            this.size = 6;    
          
              // The selection color and width. Right now we have a red selection with a small width
              this.mySelColor = '#CC0000';
              this.mySelWidth = 2;
              this.mySelBoxColor = 'darkred';// New for selection boxes
              this.mySelBoxSize = 6;
          }
          
          RTEShape.prototype.buildArrow = function(canvas)
          {
              this.type = "arrow";
          
            // Make sure we don't execute when canvas isn't supported
            if (canvas.getContext){
          
              // use getContext to use the canvas for drawing
              var ctx = canvas.getContext('2d');           
          
              var oneThirdX = this.x + (this.w/3);             
              var twoThirdX = this.x + ((this.w*2)/3);
          
              var oneFifthY = this.y - (this.y/5);    
              var twoFifthY = this.y - ((this.y*3)/5);
          
              /**/
              //ctx.beginPath();
              ctx.moveTo(oneThirdX,this.y); // 125,125
              ctx.lineTo(oneThirdX,oneFifthY); // 125,105
          
              ctx.lineTo(this.x*2,oneFifthY); // 225,105      
              ctx.lineTo(this.x*2,twoFifthY); // 225,65
          
              ctx.lineTo(oneThirdX,twoFifthY); // 125,65      
              ctx.lineTo(oneThirdX,(this.y/5)); // 125,45
          
              ctx.lineTo(this.x,(this.y+(this.y/5))/2); // 45,85
          
                  ctx.fillStyle = "green";
              ctx.fill();
          
              ctx.fillStyle = "yellow";
              ctx.fillRect(this.x,this.y,this.w,this.h);
          
            } else {
              alert('Error on buildArrow!\n'+err.description);
            }
          }
          

          【讨论】:

            【解决方案10】:

            您好,非常感谢您的建议。

            我可以建议你放弃笨重的 atan 吗?您也可以使用线性代数来增加或减少角度:

            var cospix=0.866025404; //cosinus of pi/6
            
            function canvas_arrow(context, fromx, fromy, tox, toy) {
            ctx.strokeStyle = '#AA0000';
            var headlen = 10; // length of head in pixels
            var dx = tox - fromx;
            var dy = toy - fromy;
            var length = Math.sqrt(dy*dy + dx*dx); //length of arrow
            var sina = dy/length, cosa = dx/length; //computing sin and cos of arrow angle
            var cosp=cosa*cospix-0.5*sina, cosm=cosa*cospix+0.5*sina,
            sinp=cosa*0.5+cospix*sina, sinm=cospix*sina-cosa*0.5;
            //computing cos and sin of arrow angle plus pi/6, respectively minus pi/6
            //(p for plus, m for minus at the end of variable's names)
            context.moveTo(fromx, fromy);
            context.lineTo(tox, toy);
            context.lineTo(tox - headlen * cosm, toy - headlen * sinm); //computing coordinates using the cos and sin computed above
            context.moveTo(tox, toy);
            context.lineTo(tox - headlen * cosp, toy - headlen * sinp); //computing coordinates using the cos and sin computed above
            }
            

            【讨论】:

              【解决方案11】:

              您可以推动矩阵、旋转它、绘制箭头然后弹出矩阵。

              【讨论】:

                【解决方案12】:

                我已经为此苦苦挣扎了一段时间。 我需要在 javascript 和 c# 中都这样做。对于 javascript,我找到了一个不错的库 jCanvas

                我的主要问题是绘制漂亮的箭头,而 jCanvas 做得很好。 对于我的 c# 项目,我对 jCanvas 代码进行了逆向工程。

                希望这对某人有所帮助

                【讨论】:

                  【解决方案13】:

                  这是可行的解决方案

                  function draw_arrow(ctx,fx,fy,tx,ty){ //ctx is the context
                      var angle=Math.atan2(ty-fy,tx-fx);
                      ctx.moveTo(fx,fy); ctx.lineTo(tx,ty);
                      var w=3.5; //width of arrow to one side. 7 pixels wide arrow is pretty
                      ctx.strokeStyle="#4d4d4d"; ctx.fillStyle="#4d4d4d";
                      angle=angle+Math.PI/2; tx=tx+w*Math.cos(angle); ty=ty+w*Math.sin(angle);
                      ctx.lineTo(tx,ty);
                    //Drawing an isosceles triangle of sides proportional to 2:7:2
                      angle=angle-1.849096; tx=tx+w*3.5*Math.cos(angle); ty=ty+w*3.5*Math.sin(angle);
                      ctx.lineTo(tx,ty);
                      angle=angle-2.584993; tx=tx+w*3.5*Math.cos(angle); ty=ty+w*3.5*Math.sin(angle);
                      ctx.lineTo(tx,ty);
                      angle=angle-1.849096; tx=tx+w*Math.cos(angle); ty=ty+w*Math.sin(angle);
                      ctx.lineTo(tx,ty);
                      ctx.stroke(); ctx.fill();
                  }
                  

                  【讨论】:

                    【解决方案14】:

                    虽然这个问题得到了大部分答案,但我发现答案很缺乏。最佳答案会产生难看的箭头,当使用 1 以外的宽度时,许多箭头会超出点,而其他的则有不必要的步骤。

                    这是最简单的答案,它绘制一个漂亮的箭头(用颜色填充的适当三角形),并缩回箭头的点以考虑线的宽度。

                    ctx = document.getElementById('canvas').getContext('2d');
                    
                    /* Draw barrier */
                    ctx.beginPath();
                    ctx.moveTo(50, 30);
                    ctx.lineTo(450, 30);
                    ctx.stroke();
                    
                    draw_arrow(50, 180, 150, 30);
                    draw_arrow(250, 180, 250, 30);
                    draw_arrow(450, 180, 350, 30);
                    
                    function draw_arrow(x0, y0, x1, y1) {
                      const width = 8;
                      const head_len = 16;
                      const head_angle = Math.PI / 6;
                      const angle = Math.atan2(y1 - y0, x1 - x0);
                    
                      ctx.lineWidth = width;
                    
                      /* Adjust the point */
                      x1 -= width * Math.cos(angle);
                      y1 -= width * Math.sin(angle);
                    
                      ctx.beginPath();
                      ctx.moveTo(x0, y0);
                      ctx.lineTo(x1, y1);
                      ctx.stroke();
                    
                      ctx.beginPath();
                      ctx.lineTo(x1, y1);
                      ctx.lineTo(x1 - head_len * Math.cos(angle - head_angle), y1 - head_len * Math.sin(angle - head_angle));
                      ctx.lineTo(x1 - head_len * Math.cos(angle + head_angle), y1 - head_len * Math.sin(angle + head_angle));
                      ctx.closePath();
                      ctx.stroke();
                      ctx.fill();
                    }
                    &lt;canvas id="canvas" width="500" height="180"&gt;&lt;/canvas&gt;

                    【讨论】:

                      最近更新 更多