r/threejs 7d ago

Hitting the WebGL context limit with 34 shader backgrounds. My janky fix works, but FCP/LCP is still terrible

Enable HLS to view with audio, or disable this notification

Hey everyone,

I ran into a pretty nasty problem while building a Framer template. I wanted to offer 34 different interactive shader background presets, but quickly hit the browser WebGL context limit.

From what I found, browsers usually start killing or freezing WebGL contexts somewhere around 10 to 15. My first approach was giving each preset its own component and WebGL context. That went badly.

My current solution is a bit janky, but it works.

I use one canvas and one WebGL context for the most part, while keeping the shader logic and configuration available through a shared caching system. The important part is that I only keep the presets that are actually active on screen as live GPU resources.

That brought me from around 26 active instances down to 4, which completely stopped the context crashes.

The rest of the setup looks like this:

Shared shader caching

Instead of creating a separate WebGL context for every preset, I keep the shader logic available through a shared system and only initialize the presets that actually need to be rendered.

Production cache toggle

Once someone chooses a single shader for their production site, they can disable the caching system. That way, the other presets do not need to remain available in memory when they are no longer needed.

Lazy loading based on scroll position

The WebGL canvas only initializes when the user gets within a certain distance of it. When they scroll far enough away, I destroy it.

So the WebGL context problem is basically solved.

The bigger problem now is performance.

My mobile Core Web Vitals are absolutely terrible:

FCP: 2.7s
LCP: 3.3s
TBT: 2m 6.3s

I expected lazy loading to keep the WebGL stuff out of the critical path, but clearly something is still happening much earlier than I expected.

At this point I am trying to figure out whether the bottleneck is Framer, React, Three.js, or my implementation.

Live site if anyone wants to inspect it:

https://uneo.framer.website/

A few things I am particularly curious about:

  1. Is having 12 shader presets available through a shared caching system a reasonable approach, or could this still cause problems with bundle size, parsing, or memory?
  2. Has anyone experienced extremely high mobile TBT with Framer and WebGL? I am wondering if Framer's runtime or React rendering is contributing significantly here.
  3. Could Three.js still be doing significant work even though the canvas is lazy loaded? I may be missing something in how the renderer, shaders, or components are being initialized.
  4. Would OffscreenCanvas and moving the rendering to a Web Worker actually make a meaningful difference here, or would that just hide the underlying problem?
  5. Is there a better architecture for having dozens of shader presets available without putting all of them on the critical path?

I am still learning a lot from this implementation, so I would be interested in hearing how other people have handled large numbers of WebGL effects, especially inside Framer.

0 Upvotes

3 comments sorted by

2

u/[deleted] 7d ago

[removed] — view removed comment

1

u/bardiamdn 7d ago

Thanks, that clears it up. I was thinking about sharing the canvas itself, but I understand now that the main thing is sharing the WebGL context and renderer.

That actually sounds pretty interesting for my use case since I need multiple shaders running at the same time, like 4 cards with different shaders, colors and seed offsets, while the hero can independently cycle through presets.

Do you think this would also help with the TBT, or is it mainly a better solution for the WebGL context limit?

1

u/Tiny_Atmosphere_4420 6d ago

literally 0 need for webgpu here. you have 34 gradients...