1

I'm looking for a way to lines with markers in a <canvas> element, like the picture bellow.

SVG marker-mid example

In SVG we can do that thanks the markers but I haven't found a similar way to do that with canvas. I know that we can create patterns in canvas thanks to the createPattern function but I doubt it could help to solve the issue.

EDIT: This is not a duplicate of this question since I'm looking for a way to repeat a shape/marker on a path. It's not about placing marker at a specific given point.

As I've commented bellow, I've discovered the svg-path-properties which is almost perfect for my solution.

PierreB
  • 489
  • 3
  • 14
  • 1
    https://stackoverflow.com/questions/6576827/html-canvas-draw-curved-arrows – tomasantunes Jun 06 '17 at 11:18
  • Possible duplicate of [HTML Canvas - draw curved arrows](https://stackoverflow.com/questions/6576827/html-canvas-draw-curved-arrows) – Kaiido Jun 06 '17 at 12:28

1 Answers1

1

There is unfortunately no native way to add markers to strokes in the canvas API. Even if we are able to set strokeStyle to a CanvasPattern, there is no way to make this pattern follow our path's direction.

This means that you'll have to make the calculations of the direction and position of your markers yourself...
For this, you can refer to many posts, like this one proposed by Tomàs Antunes in comments.

However, to do the exact triangle shape you shown to us, I've got an non-mathy hack, which doesn't work very well, but that I'd like to share anyway.

Strokes can have dash-arrays, which will create gaps in the stroke.
By offsetting multiple times this dash-array, and decreasing the lineWidth of our stroke, we can sort-of create these arrow shapes :

var ctx = c.getContext('2d');
// our default path
ctx.beginPath();
ctx.moveTo(50, 20);
ctx.bezierCurveTo(230, 30, 150, 60, 50, 100);
ctx.bezierCurveTo(250, 50, 150, 60, 150, 150);
ctx.stroke();
// the hack
for (var i = 0; i < 9; i += .5) {
  // set an dasharray of 1px visible, then 40 invisible
  ctx.setLineDash([1, 49]);
  // offset the dash-array by one px
  ctx.lineDashOffset = i;
  // reduce the width of our stroke
  ctx.lineWidth = i;
  ctx.stroke();
}
<canvas id="c"></canvas>

But the main caveat of this method (apart from requiring to redraw the same path 20 times) is that it will really follow the path, and that in angles, it may not completely look like our triangle shape anymore.

var ctx = c.getContext('2d');
// our default path
ctx.beginPath();
ctx.moveTo(50, 20);
ctx.bezierCurveTo(230, 30, 150, 60, 50, 100);
ctx.stroke();
// the hack
for (var i = 0; i < 9; i += .5) {
  // set an dasharray of 1px visible, then 20 invisible
  ctx.setLineDash([1, 14]);
  // offset the dash-array by one px
  ctx.lineDashOffset = i;
  // reduce the width of our stroke
  ctx.lineWidth = i*2;
  ctx.stroke();
}
<canvas id="c"></canvas>
Kaiido
  • 123,334
  • 13
  • 219
  • 285
  • This solve the issue I've pictured however it's not "generic" enough for my concrete issue. For instance if you want to have a different kind of symbol defined by another path like [here](https://codepen.io/pbellon/pen/RVGgMQ). To solve this I think the only solution would be to actually draw this in SVG then add the rendered SVG to the canvas with the `drawImage` function. I will answer my own question if I manage to do that. – PierreB Jun 06 '17 at 13:41
  • @PierreB yes, as I said this solution is only for this very specific marker of arrow shape. As I also mentionned, you could still do the calculations like it's done in the linked Q/A, other post. And your idea of drawing an svg to the canvas is actually a good one too. If you're struggling on its implementation I would be glad to help you do it in a few hours, it's being late in here. – Kaiido Jun 06 '17 at 14:57
  • Actually this Q/A fits better your needs https://stackoverflow.com/questions/17083580/i-want-to-do-animation-of-an-object-along-a-particular-path – Kaiido Jun 07 '17 at 00:33
  • I think I've found the best solution for my issue. The SVG solution I've mention earlier is far from being perfect because you can't properly position markers on a path, it's your browser that decides to place markers where it wants. So when your path is starting to be complex (like a geographical path can be) you start to have overlapping markers. Instead I've tried to use [svg-path-properties](http://geoexamples.com/svg-path-properties/) which does exactly what I was looking for: helping to place points on a given path. – PierreB Jun 07 '17 at 09:36