Labeled canvas versus hidden DOM mirror for three.js accessibility
0 reputation · 29 Apr 2021, 11:50 UTC
0 reputation · 29 Apr 2021, 11:50 UTC
When a three.js scene must be navigable by screen-reader users, the library itself offers no object-level semantics: meshes, lights, and camera state remain invisible to assistive technology, and controls such as OrbitControls do not register focusable semantics. The unresolved decision is how to represent the scene on the page. One documented approach passes a pre-existing canvas to the WebGLRenderer constructor, sets role="img" and a concise aria-label before three.js attaches the context, and exposes the entire viewport as a single static image. The alternative marks renderer.domElement as aria-hidden and maintains a separate visually hidden DOM subtree that mirrors scene state, trading duplication for dynamic, granular announcements.
Because three.js cannot declare which representation is authoritative, teams must choose per view whether the canvas is decorative, a labeled image, or a hidden surface backed by a parallel accessible region. Constraints include the risk of flooding assistive technology if a live region updates on every animation frame, and the fact that canvas fallback content is only shown when the element is unsupported, not as a screen-reader description in supporting browsers.
Should the canvas be exposed as a single labeled image when scene state is stable and object identity is irrelevant? Or should it be hidden while a throttled DOM mirror announces meaningful state changes, accepting the maintenance cost of duplicated state? How can teams determine, without library-level guidance, which representation remains authoritative for a given view?
35775 reputation · 29 Apr 2021, 22:58 UTC
The choice depends on whether the 3D scene is illustrative or interactive. Use a single labeled canvas when the scene is a static visual aid; use a hidden DOM mirror when the user must interact with individual objects or receive granular state updates.
Because three.js does not provide built-in semantic mapping for its scene graph, the "authoritative" representation is defined by the intended user goal rather than a library setting.
This approach treats the entire WebGL viewport as a single image. It is the most performant and lowest-maintenance option for scenes where object identity is irrelevant to the user's task.
role="img" and a descriptive aria-label on the canvas element.<canvas> tags; modern browsers supporting WebGL ignore this content for screen readers.This approach creates a parallel HTML structure (a "mirror") that reflects the state of the three.js scene graph. This allows screen-reader users to navigate objects via the Tab key and receive updates through standard DOM events.
renderer.domElement with aria-hidden="true". Create a visually hidden <div> containing buttons, lists, or links that correspond to scene objects.aria-live regions to the requestAnimationFrame loop. Updates should be throttled or triggered only by discrete state changes.| Metric | Labeled Canvas | DOM Mirror |
|---|---|---|
| Maintenance | Low (Single string) | High (State synchronization) |
| Granularity | Viewport level | Object level |
| Performance | Negligible overhead | DOM mutation overhead |
To ensure the chosen method is functioning correctly, perform these scoped checks:
Tab key to navigate. Focus should move through the mirror elements; the WebGL canvas itself should be skipped entirely.Diagnostic Detail Required: Does your scene require the user to perform actions on specific 3D objects (e.g., clicking a specific node in a graph), or is the scene providing a general visual context for other page content?
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.