Total Canvas Memory Use Exceeds The Maximum Limit
You’re using the HTML Canvas Element in your new web app and decide to quickly test on iOS Safari. Suddenly your canvas isn’t drawn and the console shows a yellow warning “Total canvas memory use exceeds the maximum limit”. What gives?
While you were busy building, Safari was busy stuffing itself.
Safari has a thing for hoarding <canvas>
elements. When you’re done with a canvas, Safari will hold on to it for a while, even if you’re not referencing it.
It has a cute little showcase where it presents all the canvas elements it’s currently holding on to.
That’s. Just. Great.
Safari on iOS has the same hobby. It desperatly wants to keep up with its big brother on MacOs but it just doesn’t have the same storage capacity.
Once the storage box is full, it’ll throw this message and start drawing transparent canvas elements.
So how do we deal with this?
We’re going to have to help out Safari to clean up its mess. Get rid of the things it can’t or won’t throw away.
When we’re done with a <canvas>
element we have to manually “release” it.
Meaning we resize the canvas to a very small size and clear its contents, this tricks Safari in to replacing the canvas in its storage with our compressed version.
The following function will do just that.
function releaseCanvas(canvas) {
canvas.width = 1;
canvas.height = 1;
const ctx = canvas.getContext('2d');
ctx && ctx.clearRect(0, 0, 1, 1);
}
Safari will still hold on to the <canvas>
for a while, but at least now it won’t blow the roof of its storage depot.
Unfortunately the memory limit on iOS Safari is rather low. It’s limited to 384MB on version 15, it’s lower on earlier versions, and it’s probably device specific as well.
A canvas pixel is made up of 4 values, red, green, blue, and an alpha value. Combining this with the canvas size we can calculate the amount of memory a canvas requires.
4096 × 4096 × 4 = 67108864 Bytes = 64 Megabytes
This is also the max size of a canvas on Safari
Let’s exceed the 384MB limit by creating 8 of these canvas elements.
for (let i = 0; i < 8; i++) {
const canvas = document.createElement('canvas');
canvas.width = 4096;
canvas.height = 4096;
const ctx = canvas.getContext('2d');
ctx && ctx.fillRect(0, 0, 100, 100);
}
If the maximum limit was exceeded earlier the getContext('2d')
method will return null
so that’s why we check if the ctx
variable is set before calling fillRect
When we add the releaseCanvas
function we force Safari to free up some memory and the warning goes away.
for (let i = 0; i < 8; i++) {
const canvas = document.createElement('canvas');
canvas.width = 4096;
canvas.height = 4096;
const ctx = canvas.getContext('2d');
ctx && ctx.fillRect(0, 0, 100, 100);
// Force release each canvas
releaseCanvas(canvas);
}
What we should keep in mind is that Safari either:
- caches canvas elements across sessions
- or doesn’t recalculate the free memory correctly on page refresh
So a page refresh might not clean up the error immediately, it might still show and only dissapear after multiple refreshes or clearing the cache.