Navigate

Search topics across all sections

GitHub
Transforms & Color

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.

terminal
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

App.tsxTSX
// 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

App.tsxTSX
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

App.tsxTSX
// 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.

1
Setting a fixed pixel size on the Canvas
Your 3D scene doesn't resize when the browser window changes
Don't do this
App.tsxTSX
// WRONG: Fixed pixel dimensions
<Canvas style={{ width: 800, height: 600 }}>
  <MyScene />
</Canvas>
// Scene stays 800x600 regardless of viewport
R3F's Canvas component automatically fills its parent container and listens for resize events. Set dimensions on a wrapper div using CSS, and the Canvas adapts. Fixed pixel sizes break responsiveness.
2
Not accounting for device pixel ratio (DPR)
Scene looks blurry on Retina displays or too expensive on mobile
Don't do this
App.tsxTSX
// No DPR control — uses device default
// Retina Mac: DPR 2 = 4x the pixels!
// That's a LOT of GPU work
<Canvas>
  <ExpensiveScene />
</Canvas>
Device pixel ratio determines how many physical pixels fill one CSS pixel. DPR 2 means 4x the rendering work. Pass dpr={[min, max]} to Canvas to cap it. Use lower DPR for complex scenes, higher for crisp text and simple visuals.
3
Hardcoding object positions for one aspect ratio
Objects clip off-screen on portrait phones or pile up on ultrawide monitors
Don't do this
App.tsxTSX
// Positions hardcoded for 16:9
<mesh position={[-6, 0, 0]}> {/* Off screen on phone! */}
<mesh position={[6, 0, 0]}>  {/* Also off screen! */}
useThree gives you viewport dimensions in Three.js units. Use these to compute positions and scales that adapt to any screen size. Objects stay within view on phones and spread out naturally on wider screens.

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.