简体   繁体   中英

dotted stroke in <canvas>

I guess it is not possible to set stroke property such as CSS which is quite easy. With CSS we have dashed, dotted, solid but on canvas when drawing lines/or strokes this doesn't seem to be an option. How have you implemented this?

I've seen some examples but they are really long for such a silly function.

For example:

http://groups.google.com/group/javascript-information-visualization-toolkit/browse_thread/thread/22000c0d0a1c54f9?pli=1

Fun question! I've written a custom implementation of dashed lines; you can try it out here . I took the route of Adobe Illustrator and allow you to specify an array of dash/gap lengths.

For stackoverflow posterity, here's my implementation (slightly altered for s/o line widths):

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

To draw a line from 20,150 to 170,10 with dashes that are 30px long followed by a gap of 10px, you would use:

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

To draw alternating dashes and dots, use (for example):

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

The "very short" dash length of 0 combined with the rounded lineCap results in dots along your line.

If anyone knows of a way to access the current point of a canvas context path, I'd love to know about it, as it would allow me to write this as ctx.dashTo(x,y,dashes) instead of requiring you to re-specify the start point in the method call.

This simplified version of Phrogz's code utilises the built-in transformation functionality of Canvas and also handles special cases eg when 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();
    }
}

I think my calculations are correct and it seems to render OK.

At the moment at least setLineDash([5,10]) works with Chrome and ctx.mozDash = [5,10] works with 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();

Setting to null makes the line solid.

Mozilla一直在努力实现画布的虚线描边 ,所以我们可能会在不久的将来看到它添加到规范中。

Phroz's solution is great. But when I used it in my application, I found two bugs.

Following code is debugged (and refactored for readability) version of Phroz's one.

// 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++;
    }
}

There's a much simpler way to do this. According to http://www.w3.org/TR/2dcontext/#dom-context-2d-strokestyle strokeStyle accepts strings, CanvasGradients, or CanvasPatterns. So we just take an image like this:

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

load it into a canvas, and draw our little rectangle with it.

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

that doesnt result in a perfect dashed line, but it's really straightforward and modifiable. Results may of course become imperfect when you're drawing lines which arent horizontal or vertical, a dotted pattern might help there.

PS. keep in mind SOP applies when you're trying to use imgs from external sources in your code.

There is currently no support in HTML5 Canvas specification for dashed lines.

check this out:

http://davidowens.wordpress.com/2010/09/07/html-5-canvas-and-dashed-lines/

or

Check out the Raphael JS Library:

http://raphaeljs.com/

There are support for it in Firefox at least

ctx.mozDash = [5,10];

seems like ctx.webkitLineDash worked before, but they removed it because it had some compabillity issues .

The W3C specs says ctx.setLineDash([5,10]); but it doesn't seem to be implemented yet anywhere.

I made modified the dashedLine function to add support for offsetting. It utilizes native dashed lines if the browser supports ctx.setLineDash and ctx.lineDashOffset .

Example: 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();
    };
}

Looks like context.setLineDash is pretty much implemented. See this .

" context.setLineDash([5]) will result in a dashed line where both the dashes and spaces are 5 pixels in size. "

I found properties mozDash and mozDashOffset in Mozilla specification:
http://mxr.mozilla.org/mozilla-central/source/dom/interfaces/canvas/nsIDOMCanvasRenderingContext2D.idl

They're probaly used to control dashes, but i haven't used them.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM