Command Palette

Search for a command to run...

Hectal
Course outline
Topic 1.6Beginner ~25 min · step 6 of 6

Build: A Support Check

In one line

The showroom now asks the browser whether it can draw 3D before it builds anything, and shows a message and a still picture when it can't. It also gets its first bike part: the orange down tube, placed with exact numbers.

0/6 · 0%

Before you start

You will learn

  • Check WebGL 2 before building a scene
  • Show a message and a still image as the fallback
  • Place a bike part from exact end points

You write code in

In your lumen-3d project: replace index.html and src/main.js, put showroom-poster.png in public/data/3d/, then run npm run dev

Files you touch

  • index.html
  • src/main.js
  • public/data/3d/showroom-poster.png

Think of it like this

A shop that sells by video call first checks that the customer's camera works. If it doesn't, the shop does not leave a blank screen: it says so and sends photos instead. Our support check is that first question, and the poster is the photos.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

fallback
What the page shows when the 3D part can't run: here a message and a still picture.
hidden
An HTML attribute that hides an element. In JavaScript, element.hidden = false shows it again.
down tube
The frame tube that runs from the pedals' axle (bottom bracket) up to the front of the frame (head tube).
radian
The angle unit three.js uses. 180 degrees is π radians, so −0.8657 radians is about −49.6 degrees.

Step by step

01index.html: the fallback block

Add <div id="fallback" hidden> after #app. It holds one paragraph and an <img> with alt text that describes the picture, so a screen reader user gets the same information. The CSS centres it over the page, and #fallback[hidden] { display: none; } makes sure the hidden attribute wins over display: grid.

02supportsWebGL2() runs first

The function makes a canvas that is not on the page, asks for "webgl2" and returns whether it got one. try / catch makes it return false instead of an error in a locked-down browser. When false, the code shows the fallback, writes a console warning for you as a developer, and stops: nothing else runs.

03startShowroom() holds the scene

All the Phase 0 code now sits in a function that is called only when the check passes. Nothing creates a renderer on a device that has no WebGL 2, so there is no crash and no uncaught error.

04The down tube

A cylinder of radius 0.022 m and length 0.6172 m, in Lumen orange, named "down-tube" so tests and the scene tree can find it. position.set(0.235, 0.5, 0) is the midpoint of the two end points. rotation.z = -0.8657 leans it from the bottom bracket up and forward to the head tube.

05See and test both paths

The first live example runs the real file. The second adds a few lines at the top that make every "webgl2" request fail, as on a device with no WebGL 2, and you see the fallback appear. Test the second path whenever you change the first.

Try it yourself

  1. 1

    Check the ends of the tube

    In the first example, add this at the end of startShowroom(): downTube.updateMatrixWorld(); console.log(new THREE.Vector3(0, 0.3086, 0).applyMatrix4(downTube.matrixWorld).toArray().map((n) => n.toFixed(3)).join(", "));. It prints the tube's top end: 0.470, 0.700, 0.000, the bottom of the head tube. Change the 0.3086 to -0.3086 and it prints -0.000, 0.300, 0.000 (a tiny negative zero from rounding), the bottom bracket.

  2. 2

    Stand the tube upright

    Set downTube.rotation.z = 0. The tube now stands straight up at (0.235, 0.5, 0) like a flagpole instead of leaning. This is what "a cylinder starts upright" means.

Code & diagrams

Build: the showroom with its down tube (the same src/main.js) New tab

This is the exact src/main.js below with the page's #app and #fallback elements. Open the scene tree: the stand (with its orange mark inside), the down-tube and the two lights.

idle

Console

Run the scene to see its console.
The same page on a device with no WebGL 2 New tab

Five lines at the top pretend that every request for a webgl2 context fails. The page shows the message and the poster, and no 3D code runs.

idle

Console

Run the scene to see its console.

Your project files

Your lumen-3d project after this topic. Click a file to see it in full.

index.htmlhtml

The whole file after this topic. If yours differs, you can copy this one and carry on.

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Lumen Bikes · 3D showroom</title>
    <style>
      html, body { margin: 0; height: 100%; background: #0b1118; color: #e6edf6; font-family: system-ui, sans-serif; }
      #app { position: fixed; inset: 0; }
      canvas { display: block; }
      #fallback { position: fixed; inset: 0; display: grid; place-content: center; justify-items: center; gap: 12px; padding: 16px; text-align: center; background: #0b1118; }
      #fallback[hidden] { display: none; }
      #fallback img { max-width: min(100%, 420px); max-height: 60%; border-radius: 8px; }
    </style>
  </head>
  <body>
    <div id="app"></div>
    <div id="fallback" hidden>
      <p>Your browser can't draw 3D here, so here is a picture of the Lumen bike instead.</p>
      <img src="/data/3d/showroom-poster.png" alt="The Lumen Bikes showroom: an orange e-bike on a round display stand" />
    </div>
    <script type="module" src="/src/main.js"></script>
  </body>
</html>

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Remove the fallback block from the page

In the second example, delete the #fallback element from the HTML (in the project, delete the <div id="fallback" hidden> block from index.html).

terminal
$ Run the no-WebGL 2 example
── what you'll see ──
Uncaught TypeError: Cannot set properties of null (setting 'hidden')
Exactly the failure you wanted to prevent: a device with no 3D shows nothing, and now the cause is a console error.

Myth vs fact

Myth

If WebGL 2 is missing, the scene shows nothing and that is fine.

Fact

A blank page looks like a broken site. Customers leave and testers file bugs with no clue. A sentence and a picture cost a few lines.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    A pure <img> fallback is the simplest, and it is also the version search engines and screen readers can use. The model's alt text is part of the product, not decoration. Phase 18 builds the full text alternative.

  • ▸

    The fallback has its own failure: if showroom-poster.png is missing or the path is wrong, Chrome shows a broken-image icon with the alt text beside it (the text can be cut short by the image's size limits). The paragraph above it still reaches the visitor. Test it by renaming the file. Recall from Topic 1.5: getContext("webgl2") returns null for "can't", never undefined, so the !== null test is the right one.

  • ▸

    The check does not guard against a lost context after the page starts (the GPU can reset later), or against a software renderer that is technically WebGL 2 but very slow. Phase 18 handles webglcontextlost and Phase 17 measures frame time.

  • ▸

    The down tube's numbers are copied from a cylinder's own rules, not made up: a cylinder points along +y, so the tube is turned by the angle between +y and the direction (0.47, 0.40, 0) that points from the bottom bracket to the head tube. That angle is atan2(0.47, 0.40) = 49.6°, and turning around z by a positive angle turns +y towards −x, hence the minus sign.

Remember this

  1. 1

    The problem this solves: Lena's old phone showed a white square, with no message. With the check, a device that can't draw 3D gets a clear sentence and a picture of the bike, and the 3D code never runs on a machine that cannot run it. This closes the story: the showroom never leaves a blank page again.

  2. 2

    What the build adds. index.html gains a <div id="fallback" hidden> with a message and a still image, /data/3d/showroom-poster.png (a picture of the bike; copy it into your project from this site, or make your own). src/main.js starts with supportsWebGL2() and, when it returns false, un-hides the fallback instead of starting the scene. The rest of the scene moves into startShowroom().

  3. 3

    How it plugs into what exists. The Phase 0 scene is unchanged: scene, camera, renderer, stand, lights, loop, resize. They now sit inside startShowroom(), which only runs when WebGL 2 works. The camera also stands a little closer and lower (3.2 m from the middle instead of 4, and 1.2 m high instead of 1.6, looking at a point 0.4 m up) so the bike frame fills the view.

  4. 4

    The first bike part: the down tube. It is the tube from the bottom bracket (where the pedals turn) at (0, 0.30, 0) up to the bottom of the head tube (where the fork joins) at (0.47, 0.70, 0). A CylinderGeometry is built upright around its own centre, so it is placed halfway between the two points, (0.235, 0.5, 0), and turned. The length is √(0.47² + 0.40²) = 0.6172 m. The turn is rotation.z = -0.8657 radians, about −49.6°: the tube leans forward towards +x, and a negative z rotation tips the top towards +x. Phase 2 shows how to work these numbers out; here you use them.

  5. 5

    What the showroom can do now that it could not before: show a clear message and a picture on a device with no WebGL 2; show one real bike part standing on the stand. What is still missing: the other frame tubes (Phase 2 adds the top tube and groups them into createBike()), the world split into modules (Phase 3), and a ResizeObserver, controls, colours and everything else in later phases.

Explain it without notes

01

Why does main.js ask supportsWebGL2() before it creates a renderer?

02

Where do the down tube's position (0.235, 0.5, 0) and angle −0.8657 come from?

Practice

01

Make the poster text different for a device that has WebGL 2 turned off in settings (you can't tell this from "no GPU"). Why is one message for both the right call?

02

Add a second tube to the scene: the top tube, from (−0.10, 0.82, 0) to (0.44, 0.84, 0), radius 0.018. What are its centre and length?

Done when you can

  • Your lumen-3d page shows the fallback when WebGL 2 is unavailable (test by faking it)

  • You can explain where each of the down tube's numbers comes from

  • Your index.html and src/main.js match this topic

  • The poster is in public/data/3d/ and loads at /data/3d/showroom-poster.png