Canvas and WebGL — HTML5 elements for rendering 2D/3D graphics. In React, refs are used for them.
System design context: React is designed for DOM-based UI, not pixel-level rendering. Canvas and WebGL operate outside the React rendering pipeline — you manage a raw HTML element via a ref and imperatively draw on it. This is the correct pattern for data visualizations, image editors, games, and 3D scenes. The key insight is that React manages the lifecycle (mount/unmount) while Canvas/WebGL manages the pixels.
Canvas (2D graphics):
1import { useRef, useEffect } from "react";23function DrawingApp() {4 const canvasRef = useRef(null);5 const [isDrawing, setIsDrawing] = useState(false);67 useEffect(() => {8 const canvas = canvasRef.current;9 const ctx = canvas.getContext("2d");10 ctx.fillStyle = "white";11 ctx.fillRect(0, 0, canvas.width, canvas.height);12 }, []);1314 const startDraw = (e) => {15 const ctx = canvasRef.current.getContext("2d");16 ctx.beginPath();17 ctx.moveTo(e.nativeEvent.offsetX, e.nativeEvent.offsetY);18 setIsDrawing(true);19 };2021 const draw = (e) => {22 if (!isDrawing) return;23 const ctx = canvasRef.current.getContext("2d");24 ctx.lineTo(e.nativeEvent.offsetX, e.nativeEvent.offsetY);25 ctx.stroke();26 };2728 const stopDraw = () => setIsDrawing(false);2930 return (31 <canvas32 ref={canvasRef}33 width={800}34 height={600}35 onMouseDown={startDraw}36 onMouseMove={draw}37 onMouseUp={stopDraw}38 style={{ border: "1px solid black" }}39 />40 );41}
WebGL (3D graphics) with Three.js:
1import { Canvas } from "@react-three/fiber";2import { OrbitControls } from "@react-three/drei";34function Scene() {5 return (6 <Canvas camera={{ position: [0, 0, 5] }}>7 <ambientLight intensity={0.5} />8 <pointLight position={[10, 10, 10]} />9 <mesh>10 <boxGeometry args={[1, 1, 1]} />11 <meshStandardMaterial color="orange" />12 </mesh>13 <OrbitControls />14 </Canvas>15 );16}
Performance considerations:
requestAnimationFrame, not React state updates.@react-three/fiber reconciler handles the render loop efficiently.Common pitfalls:
willReadFrequently: true in getContext("2d") for performance.Libraries: