Canvas Compositing

Learn how globalAlpha and globalCompositeOperation control transparency and how new pixels blend with everything already drawn on the canvas.

Fading Layers with globalAlpha

ctx.globalAlpha sets a transparency multiplier from 0 (invisible) to 1 (fully opaque) that applies to every drawing operation — fills, strokes, images, gradients — until you change it again. It multiplies with any alpha already present in the fill color or image itself, so a globalAlpha of 0.5 applied to a color that's already 50% transparent results in 25% final opacity.

Example 1: Layered Translucent Panels

<!DOCTYPE html>
<html>
<body>

<canvas id="myCanvas" width="350" height="230" style="border:1px solid #ccc;"></canvas>

<script>
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');

ctx.globalAlpha = 1;
ctx.fillStyle = '#1e293b';
ctx.fillRect(0, 0, canvas.width, canvas.height); // opaque background

ctx.globalAlpha = 0.5;
ctx.fillStyle = '#38bdf8';
ctx.fillRect(40, 40, 200, 120); // 50% see-through panel

ctx.globalAlpha = 0.5;
ctx.fillStyle = '#f472b6';
ctx.fillRect(140, 100, 200, 120); // overlaps the first panel

ctx.globalAlpha = 1; // always reset when you're done
</script>

</body>
</html>

Blending Pixels with globalCompositeOperation

Every draw call combines new pixels with whatever is already on the canvas using a blend rule called the composite operation. The default, 'source-over', simply paints new pixels on top wherever they're opaque. Changing globalCompositeOperation before a draw call changes that rule entirely — some operations mask existing content using the new shape, some punch holes, and some mix colors together like a Photoshop blend mode.

OperationEffect
source-over (default)New content is drawn normally on top of existing content.
destination-inKeeps only the parts of the existing canvas that overlap the new shape; everything else becomes transparent.
destination-outErases existing canvas content wherever the new shape overlaps it, like a mask-shaped eraser.
source-atopNew content is drawn only where it overlaps existing (opaque) content, and is clipped to that shape.
lighterAdds the color values of overlapping pixels together, producing bright, glowing overlaps.
multiplyMultiplies overlapping color values, always darkening — useful for shadows and tinting.

Example 2: Revealing a Photo Through a Shape (destination-in)

<!DOCTYPE html>
<html>
<body>

<canvas id="myCanvas" width="260" height="260" style="border:1px solid #ccc;"></canvas>

<script>
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');

const photo = new Image();
photo.src = 'assets/photo.jpg';

photo.onload = () => {
  ctx.drawImage(photo, 0, 0, canvas.width, canvas.height);

  ctx.globalCompositeOperation = 'destination-in';

  ctx.beginPath();
  ctx.arc(canvas.width / 2, canvas.height / 2, 100, 0, Math.PI * 2);
  ctx.fill(); // only the circular overlap between photo and circle survives

  ctx.globalCompositeOperation = 'source-over'; // reset for whatever draws next
};
</script>

</body>
</html>

Common Blend Recipes

  • Spotlight/reveal effects: draw the full scene, then mask it down with 'destination-in' and a shape or gradient
  • Punching transparent holes: 'destination-out' with a soft radial gradient for vignettes and fades
  • Additive glow and particles: 'lighter' so overlapping bright spots accumulate into a hotspot
  • Tinting or shadowing artwork: 'multiply' with a translucent color fill on top
  • Colored highlight overlays on photos: 'overlay' or 'screen' for punchy, non-flat lighting

Example 3: Additive Glow Particles

<!DOCTYPE html>
<html>
<body>

<canvas id="myCanvas" width="300" height="260" style="border:1px solid #ccc;"></canvas>

<script>
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');

const particles = [
  { x: 80, y: 100, radius: 40 },
  { x: 160, y: 140, radius: 55 },
  { x: 230, y: 90, radius: 35 },
  { x: 190, y: 190, radius: 45 }
];

ctx.fillStyle = '#0f172a';
ctx.fillRect(0, 0, canvas.width, canvas.height);

ctx.globalCompositeOperation = 'lighter';

particles.forEach((p) => {
  const glow = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, p.radius);
  glow.addColorStop(0, 'rgba(96, 165, 250, 0.8)');
  glow.addColorStop(1, 'rgba(96, 165, 250, 0)');

  ctx.fillStyle = glow;
  ctx.beginPath();
  ctx.arc(p.x, p.y, p.radius, 0, Math.PI * 2);
  ctx.fill();
});

ctx.globalCompositeOperation = 'source-over';
</script>

</body>
</html>
Note: globalAlpha and globalCompositeOperation are both persistent context state, exactly like fillStyle — they do not reset themselves after one draw call. Wrap a custom blend in save()/restore(), or explicitly set globalAlpha = 1 and globalCompositeOperation = 'source-over' afterward, or the very next shape you draw will inherit the leftover transparency or blend mode.
Note: Non-default composite operations, especially 'lighter' applied to hundreds of overlapping gradients, force the browser to recompute blending per pixel every frame and can measurably slow down animations on large canvases. Reach for compositing tricks for a handful of layered effects, not for every sprite in a busy scene.

Exercise: Canvas Compositing

What does the globalCompositeOperation property control?