Navigate
Search topics across all sections
Responsive & Resize
Your 3D scene will be viewed on phones, tablets, laptops, and ultrawide monitors. It needs to adapt gracefully to every screen size and pixel density. R3F handles the heavy lifting — you just need to know how to work with the viewport and device pixel ratio.
You build a beautiful 3D landing page on your 27-inch monitor. It looks perfect. Then you open it on your phone — objects are clipped off the edges, text is unreadable, and the frame rate is 12fps because the Retina display is rendering at 4x the pixels. Your 'responsive' website is only responsive in 2D.
Scene renders at 12fps on iPhone. Objects clipped on portrait screens. Blurry on some devices.
Real-world
Think of your 3D scene as liquid layout — like water filling any container.
Pour water into a tall glass and it rises high and narrow. Pour it into a wide bowl and it spreads flat. The water adapts to the shape of its container. Your scene should do the same.
R3F's Canvas is the container. It automatically fills its parent div and updates the renderer when the browser resizes. Your job is to make the content inside adapt — spacing objects based on viewport width, scaling elements for readability, and capping pixel density for performance.
Browser Resize
Window or container changes
Canvas Adapts
R3F updates renderer size
Viewport Updates
useThree provides new dims
Scene Responds
Objects reposition/rescale
Hands-On: Making Scenes Responsive
Let's make a 3D scene that adapts to any screen size, from phone to ultrawide. The key tools are CSS container sizing, the viewport object, and DPR control.
Step 1: Let CSS control the container
// The Canvas fills its parent container
// Control size with CSS on the wrapper
// Full viewport hero
<div className="w-full h-screen">
<Canvas><MyScene /></Canvas>
</div>
// Aspect-ratio constrained
<div className="w-full aspect-video">
<Canvas><MyScene /></Canvas>
</div>
// Responsive max-width
<div className="w-full max-w-4xl mx-auto aspect-[2/1]">
<Canvas><MyScene /></Canvas>
</div>Never set fixed pixel sizes on Canvas. Wrap it in a div and use CSS classes for sizing. R3F detects changes to the container and updates the renderer, camera aspect ratio, and viewport automatically.
Step 2: Read viewport dimensions
function AdaptiveScene() {
const { viewport, size } = useThree();
// size = pixel dimensions { width, height }
// viewport = Three.js world units at z=0
// { width, height, factor, dpr }
const isMobile = size.width < 768;
const scale = Math.min(viewport.width / 8, 1);
return (
<mesh scale={scale}>
<boxGeometry />
<meshStandardMaterial color="coral" />
</mesh>
);
}The viewport object tells you how much 3D space is visible at the camera's current settings. Use viewport.width and viewport.height to position and scale objects relative to the visible area.
Step 3: Control device pixel ratio
// DPR controls rendering resolution
// Higher = sharper but more GPU work
// Auto-detect but clamp
<Canvas dpr={[1, 2]}>
{/* Uses device DPR but never above 2 */}
</Canvas>
// Performance mode: always 1x
<Canvas dpr={1}>
{/* Best performance, slightly less sharp */}
</Canvas>
// Check DPR at runtime
function Scene() {
const dpr = useThree((s) => s.viewport.dpr);
// Reduce particle count on low DPR
const count = dpr > 1 ? 10000 : 5000;
}A DPR of 2 means rendering 4x the pixels (width x 2 and height x 2). This can tank performance on complex scenes. The dpr prop accepts a [min, max] range to cap it while still looking sharp on Retina displays.
What you just learned
R3F's Canvas automatically fills its parent container and handles resize events.
Use CSS on a wrapper div to control the Canvas size — never set fixed pixels on Canvas itself.
useThree provides viewport dimensions in Three.js units for positioning objects relative to the visible area.
Control DPR with the dpr prop on Canvas: dpr={[1, 2]} balances sharpness and performance.
Question
If a Retina display with DPR 3 renders at 3x resolution, and your scene already struggles at 30fps — should you prioritize visual sharpness or smooth frame rate? At what DPR do diminishing returns make higher resolution imperceptible?
Think about it...
You have 5 objects spaced evenly across the screen. On a wide monitor they look great, but on a phone they overlap. How do you fix this using useThree?
Hint: The useThree hook gives you viewport dimensions in Three.js world units...
Try These Challenges
Put what you learned into practice. Try each challenge in the demo above using the Leva controls, then check the solution.
Try This!
Beginner
Set DPR to 1 — pixelated!
Try This!
Beginner
Resize your browser — shapes adapt
Try This!
Beginner
Set DPR to 3 — crispy but slow
These are the patterns that trip up developers most often. Switch between Wrong and Fixed to compare the code side by side.
// WRONG: Fixed pixel dimensions
<Canvas style={{ width: 800, height: 600 }}>
<MyScene />
</Canvas>
// Scene stays 800x600 regardless of viewport// No DPR control — uses device default
// Retina Mac: DPR 2 = 4x the pixels!
// That's a LOT of GPU work
<Canvas>
<ExpensiveScene />
</Canvas>// Positions hardcoded for 16:9
<mesh position={[-6, 0, 0]}> {/* Off screen on phone! */}
<mesh position={[6, 0, 0]}> {/* Also off screen! */}Best Practices
Size with CSS, Not Props
Wrap Canvas in a div and control sizing with Tailwind or CSS. R3F auto-fills its parent and handles all resize events.
Cap DPR at 2
Use dpr={[1, 2]} on Canvas. DPR above 2 rarely improves visual quality but significantly increases rendering cost.
Use viewport for Positioning
Read viewport.width and viewport.height from useThree to position objects relative to the visible area, ensuring they adapt to any aspect ratio.
Test on Real Devices
Browser DevTools device emulation doesn't accurately represent mobile GPU performance. Test on actual phones to catch frame rate issues early.