What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more

Canvas 2D has no bloom function. You can still fake a convincing glow with three steps you already have in the API: draw the bright elements to a separate canvas, blur that canvas with the filter property, and add the blurred result back onto the scene with a lightening composite. The core is a short function, and the one thing to check before you rely on it is whether your target browsers support filter.

What Canvas 2D does and does not provide

Bloom in real engines is usually a post-processing pass: the frame is thresholded so only the brightest pixels remain, those pixels are blurred over several passes, and the blur is added back to the frame. The Canvas 2D context does not expose any of that as a named feature. What it does expose is the pieces you need to build an approximation:

The approach below uses the first two. The shadow route is covered later as a shorter alternative.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The eight-line bloom

Create two offscreen canvases once, at the same size as your game canvas. One holds only the bright elements. The other holds the blurred copy. Recreate both if the game canvas is resized.

#1 Best Overall
Sale
Game Programming Patterns
  • Brand New in box. The product ships with all relevant accessories

Step 1: set up the offscreen canvases

const bright = document.createElement('canvas');
const blurred = document.createElement('canvas');
bright.width = blurred.width = canvas.width;
bright.height = blurred.height = canvas.height;
const b = bright.getContext('2d');
const o = blurred.getContext('2d');

Step 2: the per-frame function

The function body is eight statements. It draws the bright pass, blurs it, and composites the result onto the frame you pass in.

function bloom(ctx, drawBright) {
  b.clearRect(0, 0, bright.width, bright.height);
  drawBright(b);
  o.clearRect(0, 0, blurred.width, blurred.height);
  o.filter = 'blur(12px)';
  o.drawImage(bright, 0, 0);
  ctx.globalCompositeOperation = 'lighter';
  ctx.drawImage(blurred, 0, 0);
  ctx.globalCompositeOperation = 'source-over';
}

Step 3: call it after the normal scene

Draw the frame as usual, then call bloom() with a callback that redraws only the elements that should glow.

Rank #2
drawScene(ctx);
bloom(ctx, (g) => drawStars(g));

The callback receives the offscreen context, so any function that draws with a context parameter can be reused. Keep the bright-pass callback to the sprites, particles, or pickups that should glow. Everything else stays out of it, and that choice is what makes the effect look like bloom rather than a general haze. The 12px blur value is a starting point; raise it for a wider glow and lower it for a tighter one.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Blend mode: lighter or screen

Both modes brighten the destination, but they combine colour differently. lighter adds the source and destination colour values where shapes overlap, so bright areas clip quickly toward white. screen lightens without that hard addition, which usually keeps highlights from blowing out. The example uses lighter because it is the most direct additive glow. Switch the composite string to 'screen' if overlapping glows look washed out.

Compatibility and fallback

The filter property is not universal. MDN labels it as limited availability and says it is not Baseline because it does not work in some of the most widely used browsers. Check support in the browsers your players actually use before shipping the blur route.

A simple feature check is 'filter' in ctx. This tells you whether the property exists in the browser, not whether every blur renders identically, so verify the output in your target browsers. If the check fails, use the shadow halo instead:

if ('filter' in ctx) {
  bloom(ctx, drawBrightSprites);
} else {
  ctx.shadowColor = 'rgba(255, 200, 80, 0.9)';
  ctx.shadowBlur = 24;
  drawBrightSprites(ctx);
  ctx.shadowBlur = 0;
}

The shadow branch draws the bright sprites directly on the main context, so it is a per-shape halo that runs in the same pass as the rest of the frame. It is not identical to the blurred composite, and the look will differ.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Choosing between the blur route and the shadow halo

Factor Blur plus lightening composite Shadow halo
Browser support filter is limited availability per MDN; not Baseline Shadow properties are part of the Canvas 2D API reference; still confirm in your target browsers
Visual result One blurred glow layer over the whole frame A halo around each shape you draw; looks different from a composited pass
Setup Two offscreen canvases and resize handling Three property assignments around each draw call
Performance Not stated by the cited Canvas references; measure in your game Not stated by the cited Canvas references; measure in your game

What this approach does not do

  • It has no threshold. You decide what is bright by choosing what goes into the bright pass.
  • It does not model high dynamic range. Colour values are the ordinary 8-bit values the canvas stores.
  • It is not a standard bloom effect. The output is a blurred additive layer, so it may look softer or brighter than a multi-pass post-process.
  • No frame-rate figure applies. The Canvas references do not give a cost for a blur on an offscreen canvas, and the cost depends on canvas size, the number of frames, and the device.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

When a library route is the wrong fit

If you already use PixiJS, its blur filter is an alternative, but it is a different renderer. The PixiJS 7.x container guide describes container filters, including blur, as advanced features that are WebGL-only, and warns that filters can slow performance and increase memory use when applied often. That conflicts with a no-WebGL requirement, so for this title it marks a boundary rather than a solution. The statement applies to the cited 7.x guide, not to every PixiJS version or renderer. (PixiJS 7.x Guides: Containers)

For a Canvas 2D game with a small number of glowing elements, the two-canvas function and the shadow fallback cover the common cases without adding a rendering library.

Quick Recap

SaleBestseller No. 1
Game Programming Patterns
Game Programming Patterns
Brand New in box. The product ships with all relevant accessories
$24.95
SaleBestseller No. 2
Designing Games: A Guide to Engineering Experiences
Designing Games: A Guide to Engineering Experiences
Used Book in Good Condition
$34.99

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.