ScreenshotNeo

BlogHow-to

How to Fix Screenshots of Websites with WebGL Canvas Content

Blank WebGL screenshots often come from a drawing buffer cleared after compositing. Learn when to preserve it, capture during rendering, and handle OffscreenCanvas.

By the ScreenshotNeo team4 October 20268 min read

A WebGL screenshot can be blank because the browser may clear the drawing buffer after presenting it for compositing. If you control the page, request preserveDrawingBuffer: true when creating the WebGL context, then check rendering performance. If you control the renderer, you can instead read or export the pixels synchronously while rendering. For WebGL on an OffscreenCanvas, note that transferToImageBitmap() leaves a new blank bitmap and preserveDrawingBuffer does not change that behavior.

If you do not control the website, you cannot change its WebGL context options. Confirm the canvas is the rendered content, capture after the intended draw, and use the screenshot tool’s supported browser capture path. There is no single verified automation flag or timing fix that applies across tools and browser versions.

1. Why is my WebGL canvas screenshot blank?

WebGL drawing buffers are not preserved by default. With preservation disabled, after the browser has composited the rendered buffer, it may clear it to its default values. A later attempt to use the context as an image source can therefore return no useful pixels or behave in an undefined way.

The WebGL specification names readPixels, toDataURL, drawImage, and ImageBitmap creation among operations affected when the buffer is not preserved. The key detail is timing: a page may visibly render correctly, but a later export or capture may happen after the buffer has been cleared. See the Khronos WebGL Specification.

2. Check what is actually being captured

  1. Inspect the target region and confirm it is rendered by a WebGL canvas. The visible content could instead come from an overlay, an iframe, or another layer.
  2. Check whether the page uses WebGL 1 or WebGL 2, and locate the first call that creates its context: canvas.getContext('webgl', options) or canvas.getContext('webgl2', options).
  3. Determine whether your capture reads the canvas later, after the render function returns, or captures the browser’s composed page. Those paths do not necessarily have the same timing or behavior.
  4. If the canvas is transferred from an OffscreenCanvas, look for transferToImageBitmap(); it has specific bitmap replacement behavior described below.

3. Fix for a page you control: preserve the drawing buffer

Pass preserveDrawingBuffer: true when the WebGL context is first created. It is a context creation option; changing an options object afterward does not update an already-created context.

const canvas = document.querySelector('#scene');

const gl = canvas.getContext('webgl2', {
  preserveDrawingBuffer: true,
  alpha: true,
  antialias: true,
});

if (!gl) {
  throw new Error('WebGL 2 is unavailable');
}

function render() {
  gl.viewport(0, 0, canvas.width, canvas.height);
  gl.clearColor(0.08, 0.1, 0.14, 1);
  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

  // Draw the scene here.
}

render();

For a WebGL 1 context, use canvas.getContext('webgl', { preserveDrawingBuffer: true }). Keep the rest of your context attributes as required by the renderer. The example includes alpha and antialias to show that the preservation option can be supplied alongside other attributes; choose those values for your application rather than copying them blindly.

Apply the option at the point where the context is created. If another library creates the context first, pass the option through that library’s documented context attributes. Calling getContext again with different attributes does not retrofit the existing context.

Trade-off to check

Preserving the drawing buffer can cause significant performance loss on some platforms, according to Khronos. Measure rendering responsiveness on the devices and browsers that matter to your users. If preserving the buffer is too costly, consider capturing during the render operation instead.

4. Alternative for a renderer you control: capture during rendering

If the application owns the render path, read or export the pixels synchronously as part of the function that draws the scene. The WebGL specification describes synchronous access in the rendering function, such as readPixels or toDataURL, as an alternative technique. This avoids relying on a later read after the buffer may have been cleared.

For example, a 2D canvas can receive the WebGL canvas pixels while the drawing buffer is still available, then export them as a PNG blob:

const canvas = document.querySelector('#scene');
const gl = canvas.getContext('webgl');
const copy = document.createElement('canvas');
copy.width = canvas.width;
copy.height = canvas.height;
const ctx = copy.getContext('2d');

function renderAndExport() {
  // Draw the complete WebGL frame here.

  // Copy while still in the render operation.
  ctx.drawImage(canvas, 0, 0);
  copy.toBlob((blob) => {
    if (!blob) {
      console.error('Canvas export returned no blob');
      return;
    }
    const link = document.createElement('a');
    link.href = URL.createObjectURL(blob);
    link.download = 'webgl-frame.png';
    link.click();
    URL.revokeObjectURL(link.href);
  }, 'image/png');
}

renderAndExport();

Keep the copy/export adjacent to the draw operation in the actual render flow. A callback or later task that runs after the browser has presented and cleared the frame can miss the intended pixels. For applications needing explicit pixel control, an application-owned framebuffer and readback workflow may also fit, but it requires changes to the renderer.

5. OffscreenCanvas and transferToImageBitmap

For an OffscreenCanvas, transferToImageBitmap() transfers the current image and replaces the canvas’s bitmap with a new blank one. If a later capture reads the canvas after that transfer, it can see the blank bitmap. The HTML Standard states that preserveDrawingBuffer has no effect for this transfer behavior. See the WHATWG canvas standard.

Inspect the order of operations: render the frame, transfer it, then use the returned ImageBitmap as intended. Do not expect setting preserveDrawingBuffer: true to make the transferred-from canvas retain that bitmap. The transfer behavior is separate from the ordinary WebGL compositing lifecycle.

6. Capturing a site you do not control

You cannot change a third-party site’s context creation options from an ordinary screenshot request. Try these diagnostic steps:

  1. Wait for the page’s intended scene to finish drawing, using a supported selector, delay, or page readiness condition in your capture tool.
  2. Confirm the screenshot tool captures the browser page through its normal browser capture path, rather than relying on a later canvas export.
  3. Compare a screenshot of the full page with a screenshot focused on the canvas region to identify overlays or layout issues.
  4. Repeat across the target browser and tool versions. Automation behavior and timing are tool-specific; the sources here do not establish a universal flag or fix.
  5. If the site offers a static image, server-side render, or export function, use that path when a deterministic capture is required.

Waiting can help if the capture happens before the scene is drawn, but it cannot guarantee that a later canvas read will retain a buffer that the browser has cleared. A blank capture may have a timing cause, a buffer preservation cause, or a different cause such as capturing the wrong layer.

7. Troubleshooting common failures

Symptom Likely cause What to check or change
The WebGL canvas is visible in the browser but exports blank later The drawing buffer was not preserved and the read occurs after compositing. Set preserveDrawingBuffer: true when creating the context, or move the read/export into the render operation.
Adding the option has no effect The context was already created before the option was added. Find the first context creation call and pass the option there. Recreate the context through the application’s initialization path.
The screenshot is blank after transferToImageBitmap() The transfer replaced the OffscreenCanvas bitmap with a blank one. Use the returned bitmap and account for transfer order. Preservation does not override this behavior.
The canvas looks blank only in automation The capture may occur before drawing, use a different capture path, or target a different layer. Verify the rendered region, wait for the intended draw using supported controls, and check behavior for the specific tool and version.
Enabling preservation slows rendering Preserving the drawing buffer can carry a platform-dependent performance cost. Measure on target devices and consider synchronous capture in the renderer instead.
The screenshot contains a different or partially drawn frame The capture and render are not synchronized, or the scene has not finished drawing. Coordinate capture with the render lifecycle if you own it; otherwise use the capture tool’s supported readiness controls and verify repeatability.

8. Performance, reliability, and cost

For an application you own, preserving the buffer is straightforward but can slow rendering on some hardware. Capturing synchronously can avoid depending on a later buffer read, though readback and image encoding are still work that belongs in your rendering path. Choose based on the required capture frequency and measure on the devices you support.

For a third-party page, reliability depends on the site’s rendering lifecycle and the screenshot tool’s browser behavior. A wait condition can address a scene that has not drawn yet, but it cannot correct every buffer lifecycle issue. No universal browser automation workaround is established by the sources cited here.

Cost depends on the capture method and service. With a self-managed browser, account for the compute and maintenance needed to run it. If using an API, check its billing rules for failed loads, retries, and cache hits before scaling capture volume.

9. Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server. It can capture a page by URL, but a third-party WebGL site still controls its own rendering lifecycle; no screenshot service can change that site’s WebGL context creation options through a normal request. Use the call below to try a capture through the API, and consult the ScreenshotNeo API documentation for supported parameters.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
  • Cookie and consent banners are accepted like a visitor, and 60+ known consent platforms, newsletter popups, and chat widgets are removed before the shot; each step can be turned off.
  • Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed. Responses identify page verdict and billing status in headers.
  • An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.
  • The Free plan includes 1,000 shots per month with no card. Paid plans start at $5 for 3,000 shots; every feature is on every plan.

Sign up for ScreenshotNeo’s free plan to try 1,000 screenshots a month with no card.

10. FAQ

Does preserveDrawingBuffer default to true?

No. It defaults to false.

Can I turn it on after a WebGL context has been created?

No. It is a context creation attribute, so pass it when the application first creates the context.

Does the option guarantee a third-party screenshot service will capture the 3D scene?

No. It affects context creation on a page you control. Capture timing and automation behavior remain specific to the site, tool, and browser version.

Does preserving the drawing buffer solve OffscreenCanvas transfer behavior?

No. transferToImageBitmap() replaces the OffscreenCanvas bitmap with a blank one, and the HTML Standard says preservation has no effect on that transfer.

Sources