React Three Fiber — 3D-графика в React

10 сентября 2026
Автор

Антон Ларичев

React Three Fiber (R3F) — это React-рендерер для Three.js. Вместо императивного создания объектов через new THREE.Mesh() вы описываете 3D-сцену декларативно: каждый объект Three.js становится JSX-компонентом. Состояние сцены управляется через хуки, анимации — через специальный хук useFrame, а взаимодействие с объектами — через стандартные React-события.

Главное преимущество — вы остаётесь в привычной экосистеме React: переиспользуемые компоненты, Context, Suspense для загрузки ресурсов, и всё это без необходимости вручную синхронизировать состояние с Three.js.

Установка

Для работы нужны три пакета: сам Three.js, рендерер и вспомогательная библиотека @react-three/drei с готовыми примитивами.

npm install three @react-three/fiber @react-three/drei
npm install --save-dev @types/three

Если используете React 18 и выше — дополнительной настройки не нужно, R3F поддерживает concurrent-режим из коробки.

Курс по теме

Пройти курс React и Redux Toolkit

40 000+ студентов · рейтинг 4.8 · гарантия возврата 30 дней

Основы: Canvas и первая сцена

Canvas — точка входа. Он создаёт WebGL-контекст, рендерер и цикл анимации. Все 3D-компоненты должны находиться внутри Canvas.

import { Canvas } from '@react-three/fiber'

export function App() {
  return (
    <div style={{ width: '100vw', height: '100vh' }}>
      <Canvas>
        <mesh>
          <boxGeometry args={[1, 1, 1]} />
          <meshStandardMaterial color="orange" />
        </mesh>
        <ambientLight intensity={0.5} />
        <directionalLight position={[5, 5, 5]} />
      </Canvas>
    </div>
  )
}

Ключевой момент: Canvas по умолчанию занимает 100% родительского контейнера. Контейнеру нужно явно задать размеры — иначе высота будет 0 и сцена не отобразится.

Как R3F преобразует JSX в Three.js

R3F не создаёт собственные компоненты — он проксирует конструкторы Three.js. Любой класс из THREE.* становится доступен как тег в нижнем camelCase:

// Three.js (императивно)
const geometry = new THREE.SphereGeometry(1, 32, 32)
const material = new THREE.MeshStandardMaterial({ color: 'blue' })
const mesh = new THREE.Mesh(geometry, material)
scene.add(mesh)

// React Three Fiber (декларативно)
<mesh>
  <sphereGeometry args={[1, 32, 32]} />
  <meshStandardMaterial color="blue" />
</mesh>

Проп args передаётся в конструктор как массив аргументов. Все остальные пропы устанавливаются как свойства объекта:

<mesh position={[0, 1, 0]} rotation={[0, Math.PI / 4, 0]} scale={1.5}>
  <boxGeometry args={[1, 2, 1]} />
  <meshStandardMaterial color="#2196f3" wireframe={false} />
</mesh>

Освещение

Без освещения meshStandardMaterial и meshPhongMaterial будут полностью чёрными — эти материалы рассчитывают освещение физически. Для отладки можно использовать meshBasicMaterial, который не зависит от света.

<Canvas>
  {/* Фоновый свет — равномерно освещает все поверхности */}
  <ambientLight intensity={0.4} />

  {/* Направленный свет — имитирует солнце, отбрасывает тени */}
  <directionalLight
    position={[10, 10, 5]}
    intensity={1}
    castShadow
    shadow-mapSize={[2048, 2048]}
  />

  {/* Точечный свет — излучает во все стороны из точки */}
  <pointLight position={[-5, 3, 0]} intensity={0.8} color="#ff6b6b" />

  <mesh receiveShadow castShadow>
    <boxGeometry />
    <meshStandardMaterial color="white" />
  </mesh>
</Canvas>

Чтобы включить тени, на Canvas нужно передать shadows, а на объектах — castShadow и receiveShadow:

<Canvas shadows>
  {/* ... */}
</Canvas>

Анимации: хук useFrame

useFrame — главный инструмент для анимаций. Колбэк вызывается каждый кадр синхронно с рендер-циклом Three.js, в обход React-рендеринга. Это важно: мутации объектов внутри useFrame не вызывают перерисовку компонента.

import { useRef } from 'react'
import { useFrame } from '@react-three/fiber'
import { Mesh } from 'three'

function RotatingBox() {
  const meshRef = useRef<Mesh>(null)

  useFrame((state, delta) => {
    if (!meshRef.current) return
    meshRef.current.rotation.x += delta
    meshRef.current.rotation.y += delta * 0.5
  })

  return (
    <mesh ref={meshRef}>
      <boxGeometry args={[1, 1, 1]} />
      <meshStandardMaterial color="orange" />
    </mesh>
  )
}

delta — время в секундах с предыдущего кадра. Используйте delta вместо фиксированных инкрементов, чтобы анимация была независима от частоты кадров.

state содержит полезные данные сцены:

useFrame((state) => {
  // Время с момента запуска
  const t = state.clock.elapsedTime

  // Синусоидальное покачивание
  meshRef.current.position.y = Math.sin(t) * 0.5

  // Камера
  console.log(state.camera.position)

  // Размеры viewport
  console.log(state.size.width, state.size.height)
})

Взаимодействие с объектами

R3F реализует систему событий поверх raycasting Three.js. События вешаются прямо на JSX-теги:

function InteractiveCube() {
  const [hovered, setHovered] = useState(false)
  const [clicked, setClicked] = useState(false)

  return (
    <mesh
      onClick={() => setClicked((c) => !c)}
      onPointerOver={() => setHovered(true)}
      onPointerOut={() => setHovered(false)}
      scale={clicked ? 1.5 : 1}
    >
      <boxGeometry />
      <meshStandardMaterial color={hovered ? '#ff6b6b' : '#4ecdc4'} />
    </mesh>
  )
}

Доступные события: onClick, onDoubleClick, onContextMenu, onPointerDown, onPointerUp, onPointerMove, onPointerOver, onPointerOut, onPointerEnter, onPointerLeave, onPointerCancel, onWheel.

Обработчик получает объект события с расширенными данными:

<mesh
  onClick={(event) => {
    event.stopPropagation() // Остановить обработку вложенных объектов
    console.log(event.point) // THREE.Vector3 — точка пересечения
    console.log(event.face)  // Грань меша
    console.log(event.distance) // Расстояние от камеры
  }}
>

Библиотека @react-three/drei

drei — набор готовых хуков и компонентов для частых задач. Без неё пришлось бы писать намного больше кода.

OrbitControls — управление камерой

import { OrbitControls } from '@react-three/drei'

<Canvas>
  <OrbitControls
    enableZoom={true}
    enablePan={true}
    minDistance={2}
    maxDistance={20}
    maxPolarAngle={Math.PI / 2}
  />
  {/* сцена */}
</Canvas>

Загрузка моделей

import { useGLTF } from '@react-three/drei'

function Model() {
  const { scene } = useGLTF('/models/robot.glb')
  return <primitive object={scene} />
}

// Предзагрузка до монтирования компонента
useGLTF.preload('/models/robot.glb')

Текст в 3D

import { Text } from '@react-three/drei'

<Text
  position={[0, 2, 0]}
  fontSize={0.5}
  color="white"
  anchorX="center"
  anchorY="middle"
>
  Привет, мир!
</Text>

Environment и освещение на основе HDRI

import { Environment } from '@react-three/drei'

<Canvas>
  <Environment preset="city" background />
  <mesh>
    <sphereGeometry args={[1, 64, 64]} />
    {/* envMapIntensity добавляет отражения из Environment */}
    <meshStandardMaterial metalness={1} roughness={0} />
  </mesh>
</Canvas>

Практический пример: анимированная галерея объектов

Соберём сцену с несколькими вращающимися объектами, которые реагируют на наведение мыши и меняют скорость при клике:

import { useRef, useState } from 'react'
import { Canvas, useFrame } from '@react-three/fiber'
import { OrbitControls, Environment } from '@react-three/drei'
import { Mesh } from 'three'

type ShapeProps = {
  position: [number, number, number]
  color: string
  speed?: number
}

function AnimatedShape({ position, color, speed = 1 }: ShapeProps) {
  const ref = useRef<Mesh>(null)
  const [hovered, setHovered] = useState(false)
  const [fast, setFast] = useState(false)

  useFrame((state, delta) => {
    if (!ref.current) return
    const multiplier = fast ? 4 : hovered ? 2 : 1
    ref.current.rotation.x += delta * speed * multiplier
    ref.current.rotation.y += delta * speed * 0.7 * multiplier
  })

  return (
    <mesh
      ref={ref}
      position={position}
      onClick={() => setFast((f) => !f)}
      onPointerOver={() => setHovered(true)}
      onPointerOut={() => setHovered(false)}
      scale={hovered ? 1.2 : 1}
    >
      <icosahedronGeometry args={[0.7, 0]} />
      <meshStandardMaterial
        color={color}
        metalness={0.3}
        roughness={0.4}
        emissive={color}
        emissiveIntensity={hovered ? 0.3 : 0}
      />
    </mesh>
  )
}

const SHAPES = [
  { position: [-3, 0, 0] as [number, number, number], color: '#ff6b6b', speed: 0.8 },
  { position: [0, 0, 0] as [number, number, number], color: '#4ecdc4', speed: 1.2 },
  { position: [3, 0, 0] as [number, number, number], color: '#ffe66d', speed: 1.0 },
]

export function Gallery() {
  return (
    <div style={{ width: '100vw', height: '100vh', background: '#1a1a2e' }}>
      <Canvas camera={{ position: [0, 0, 8], fov: 60 }}>
        <ambientLight intensity={0.2} />
        <Environment preset="night" />

        {SHAPES.map((props, i) => (
          <AnimatedShape key={i} {...props} />
        ))}

        <OrbitControls enableZoom={false} />
      </Canvas>
    </div>
  )
}

Производительность

useMemo для геометрий и материалов

Если геометрия или материал не зависят от пропов — вынесите их за пределы компонента или используйте useMemo. Каждый рендер иначе будет создавать новые объекты Three.js:

import { useMemo } from 'react'
import { SphereGeometry, MeshStandardMaterial } from 'three'

function OptimizedSphere() {
  const geometry = useMemo(() => new SphereGeometry(1, 64, 64), [])
  const material = useMemo(
    () => new MeshStandardMaterial({ color: 'blue', wireframe: true }),
    []
  )

  return <mesh geometry={geometry} material={material} />
}

instancedMesh для повторяющихся объектов

Для тысяч одинаковых объектов используйте instancedMesh — он отправляет все трансформации в один draw call:

import { useRef, useEffect } from 'react'
import { InstancedMesh, Matrix4, Color } from 'three'

function Particles({ count = 1000 }) {
  const ref = useRef<InstancedMesh>(null)

  useEffect(() => {
    if (!ref.current) return
    const matrix = new Matrix4()
    const color = new Color()

    for (let i = 0; i < count; i++) {
      matrix.setPosition(
        (Math.random() - 0.5) * 20,
        (Math.random() - 0.5) * 20,
        (Math.random() - 0.5) * 20
      )
      ref.current.setMatrixAt(i, matrix)
      ref.current.setColorAt(i, color.setHSL(i / count, 1, 0.5))
    }
    ref.current.instanceMatrix.needsUpdate = true
  }, [count])

  return (
    <instancedMesh ref={ref} args={[undefined, undefined, count]}>
      <sphereGeometry args={[0.05, 8, 8]} />
      <meshBasicMaterial />
    </instancedMesh>
  )
}

Загрузка ресурсов с Suspense

R3F поддерживает React Suspense для асинхронной загрузки текстур и моделей. Оберните компоненты с ресурсами в Suspense:

import { Suspense } from 'react'
import { useTexture } from '@react-three/drei'

function Earth() {
  const texture = useTexture('/textures/earth.jpg')

  return (
    <mesh>
      <sphereGeometry args={[2, 64, 64]} />
      <meshStandardMaterial map={texture} />
    </mesh>
  )
}

export function Scene() {
  return (
    <Canvas>
      <Suspense fallback={null}>
        <Earth />
      </Suspense>
    </Canvas>
  )
}

fallback={null} означает, что пока модель грузится — ничего не показывается. Можно передать другой компонент-заглушку.

Когда использовать React Three Fiber

R3F хорошо подходит для:

  • Интерактивных 3D-конфигураторов продуктов
  • Визуализации данных в трёхмерном пространстве
  • Игровых интерфейсов и казуальных браузерных игр
  • Анимированных лендингов с 3D-элементами
  • Образовательных симуляций

Если нужна максимальная производительность для AAA-игр или сложных физических симуляций — Three.js напрямую даст больше контроля. Но для большинства задач накладные расходы R3F минимальны и не ощущаются.

Подробнее о работе с компонентами и хуками в React можно узнать на курсе React — Полный курс.

Стрелочка влевоВизуализация данных в React с RechartsReact forwardRef — передача рефов дочерним компонентамСтрелочка вправо

Постройте личный план изучения React до уровня Middle — бесплатно!

React — часть карты развития Frontend

  • step100+ шагов развития
  • lessons30 бесплатных лекций
  • lessons300 бонусных рублей на счет

Бесплатные лекции

Все гайды по React

React тестирование с Vitest
React с TanStack Router
Uncontrolled Components: когда DOM управляет даннымиБезопасность в React: защита от XSS, CSRF и утечек данныхRender Props: гибкое управление рендерингом в ReactРефакторинг React-кода: техники и лучшие практикиReact с WebSocket — реалтайм в компонентахReact Native приложение с нуля до публикации в сторахПрофилирование React: как найти и устранить узкие местаЧастичное применение: как создавать компоненты без лишнего кодаИменование компонентов в React: соглашения и лучшие практикиЛенивая загрузка: как ускорить React-приложение в разыОшибки гидратации в React и способы их устраненияHOC в React: мастерство композиции компонентовuseMemo: как спасти производительность от тяжелых вычисленийError Boundaries: создаем надежные React-приложенияКонтролируемые компоненты в React: полный контроль над формамиCompound Components в React: создаем гибкие компоненты с мощным APIДокументирование компонентов в React: Storybook, JSDoc и READMEКомпозиция компонентов в React: строим гибкие интерфейсыКомментирование кода в React: когда и как писать комментарииCode Splitting в React: как уменьшить бандл и ускорить загрузку приложенияАсинхронные компоненты в React: новый стандарт работы с даннымиДоступность (a11y) в React: ARIA, семантика и клавиатурная навигация
Zustand — управление состоянием в ReactZod - валидация с TypeScriptYup - валидация схемXState - конечные автоматыТемизация в ReactТестирование хуковTailwind CSS с ReactSWR - библиотека для запросовStyled Components — стилизация через JSStorybook - документация компонентовSnapshots тестированиеRTK Query - работа с APIRedux Toolkit - современный ReduxRecoil — библиотека управления состоянием от FacebookВиртуализация списков с react-window: как отображать тысячи элементов без лаговReact Toastify - уведомления в ReactReact Testing LibraryСоздание таблиц в React гайд по react-tableReact Spring - анимацииРабота с формами и селектами в ReactReact Query (TanStack Query) - работа с серверомПлагины в React что это и как их использоватьReact PDF - работа с PDF файламиОбзор популярных библиотек для ReactReact Icons - библиотека иконок для ReactReact Hook Form — валидация форм в ReactReact Dropzone — загрузка файловПодключение Bootstrap к React-приложениюReact Beautiful DnD - перетаскивание элементовАнимация при монтировании компонентов в ReactМокирование APIМикрофронтенды с React (micro-frontends)MobX — реактивное управление состоянием в ReactЗагрузка и индикаторыАнимация списков в ReactJotai - атомарное состояниеБесконечная прокруткаFramer Motion - библиотека анимацийEmotion — библиотека CSS-in-JSДинамические стили в ReactE2E тестирование с CypressCSSTransition - переходыCSS-in-JS — плюсы и минусыКонтекст vs Redux — когда что использоватьИспользование Chart.js в ReactAxios с ReactТестирование асинхронных компонентовОбработка ошибок API
useState в React что это и как использоватьuseTransition - плавные переходы между состояниямиuseSyncExternalStore — работа с внешними сторамиuseRef в React — создание ссылок на DOM и значенияuseOptimistic — оптимистичные обновления UIuseLayoutEffect в React — эффект до отрисовкиuseInsertionEffect — внедрение стилей до мутаций DOMuseImperativeHandle в React — настройка ref дочернего компонентаuseId — генерация уникальных идентификаторовuseFormStatus - отслеживание статуса отправки формыuseDeferredValue — отложенное обновление состоянияuseDebugValue — отладка кастомных хуковuseCallback в React — мемоизация функцийuseReducer — альтернатива useState для сложной логикиuseMemo в React: как и когда оптимизировать тяжелые вычисленияХук use() в React 19useEffect в React что это и как использоватьuseContext — работа с контекстом в ReactuseCallback в React — мемоизация функций и оптимизация ре-рендеровuseActionState в React 19Оптимизация рендеринга в React: от теории к глубокой практикеЧто такое useRef и как его применять в ReactКак и зачем использовать React HooksУправление состоянием в React через ContextНовые возможности React 19Как предотвратить лишние ре-рендеры в React: полное руководствоuseMemo vs useCallback: подробное руководство по мемоизации в ReactПравила хуков — правила использованияflushSync — синхронные обновления состояния в ReactuseEffect vs useLayoutEffect: в чём разница и какой хук выбрать?Кастомные хуки в React — создание собственных хуковuseState продвинутое использование в React
Transition API — плавные обновления интерфейса в ReactReact Suspense — приостановка рендераStrictMode в React — как находить ошибки на этапе разработкиСерверные компоненты React (RSC) — подробный разбор и практикаКак работает рендеринг в ReactЧто такое props в React и как их правильно использоватьКак работает JSX связка React и HTMLЧто такое React.js и как его использоватьКак использовать элементы в ReactКак использовать React DOM в проектеЧто такое компоненты в React и как их применятьРабота с children в ReactПорталы в React: рендер компонентов вне иерархии DOMFragment в React: группировка элементов без лишних узлов DOMCSS Modules в ReactConcurrent Mode — конкурентный режим в React
Открыть базу знаний

Лучшие курсы по теме

изображение курса

React и Redux Toolkit

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

TypeScript с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Next.js - с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее

Отправить комментарий