Indonesia Singapore ไทย Pilipinas Việt Nam Malaysia မြန်မာ ລາວ
← Back to Blog

Three.js WebGL Effects: Performance Cost vs. Visual Payoff

GPU-intensive WebGL effects can coexist with strong Core Web Vitals — if you load them asynchronously after LCP and gate by device capability.

By Diesel Grizzly →
A tangle of glowing threads being woven by a robotic loom suspended inside a web browser window
Illustrated by Mikael Venne

Three.js WebGL effects are stunning — but do GPU-intensive renders hurt your Core Web Vitals? A performance engineer's honest breakdown for SEA brands.

Three WebGL experiments dropped on Codrops this week that are, technically speaking, absurd — in the best way. Image ribbons woven into a flowing loom. Real-time 3D face masks built with MediaPipe landmarks. A shape-aware ASCII renderer evaluating 95 glyphs per character cell on the GPU. Each one is a minor act of engineering poetry. Each one would also, naively deployed, detonate your Interaction to Next Paint score.

That tension — between visual ambition and measurable performance — is exactly where most brand teams make the wrong call. They either greenlight the effect wholesale or kill it outright. Neither is the right answer.

What These Three.js Experiments Are Actually Doing to the GPU

Let’s be specific. Clément Grellier’s infinite loom demo renders images as geometry — transforming pixel data into ribbon-like meshes that flow and unravel across the viewport. This is not a CSS animation. It’s a full WebGL pipeline: vertex shaders computing ribbon positions per frame, fragment shaders handling colour and blending, geometry updates on every tick. On a mid-range Android device — the median device in markets like Indonesia, Vietnam, and the Philippines — that kind of per-frame geometry mutation will fight your main thread hard if it’s not isolated properly.

Edoardo Lunardi’s ASCII renderer goes further. Rather than a simple luminance-to-character lookup, it evaluates shape similarity across 95 glyphs for every character cell. That’s brute-force pattern matching running on the GPU, which is genuinely clever architecture — offloading work that would destroy the CPU onto hardware designed for parallel computation. The technique matters: GPU-bound is far preferable to main-thread-bound when it comes to INP and TBT.

Medical imaging and AR applications aside, Marek Jóźwiak’s MediaPipe face mask demo is the one with the clearest commercial precedent — beauty, skincare, and fashion try-on experiences are live in multiple SEA markets already. The pipeline here is heavier: MediaPipe’s face landmark model runs inference every frame, feeding 468 landmark points into Three.js to deform and texture a canonical face mesh in real time. Camera permission, model loading, inference latency, render loop — that’s four distinct performance bottlenecks before the user sees anything useful.

The Core Web Vitals Calculus No One Is Running

Here’s the uncomfortable truth most creative agencies don’t want to hear: Google’s ranking signals don’t care how beautiful your WebGL experience is. LCP, INP, and CLS are measured against objective thresholds, and a Three.js canvas that initialises on page load will hammer your LCP if it blocks the critical rendering path.

The fix is not to avoid WebGL. The fix is sequencing. Load your Three.js scene asynchronously, well after LCP has fired. Use IntersectionObserver to defer canvas initialisation until the element is actually in viewport. For heavier pipelines like the MediaPipe face mask, lazy-load the ML model only when the user explicitly triggers the feature — not on DOMContentLoaded.

A practical benchmark: if your Three.js bundle exceeds 150KB gzipped and loads synchronously, you’ve already made a choice about your LCP score. The main Three.js build is around 600KB uncompressed. Tree-shake aggressively, use the modular three/addons import paths, and consider splitting the scene into a separate chunk that loads post-interaction.


Device-Gating Is Not Optional in Southeast Asia

This is where SEA-specific context changes the calculus significantly. The Codrops demos are built and tested on developer hardware — likely a MacBook with a discrete GPU and Chrome DevTools set to no throttling. Your users in Tier 2 and Tier 3 cities across the region are on something considerably more constrained.

The navigator.deviceMemory API returns device RAM in rough buckets (0.25, 0.5, 1, 2, 4, 8 GB). A device with 1GB or less should almost certainly receive a static fallback — a high-quality image or a CSS-animated alternative — rather than a live WebGL canvas. Similarly, navigator.hardwareConcurrency gives you CPU core count, and the Network Information API can signal connection quality. None of these are perfect signals, but triangulating across two or three gives you a defensible heuristic for progressive enhancement.

Shopee and Lazada’s own creative teams have quietly been doing this for product page animations for two years — serving rich CSS transitions on capable devices, static assets on everything else. It’s not glamorous engineering, but it’s the reason their pages don’t collapse under the weight of their own ambition.

Scaling WebGL Effects Across a Design System

If you’re considering WebGL as a recurring brand element — not just a one-off campaign page — the architecture question shifts from “will this work” to “how do we govern this at scale.” Three.js scenes that live as isolated experiments are manageable. Three.js scenes that need to co-exist with a React or Vue component tree, share a design token system, and be maintained by a team of five developers are a different problem.

Threlte, the Svelte-based Three.js wrapper used in the face mask demo, is an interesting model here. It treats 3D scene elements as reactive components, which means they can respond to the same design system state — colour tokens, breakpoints, animation timing curves — as the rest of your UI. For teams already on Svelte or SvelteKit, this dramatically reduces the cognitive overhead of introducing 3D into a production codebase. React Three Fiber plays a similar role for React teams.

The ASCII renderer’s approach to GPU-side computation — pushing the expensive evaluation into a shader rather than the CPU — is also worth internalising as a principle. Any time you find yourself doing complex per-frame calculations in JavaScript, ask whether that logic belongs in a GLSL shader instead. The performance profile is categorically different.


Key Takeaways

  • Defer Three.js scene initialisation until after LCP fires and use IntersectionObserver to avoid loading canvas resources before they’re in viewport.
  • Gate GPU-intensive WebGL effects behind device capability signals (deviceMemory, hardwareConcurrency) and serve static or CSS fallbacks to low-end devices — the majority hardware profile in many SEA markets.
  • Adopt a reactive wrapper like Threlte or React Three Fiber if WebGL is entering your design system — it keeps 3D scenes maintainable alongside your existing component architecture and token system.

The real question isn’t whether WebGL effects are worth the performance cost. It’s whether your team has the architectural discipline to deploy them responsibly — because an effect that works beautifully on 20% of your audience while degrading the experience for the other 80% is not a creative win, it’s a conversion leak dressed up in a vertex shader.


At grzzly, we work with digital teams across Southeast Asia on exactly this — bridging the gap between creative ambition and performance engineering, so rich web experiences actually ship without wrecking Core Web Vitals or fragmenting across device tiers. If your team is evaluating WebGL, Three.js, or any GPU-heavy creative direction for an upcoming campaign or product build, we’d rather have that conversation before the code is written than after the performance audit. Let’s talk

Diesel Grizzly

Written by

Diesel Grizzly

Core Web Vitals, rendering strategies, PWAs, and the relentless pursuit of sub-second load times. Believes that performance is the most underrated conversion optimisation lever in existence.

Enjoyed this?
Let's talk.

Start a conversation