Search code examples
canvashtml5-canvas2dtransparencyalphablending

Transparency groups in CanvasRenderingContext2D


Is there a way to combine multiple draw operations to a 2d canvas rendering context in such a way that they their combined result is composed onto the previous content of the canvas, as opposed to each drawing operation being composed by itself?

One application: I'd like to draw a translucent line with an arrow head, and I'd like to avoid increased opacity in those areas where the line and the arrow head overlap.

Many other rendering models support such features. SVG has a group opacity setting, described in section 14.5. The PDF reference describes “Transparency Groups” in section 7.3. In many graphics applications a user can build layers and then compose them as a whole.

I guess I could set up a second invisible canvas to be used as an off-screen image, render my content to that and then use globalAlpha to compose the result onto the main canvas with the desired translucency. But I hope that there is some more elegant solution, even if I couldn't find it in the docs so far.

Merge multiple paths in CanvasRenderingContext2D to fill and stroke as a set apparently has a similar goal in mind. But the focus there appears to be how to perform boolean operations on paths, probably the way clipper does it. So for this post here, I'm not interested in manipulating the paths up front; I want to be able to draw each stroke as I usually would.


Solution

  • No, that's currently not possible without using an intermediate canvas.

    Unless all of your draw operations in question can be combined into a single path, thereby using a single call to stroke() and fill(). I assume that's not the case here, but people searching later may be looking for that information.

    // two rectangles composed into a single draw operation:
    ctx.rect(0, 0, 10, 10);
    ctx.rect(5, 5, 10, 10);
    ctx.fill();
    
    // With transparency, the above produces a different result from:
    ctx.rect(0, 0, 10, 10);
    ctx.fill();
    ctx.rect(5, 5, 10, 10);
    ctx.fill();