<canvas>中的点划线

48
11个回答

68

有趣的问题!我编写了一个自定义虚线实现; 你可以在这里尝试一下。我采用了Adobe Illustrator的方法,允许您指定一个虚线/间隙长度的数组。

为了stackoverflow的永久保存,这是我的实现(稍微修改了s/o线宽):

var CP = window.CanvasRenderingContext2D && CanvasRenderingContext2D.prototype;
if (CP && CP.lineTo){
  CP.dashedLine = function(x,y,x2,y2,dashArray){
    if (!dashArray) dashArray=[10,5];
    if (dashLength==0) dashLength = 0.001; // Hack for Safari
    var dashCount = dashArray.length;
    this.moveTo(x, y);
    var dx = (x2-x), dy = (y2-y);
    var slope = dx ? dy/dx : 1e15;
    var distRemaining = Math.sqrt( dx*dx + dy*dy );
    var dashIndex=0, draw=true;
    while (distRemaining>=0.1){
      var dashLength = dashArray[dashIndex++%dashCount];
      if (dashLength > distRemaining) dashLength = distRemaining;
      var xStep = Math.sqrt( dashLength*dashLength / (1 + slope*slope) );
      if (dx<0) xStep = -xStep;
      x += xStep
      y += slope*xStep;
      this[draw ? 'lineTo' : 'moveTo'](x,y);
      distRemaining -= dashLength;
      draw = !draw;
    }
  }
}

要在从20,150170,10的线上画出30px长、后跟10px间隔的虚线,您可以使用:

myContext.dashedLine(20,150,170,10,[30,10]);

要绘制交替的短横线和点,请使用(例如):

myContext.lineCap   = 'round';
myContext.lineWidth = 4; // Lines 4px wide, dots of diameter 4
myContext.dashedLine(20,150,170,10,[30,10,0,10]);

在使用圆形线帽以及长度为0的“非常短”虚线时,你的线路上会出现点。

如果有人知道如何访问画布上下文路径的当前点,请告诉我,这将允许我将其写成ctx.dashTo(x,y,dashes)而不需要在方法调用中重新指定起始点。


1
@Sam,你不是唯一一个认为我们不应该被迫在画布上绘制虚线的人:http://lists.whatwg.org/pipermail/whatwg-whatwg.org/2007-May/011224.html - Phrogz
1
@Jon JavaScript的“布尔运算符”实际上是“守卫运算符”。使用短路求值,它们返回最后评估的“真实性”的值。以下是一些正确的语句:42 == 42 || "hi"; "hi" == false || "hi"; true == 17 && true; false == 17 && false; "" == "" && false。打开您友好的开发人员控制台并自行尝试。 :) 第一行将CP设置为原型(如果存在CanvasRenderingContext2D),否则将其设置为undefined - Phrogz
4
当x坐标的差为0时,你正在除以零,这是无效的。 - Honza Brabec
太棒了。你在 Stack Overflow 上有一些我最喜欢的答案。 - alex
1
@HonzaBrabec 是的,你说得对。这个问题已经通过使用“1e15”技巧解决了。还有其他更简洁的方法可以解决,但这需要改动代码最少并且足够有效。 - Phrogz
显示剩余2条评论

43

这是 Phrogz 代码的简化版,它利用了 Canvas 的内置转换功能,并处理特殊情况,例如当 dx = 0 时。

var CP = window.CanvasRenderingContext2D && CanvasRenderingContext2D.prototype;
if (CP.lineTo) {
    CP.dashedLine = function(x, y, x2, y2, da) {
        if (!da) da = [10,5];
        this.save();
        var dx = (x2-x), dy = (y2-y);
        var len = Math.sqrt(dx*dx + dy*dy);
        var rot = Math.atan2(dy, dx);
        this.translate(x, y);
        this.moveTo(0, 0);
        this.rotate(rot);       
        var dc = da.length;
        var di = 0, draw = true;
        x = 0;
        while (len > x) {
            x += da[di++ % dc];
            if (x > len) x = len;
            draw ? this.lineTo(x, 0): this.moveTo(x, 0);
            draw = !draw;
        }       
        this.restore();
    }
}

我认为我的计算是正确的,而且它似乎渲染得很好。


3
想要提醒一下,Phrogz的解决方案不适用于竖线。这个解决方案可行。 - bits
1
最好防止重复定义dashedLine函数:CP.dashedLine = CP.dashedLine || function... - Drew Noakes
此外,如果!window.CanvasRenderingContext2D,则CP可能未定义,此时CP.lineTo也会失败。 - Drew Noakes

10

目前至少setLineDash([5,10])在Chrome上可用,ctx.mozDash = [5,10]在FF上可用:

var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");

if ( ctx.setLineDash !== undefined )   ctx.setLineDash([5,10]);
if ( ctx.mozDash !== undefined )       ctx.mozDash = [5,10];

ctx.beginPath();              
ctx.lineWidth="2";
ctx.strokeStyle="green";
ctx.moveTo(0,75);
ctx.lineTo(250,75);
ctx.stroke();

将设置为 null 可以使线条变成实线。


IE 11 似乎也支持 setLineDash: https://msdn.microsoft.com/zh-cn/library/dn265063(v=vs.85).aspx - A. K-R
1
截至2015年12月,根据DMN的说法,所有当前的浏览器都支持setLineDash函数:链接 - Marconius

7

Phroz的解决方案很好。但是当我在我的应用程序中使用它时,我发现了两个错误。

以下代码是Phroz的代码进行调试后(并且为了可读性进行了重构)的版本。

// Fixed: Minus xStep bug (when x2 < x, original code bugs)
// Fixed: Vertical line bug (when abs(x - x2) is zero, original code bugs because of NaN)
var CP = window.CanvasRenderingContext2D && CanvasRenderingContext2D.prototype;
if(CP && CP.lineTo) CP.dashedLine = function(x, y, x2, y2, dashArray){
    if(! dashArray) dashArray=[10,5];
    var dashCount = dashArray.length;
    var dx = (x2 - x);
    var dy = (y2 - y);
    var xSlope = (Math.abs(dx) > Math.abs(dy));
    var slope = (xSlope) ? dy / dx : dx / dy;

    this.moveTo(x, y);
    var distRemaining = Math.sqrt(dx * dx + dy * dy);
    var dashIndex = 0;
    while(distRemaining >= 0.1){
        var dashLength = Math.min(distRemaining, dashArray[dashIndex % dashCount]);
        var step = Math.sqrt(dashLength * dashLength / (1 + slope * slope));
        if(xSlope){
            if(dx < 0) step = -step;
            x += step
            y += slope * step;
        }else{
            if(dy < 0) step = -step;
            x += slope * step;
            y += step;
        }
        this[(dashIndex % 2 == 0) ? 'lineTo' : 'moveTo'](x, y);
        distRemaining -= dashLength;
        dashIndex++;
    }
}

6

几个月前已经验证并修复。 - antony.trupe

5

有一种更简单的方法来实现这个。根据http://www.w3.org/TR/2dcontext/#dom-context-2d-strokestyle,strokeStyle接受字符串、CanvasGradients或CanvasPatterns。因此,我们只需要取一个像这样的图像:

  <img src="images/dashedLineProto.jpg" id="cvpattern1" width="32" height="32" />

将其加载到画布中,然后用它绘制我们的小矩形。

  var img=document.getElementById("cvpattern1");
  var pat=ctx.createPattern(img,"repeat");
  ctx.strokeStyle = pat;
  ctx.strokeRect(20,20,150,100);

这并不会得到一个完美的虚线,但它非常简单和可修改。当你画非水平或垂直的线时,结果可能会变得不完美,此时点状图案可能有所帮助。

另外,请记住,在您尝试在代码中使用外部来源的图像时,SOP适用。


4

看起来context.setLineDash基本上已经实现了。 请参阅此链接

" context.setLineDash([5]) 将产生一个虚线,其中破折号和空格的大小都为5像素。"


3

2

至少在Firefox中支持它。

ctx.mozDash = [5,10];

似乎之前有ctx.webkitLineDash方法可用,但由于存在一些兼容性问题而被移除。
根据W3C规范,应使用ctx.setLineDash([5,10]);,但目前似乎还没有在任何地方实现。

2

我修改了 dashedLine 函数以支持偏移。如果浏览器支持 ctx.setLineDashctx.lineDashOffset,则使用本机虚线。

示例:http://jsfiddle.net/mLY8Q/6/

var CP = window.CanvasRenderingContext2D && CanvasRenderingContext2D.prototype;
if (CP.lineTo) {

    CP.dashedLine = CP.dashedLine || function (x, y, x2, y2, da, offset) {

        if (!da) da = [10, 5];
        if (!offset) offset = 0;

        if (CP.setLineDash && typeof (CP.lineDashOffset) == "number") {
            this.save();
            this.setLineDash(da);
            this.lineDashOffset = offset;

            this.moveTo(x, y);
            this.lineTo(x2, y2);

            this.restore();
            return;
        }


        this.save();
        var dx = (x2 - x),
            dy = (y2 - y);
        var len = Math.sqrt(dx * dx + dy * dy);
        var rot = Math.atan2(dy, dx);
        this.translate(x, y);
        this.moveTo(0, 0);
        this.rotate(rot);
        var dc = da.length;
        var di = 0;

        var patternLength = 0;
        for (var i = 0; i < dc; i++) {
            patternLength += da[i];
        }
        if (dc % 2 == 1) {
            patternLength *= 2;
        }

        offset = offset % patternLength;
        if (offset < 0) {
            offset += patternLength;
        }

        var startPos = 0;
        var startSegment = 0;
        while (offset >= startPos) {



            if (offset >= startPos + da[startSegment % dc]) {
                startPos += da[startSegment % dc];
                startSegment++;
            } else {
                offset = Math.abs(offset - startPos);
                break;
            }


            if (startSegment > 100) break;
        }
        draw = startSegment % 2 === 0;
        x = 0;
        di = startSegment;


        while (len > x) {
            var interval = da[di++ % dc];
            if (x < offset) {
                interval = Math.max(interval - offset, 1);
                offset = 0;
            }

            x += interval;
            if (x > len) x = len;
            draw ? this.lineTo(x, 0) : this.moveTo(x, 0);
            draw = !draw;
        }
        this.restore();
    };
}

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