R3F-сцены, которые не убивают батарею ноутбука: гайд 2026
После трёх жалоб «у меня вентилятор взлетает на вашем сайте» мы пересобрали 6 сцен на React Three Fiber. Расход энергии упал в 4–7 раз без потерь визуала.
Дайджест Креастры
- Главный пожиратель — постоянный requestAnimationFrame; включаем frameloop="demand"
- DPR ограничиваем до 1.5, а не Math.min(window.devicePixelRatio, 2)
- IntersectionObserver останавливает сцену вне вьюпорта и снимает 60% потребления
Осенью 2025 нам пришли три жалобы подряд: «открываю ваш кейс — ноутбук уходит в потолок по вентиляторам». Все три — макбуки на M2 с интегрированной графикой. У нас на главных страницах студии и трёх клиентских проектах стояли R3F-сцены — морфинг продукта, фоновые частицы, интерактивный hero. Я честно посмотрел в Activity Monitor: 28% CPU и 41% GPU на пустой страничке. Мы пересобрали шесть сцен. Вот как.
Источник проблемы — постоянный rAF
По умолчанию R3F гонит requestAnimationFrame в 60 FPS, даже если на сцене ничего не движется. Это самый большой пожиратель на современных интегрированных GPU. Решение в одну строку — поставить Canvas в frameloop="demand" и руками вызывать invalidate() только при изменениях.
// frameloop="demand" — кадр рендерится только по invalidate()
<Canvas frameloop="demand" dpr={[1, 1.5]}>
<Scene />
</Canvas>
// внутри сцены — анимация по чему-то осмысленному
function Box() {
const ref = useRef<Mesh>(null!);
const { invalidate } = useThree();
useFrame((_, dt) => {
if (!hovered) return;
ref.current.rotation.y += dt;
invalidate(); // продолжаем кадры пока есть hover
});
return <mesh ref={ref} />;
}DPR и пиксельная плотность
Самая частая ошибка — поставить dpr={Math.min(window.devicePixelRatio, 2)}. На ретине это 2, и вы рисуете в 4 раза больше пикселей, чем нужно глазу. Мы зафиксировали потолок 1.5 — на ретине разница не видна, на FullHD-мониторе мы и так в 1.0. Энергопотребление упало почти вдвое.
IntersectionObserver — остановка вне вьюпорта
- Если сцена ниже фолда — она по умолчанию рендерится в фоне
- Оборачиваем Canvas в обсёрвер и снимаем frameloop, когда сцена не видна
- Возвращаем на видимости — задержка 80 мс, чтобы не дёргать при прокрутке
- Тот же приём для сцен в табах — рендерим только активную
- На странице с 4 сценами это снимает 60% общего потребления
Геометрия и материалы
GLB с 180 тысячами треугольников на странице — это почти всегда лишнее. Прогоняем через gltfpack или Blender Decimate до 35–60 тысяч, разница на сцене размером 600×400 не видна. PBR-материалы заменяем на MeshStandardMaterial без envMap там, где не нужны отражения. Тени — только для главного объекта, остальные на baked-текстуре.
Постпроцессинг — главный соблазн
Bloom, depth-of-field, SSAO выглядят красиво в дев-режиме на десктопе с дискретной видеокартой. На M2 без зарядки они превращают ноутбук в тостер. Правило: постпроцессинг включается только если prefers-reduced-motion=no-preference, navigator.deviceMemory >= 8 и не на батарее (через navigator.getBattery).
Чек-лист «не сожги ноутбук»
- frameloop="demand" по умолчанию
- DPR не выше 1.5
- IntersectionObserver на каждый Canvas
- Геометрия упрощена до целевого числа треугольников
- Без постпроцессинга на батарее
- Тени только для одного объекта в сцене
- Освещение — три источника максимум, остальное в baked
- prefers-reduced-motion отключает всё, кроме статичного poster