Search code examples
htmlcanvasstroke

Creating an outer stroke on canvas text without having an inner stroke


I've seen the option to simulate an outer stroke by simply doubling the stroke (and changing the order) when writing text to canvas (see here)

However, the app I'm working on allows for rgba colours to be used. As a result, it's possible that the "hidden" inner stroke becomes visible. Here's a sample:

In this example, the stroke is white and the text fill is rgba(0,0,0,0.5).

As a result, the hidden white inner stroke can be seen. Is it possible to apply an outer stroke without the inner? If not, is it possible to apply something like a clipping mask to the stroke to prevent the inner from being visible if the text fill has opacity?

I feel like I know the answer here, but hoping someone might have a clever solution.

Thanks -


Solution

  • By using a simple sequence of composition operation you can obtain the result by these steps:

    • Stroke the text using target color for stroke
    • Change composition mode to destination-out
    • Fill using any solid color. This will knock out the interior.
    • Change composition mode to source-over
    • Fill again using target color and alpha

    If you need to see the background through you can do these steps on a hidden (off-screen) canvas, then draw back the result on top of the background.

    result

    var ctx = c.getContext("2d");
    ctx.font = "128px sans-serif";
    ctx.textBaseline = "top";
    ctx.lineJoin = "round";
    ctx.strokeStyle = "#fff";
    ctx.lineWidth = 11;
    
    ctx.strokeText("MY TEXT", 10, 10);                 // stroke
    ctx.globalCompositeOperation = "destination-out";  // will delete what's under
    ctx.fillText("MY TEXT", 10, 10);                   //   on next draw
    ctx.globalCompositeOperation = "source-over";      // normal comp. mode
    ctx.fillStyle = "rgba(0,0,0,0.5)";                 // draw in target fill/color
    ctx.fillText("MY TEXT", 10, 10);
    #c {background:rgb(55, 68, 236)}
    <canvas id=c width=580></canvas>