Canvas area exceeds the maximum limit

If there’s one thing I’m sure of it’s that Safari and the HTML canvas element aren’t sitting in a tree together. It’s 2022 and Safari still can’t handle canvas elements having more than 16777216 pixels.

Safari simply cannot draw large canvas elements. The limit is set at 16.777.216 pixels. Create a canvas with more pixels and iOS Safari will tell us it exceeds the memory limit.

When we open this page on iOS Safari, it’ll show the following warning in the developer console.

Canvas area exceeds the maximum limit (width * height > 16777216).

That’s because the page creates a canvas that is 4097 × 4096 pixels in size and tries to use its 2d context.

const canvas = document.createElement("canvas");
canvas.width = 4097;
canvas.height = 4096;

const ctx = canvas.getContext("2d");
ctx.fillRect(0, 0, 100, 100);

All is fine until we want to fill our rectangle. That’s where Safari draws a line in the sand.

As we can see it doesn’t matter if we only use part of the canvas. The moment we try to interact with the canvas rendering context Safari warns the canvas is too big.

Well at least on MacOS Safari we don’t get the memory limit warning.

Problem is, if we want to get the ImageData in the canvas, MacOS Safari will invalidate the canvas element if the requested data amount exceeds the maximum size.

ctx.getImageData(0, 0, 4097, 4096);

If you’re on Safari, click below to run the above code.

Say hello to these errors in your developer console.

Unable to get image data from canvas. Requested size was 4097 x 4096. InvalidStateError: The object is in an invalid state.

So how to deal with this limitation.

We can create canvas elements that exceed 4096 pixels in width or height as long as the total amount of pixels is below 16777216.

This will work:

const canvas = document.createElement("canvas");
canvas.width = 5120;
canvas.height = 3072;

const ctx = canvas.getContext("2d");
ctx.fillRect(0, 0, 100, 100);

To make it easier to stay below the limit we can use the following function. It returns a relatively scaled size below the maximumPixels passed.

function limitSize(size, maximumPixels) {
  const { width, height } = size;

  const requiredPixels = width * height;
  if (requiredPixels <= maximumPixels) return { width, height };

  const scalar = Math.sqrt(maximumPixels) / Math.sqrt(requiredPixels);
  return {
    width: Math.floor(width * scalar),
    height: Math.floor(height * scalar),
  };
}

Conclusion, when we’re dealing with Safari, we should always make sure to limit the size of our canvas elements before drawing to it. Other browsers have limits at a much higher level that we’re unlikely to exceed.

Our suffering doesn’t end here, the worst is yet to come.

When we create too many canvas elements we’ll run into a secondary Safari issue. We’ll explore that issue in the follow up article Total canvas memory use exceeds the maximum limit

I use Twitter to share new webdevelopment tips and tricks, so Follow me there if you found this interesting and want to learn more.

Rik Schennink

Indie Product Developer

to pqina.nl