Three.js vs WebGL: What It Means for Your Site

Artyom Sklyarov··8 min read

WebGL is a browser graphics API; Three.js is a higher-level toolkit that can handle the scene, camera, materials and rendering work above it. The useful choice is how much of that toolkit your page needs. A model with lights and a camera can justify it; our shader backgrounds use direct WebGL because they need much less scene machinery. In our reported September 21, 2026 experiment, a browser without a usable GPU spent about two and a half seconds creating a context. That is a finding from one deliberately adverse environment, not a typical visitor benchmark. Below are the reported measurements, the implementation choice they informed, and the limits of applying them elsewhere.

The MVP in 5 Days page on suur.io: a dark hero with the line Monday, an idea. Friday, a product, over a WebGL background rendered by a 7KB hand-written shader
The MVP in 5 Days page on suur.io: a dark hero with the line Monday, an idea. Friday, a product, over a WebGL background rendered by a 7KB hand-written shader

The plain answer

WebGL is a browser graphics API used through a <canvas> context. Availability depends on the browser, device and configuration, so a page needs a fallback when context creation fails. At this level you manage vertex data, shader compilation, buffers, uniforms and draw calls. Direct control also means owning that setup and its error handling.

Three.js is a JavaScript library with scene graphs, cameras, lights, materials, geometry, model loaders and rendering tools. It supports WebGL and also offers a WebGPU renderer. The practical advantage is reusable scene machinery; choosing it does not remove the need to test browser support, fallbacks and performance.

So the comparison is between using the raw API and using the library. When using its WebGL renderer, both approaches ultimately use the browser graphics API. The difference is how much you write, how much you download, and what happens when the graphics card is not there.

What Three.js adds, and what it weighs

For version 0.184.0, our September 21 account reported a minified ES module file of 364,998 bytes and a gzipped size of 86,685 bytes. The raw benchmark artifact was not retained with this article, so these are historical reported measurements, not an independently reproduced test. They describe that file, not every application's tree-shaken production bundle. Imports, build settings and compression change what a visitor downloads. Measure the output of the actual app before treating 87KB as its cost.

For that you get everything the library knows how to do: a perspective camera that behaves like a real lens, physically based materials that react to light, a loader that turns a designer's glTF export into something on screen, shadows, environment maps, and a renderer that handles the hundred details of WebGL state you would otherwise handle yourself. If your page has a product model, a spinning object, a scene you can orbit, or anything that needs lighting, this is money well spent. Writing a glTF loader and a PBR material system by hand is a project, not a task.

Now the other side. Our site runs WebGL on the homepage, on the MVP in 5 Days page, on every offer page and on the design subscriptions page. Eight separate effects exist in the codebase. Not one of them imports Three.js. The one behind the MVP page, a domain-warped noise gradient with grain and a vignette, is 6,867 bytes of source and compresses to 3,052 bytes. These historical source-size measurements are not the same as a production bundle comparison.

That is not an argument against Three.js. Our full-screen fragment shader did not need a scene graph, model loader or lighting system, so direct WebGL kept this particular implementation small. A shared rendering architecture or other effects on a page could still make a library worthwhile.

For honesty: three is still listed in our package.json. It came in with a 3D glass version of our logo that we built, shipped, and then removed from the homepage, and the component is parked in the repo for a future page. It ships in no bundle today. We checked the built output rather than trusting the dependency list.

The cost nobody measures

Here is the part that changed how we think about this, and it applies to Three.js and raw WebGL alike.

A WebGL effect requests a context, for example with canvas.getContext("webgl"). The cost and outcome depend on the environment: a context can use hardware acceleration, use a software path when available, or fail. Our experiment explicitly forced Chrome onto SwiftShader software rendering. It does not establish how every device without GPU access behaves.

We measured our own pages in headless Chrome with four-times CPU throttling and WebGL forced onto SwiftShader. This deliberately adverse software-rendering setup is not representative of every lab run or user device. The following values are medians of three warm runs recorded on 21 September 2026:

  • On the MVP page, the getContext call alone took 2,531ms. Not the shader compile. Not the first frame. The call that asks for the context.
  • Total long-task time on the page, meaning main-thread blocks over 50ms that stop the page responding: 3,445ms, with the longest single task at 2,926ms.
  • The same page with the WebGL effect absent, for comparison: about 240ms of long tasks in total.

Our components already detected software rendering and dropped to a single static frame, which is the standard advice and which we had followed. It did not help, because the detection runs after the context exists, and the context is the expensive part. You cannot ask the browser which renderer it would use without asking it to create one.

We also reported a run with failIfMajorPerformanceCaveat: true. In that configuration, Chrome still returned a SwiftShader context and the call took 2,181ms. That is an observation about the tested configuration, not proof that the option never works or that all software contexts must be rejected.

The fix, and what it measured

The implementation change delayed the context request using requestIdleCallback with a two-second timeout. That schedules work later; it does not move synchronous context creation off the main thread or guarantee that hydration has finished. A timeout can cause the callback to run even when the page is busy. Other valid options include a static fallback, requesting the effect after interaction, or omitting it on the page.

Main-thread cost on the MVP page, median of three runs: total long-task time 3,445ms before and 1,647ms after deferring the WebGL context; longest task 2,926ms before and 1,257ms after; time inside getContext 2,531ms before and 1,104ms after
Main-thread cost on the MVP page, median of three runs: total long-task time 3,445ms before and 1,647ms after deferring the WebGL context; longest task 2,926ms before and 1,257ms after; time inside getContext 2,531ms before and 1,104ms after

Same shader, same page, same throttled software renderer, three warm runs each, medians:

Context during loadContext in idle time
Total long-task time3,445ms1,647ms
Longest single task2,926ms1,257ms
Time inside getContext2,531ms1,104ms

The recorded context duration fell from 2,531ms to 1,104ms. These three-run medians do not isolate why it changed: scheduling, cache state and run-to-run variation may contribute. We cannot attribute the reduction specifically to avoiding hydration. A synchronous 1,104ms call is still a substantial main-thread block, even if it happens later.

These are reported lab numbers from a deliberately hostile configuration, not field data from visitors, and the raw traces were not retained with the article. The first run against a freshly deployed page reportedly took 9 seconds and was excluded as a cold-cache run. The table therefore describes warm runs only and understates that observed cold case. We did not establish a negligible cost on mobile GPUs; that requires separate device testing.

When to use which

Use Three.js when:

  • There is a 3D object: a product, a logo, a scene, anything a designer exported as a model.
  • You need a camera, lights, shadows, reflections or materials that respond to light.
  • You want to load standard formats, glTF above all.
  • The interaction is spatial: orbit, zoom, drag an object.
  • You may want WebGPU later without rewriting.

Write WebGL directly when:

  • The effect is a full-screen shader: gradients, noise, grain, ripples, glass, halftone.
  • There is one flat surface and no camera.
  • The whole point is a background that must cost almost nothing to download.
  • You are comfortable owning a few hundred lines of shader and setup code.

Use neither when:

  • CSS can express the effect. It may avoid JavaScript and WebGL context setup, but rendering cost still depends on the property, layer composition and device. A CSS blur or animated gradient is not automatically cheap.
  • The page has to be fast on hardware you do not control. Marketing sites are viewed on whatever the visitor has.

For a nonessential decorative effect: test delayed initialization, pause rendering out of view, cap resolution, respect reduced motion and clean up resources on unmount. Our September implementation used a 1.5 pixel-ratio cap and a static frame for reduced motion or detected software rendering. Those choices are examples, not universal optimal settings; creating even that first frame still incurs context setup.

What this means for a marketing site

A moving hero should earn its cost. In our adverse test, context creation was a large part of that cost; another page may be limited by bundle download, shader complexity or continuous rendering. Compare the effect with a static fallback on representative devices before deciding whether to keep it.

We sell this as an add-on. The 3D / WebGL Brand Experience is $12,000 flat, on top of a Landing Page Sprint at $3,500 or a Product Sprint at $9,000, and takes about two weeks on top of the base build. It ships with the deferral, the viewport pause, the pixel-ratio cap and the reduced-motion frame described above, because we measured what happens without them on our own site first. The slow mobile loads that led here were flagged by our own audits of suur.io, from the first one before launch.

A

Artyom Sklyarov

Founder & Designer at SUUR

More about Artyom →

Have a product idea?

Designed, built, and shipped in about 5 days.

Start a Project