1

I have an issue with the painting context.stokeText that style contains an alpha. A big value of line width makes some effect of intersected strokes, as a result, the color is darker. How I can avoid this?

ctx.strokeStyle ="rgba(0,0,0,0.3)";                
ctx.lineWidth = 15;
ctx.lineJoin="round";                
ctx.strokeText(text, x, y);

Image

1 Answers1

0

That's a bit of an inconsistency in the specs since usually overlapping sub-pathes are painted only once.

However strokeText() does create one shape per glyph, and thus this method will indeed paint each glyphs on their own, creating this visible overlapping.

To overcome this, you'll to be a bit creative:

  • first draw your text fully opaque,
  • then redraw the produced pixels with the desired alpha level (many ways to do so).
  • draw that on your scene (or draw the background behind).

Here are a few ways (there are many others):

Probably the easiest, but which costs more memory: use a second disconnected canvas:

const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");

// create a new canvas just for the text
const canvas2 = canvas.cloneNode();
const ctx2 = canvas2.getContext("2d");
ctx2.font = "60px sans-serif";
const text = "MY TEXT";
const x = canvas.width - ctx2.measureText(text).width - 20;
const y = canvas.height - 20;
// draw it fully opaque
ctx2.lineWidth = 15;
ctx2.lineJoin="round";                
ctx2.strokeText(text, x, y);

// draw the background on the visible canvas
ctx.fillStyle = "#ffe97f";
ctx.fillRect(0, 0, canvas.width, canvas.height);

// now draw our text canvas onto the visible one
// with the desired opacity
ctx.globalAlpha = 0.3;
ctx.drawImage(canvas2, 0, 0);
<canvas width="465" height="234"></canvas>

More memory friendly, but which requires you to rewrite your drawing logic in a different direction, use compositing:

const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
ctx.font = "60px sans-serif";
const text = "MY TEXT";
const x = canvas.width - ctx.measureText(text).width - 20;
const y = canvas.height - 20;

// first draw the text fully opaque
ctx.lineWidth = 15;
ctx.lineJoin="round";                
ctx.strokeText(text, x, y);

// now apply the opacity
ctx.fillStyle ="rgba(0,0,0,0.3)";
ctx.globalCompositeOperation = "source-in";
ctx.fillRect(0, 0, canvas.width, canvas.height);

// and the background
ctx.fillStyle = "#ffe97f";
ctx.globalCompositeOperation = "destination-over";
ctx.fillRect(0, 0, canvas.width, canvas.height);

// if you want to keep drawing "normaly"
ctx.globalCompositeOperation = "source-over";
<canvas width="465" height="234"></canvas>

A mix of both, with different compositing rules:

const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
ctx.font = "60px sans-serif";
const text = "MY TEXT";
const x = canvas.width - ctx.measureText(text).width - 20;
const y = canvas.height - 20;

// first draw the text fully opaque
ctx.lineWidth = 15;
ctx.lineJoin="round";                
ctx.strokeText(text, x, y);

// now redraw over itself with the desired opacity
ctx.globalAlpha = 0.3;
ctx.globalCompositeOperation = "copy";
ctx.drawImage(canvas, 0, 0);
ctx.globalAlpha = 1;

// and the background
ctx.fillStyle = "#ffe97f";
ctx.globalCompositeOperation = "destination-over";
ctx.fillRect(0, 0, canvas.width, canvas.height);

// if you want to keep drawing "normaly"
ctx.globalCompositeOperation = "source-over";
<canvas width="465" height="234"></canvas>
Kaiido
  • 123,334
  • 13
  • 219
  • 285