Интерактивные фоны, частицы и 3D-сцены — тренд, но реализация через DOM или SVG упирается в производительность: 60 FPS держатся только до сотни объектов. Canvas же позволяет рендерить тысячи частиц без лагов — но только при правильной архитектуре. Например, клиент хотел фон из 3000 частиц на React — DOM уронил FPS до 5. Переписали на Canvas с пулом частиц — получили стабильные 60 FPS на мобильном.
Мы реализуем Canvas-анимации под ключ: от простых частиц до WebGL-сцен с шейдерами. Прозрачная стоимость разработки рассчитывается исходя из сложности. Оценим ваш проект за 1 день — свяжитесь с нами для консультации.
Какие проблемы решаем
Низкая производительность при большом количестве объектов
DOM-анимации тормозят уже на 100–200 элементах. Canvas перерисовывает всё за один проход, но требует ручного управления памятью и переиспользования объектов. Мы оптимизируем пулы частиц, избегаем gc и боремся с N+1 отрисовкой. Гарантируем, что после оптимизации анимация будет работать на 60 FPS на всех целевых устройствах.
Размытые пиксели на Retina-дисплеях
Стандартный canvas рисует в CSS-пикселях — на Retina изображение получается размытым. Мы умножаем размер на devicePixelRatio и масштабируем контекст.
Сложность интеграции с React/Vue
Простое использование canvas вызывает 'hydration mismatch' и потерю состояния. Мы обернули движок в хуки useCanvas с корректным управлением жизненным циклом.
Неконтролируемая память в анимациях
Без грамотного управления объектами память растёт, вызывая GC-фризы. Мы используем объектные пулы и избегаем аллокаций в render loop.
Какую производительность даёт Canvas
Canvas в 10–50 раз быстрее SVG на тысячах объектов благодаря отсутствию DOM-накладных расходов. Мы добиваемся стабильных 60 FPS за счёт:
-
requestAnimationFrameс автоматической паузой при неактивной вкладке - Ограничения delta-time (кап 100 ms)
- Принудительного сброса gc через переиспользование объектов
Сравнение Canvas vs SVG vs DOM-анимации:
| Характеристика | Canvas | SVG | DOM-анимации |
|---|---|---|---|
| Максимум объектов | 50 000+ | 1 000 | 500 |
| Интерактивность | Своя система | По событиям | По событиям |
| Трансформации | Кастомные | CSS-свойства | CSS-свойства |
| Retina-поддержка | Через DPR | Автоматически | Автоматически |
Оптимизация под Retina и мобильные устройства включена в стоимость.
Как мы это делаем: стек и примеры
Используем TypeScript, React 18, Three.js r150. Ключевые паттерны: Repository, BFF для частиц, render loop с разделением на update/draw. Код движка tree-shake-ready, что позволяет включать только нужные модули в финальный бандл.
Базовый Canvas-движок
// lib/canvas-engine.ts export interface AnimationContext { canvas: HTMLCanvasElement ctx: CanvasRenderingContext2D width: number height: number dpr: number // device pixel ratio dt: number // delta time в секундах } export type RenderFn = (context: AnimationContext) => void export class CanvasEngine { private canvas: HTMLCanvasElement private ctx: CanvasRenderingContext2D private dpr: number private rafId: number | null = null private lastTime: number = 0 private renderFn: RenderFn constructor(canvas: HTMLCanvasElement, renderFn: RenderFn) { this.canvas = canvas this.ctx = canvas.getContext('2d')! this.dpr = window.devicePixelRatio || 1 this.renderFn = renderFn this.resize() } resize() { const { canvas, dpr } = this const rect = canvas.getBoundingClientRect() canvas.width = rect.width * dpr canvas.height = rect.height * dpr this.ctx.scale(dpr, dpr) } start() { this.lastTime = performance.now() this.tick(this.lastTime) } stop() { if (this.rafId !== null) { cancelAnimationFrame(this.rafId) this.rafId = null } } private tick = (timestamp: number) => { const dt = Math.min((timestamp - this.lastTime) / 1000, 0.1) this.lastTime = timestamp const rect = this.canvas.getBoundingClientRect() this.renderFn({ canvas: this.canvas, ctx: this.ctx, width: rect.width, height: rect.height, dpr: this.dpr, dt, }) this.rafId = requestAnimationFrame(this.tick) } } React-хук useCanvas
// hooks/useCanvas.ts import { useEffect, useRef } from 'react' import { CanvasEngine, RenderFn } from '../lib/canvas-engine' export function useCanvas(renderFn: RenderFn) { const canvasRef = useRef<HTMLCanvasElement>(null) const engineRef = useRef<CanvasEngine | null>(null) useEffect(() => { const canvas = canvasRef.current if (!canvas) return const engine = new CanvasEngine(canvas, renderFn) engineRef.current = engine engine.start() const handleResize = () => engine.resize() window.addEventListener('resize', handleResize) return () => { engine.stop() window.removeEventListener('resize', handleResize) } }, [renderFn]) return canvasRef } Система частиц с физикой
// lib/particle-system.ts interface Particle { x: number y: number vx: number vy: number radius: number color: string life: number maxLife: number } export class ParticleSystem { private particles: Particle[] = [] private readonly maxParticles: number constructor(maxParticles = 500) { this.maxParticles = maxParticles } emit(x: number, y: number, count = 5) { for (let i = 0; i < count; i++) { if (this.particles.length >= this.maxParticles) break const angle = Math.random() * Math.PI * 2 const speed = 50 + Math.random() * 150 this.particles.push({ x, y, vx: Math.cos(angle) * speed, vy: Math.sin(angle) * speed - 100, radius: 2 + Math.random() * 4, color: `hsl(${200 + Math.random() * 60}, 80%, 60%)`, life: 1, maxLife: 0.8 + Math.random() * 0.8, }) } } update(dt: number) { const gravity = 300 this.particles = this.particles.filter(p => { p.x += p.vx * dt p.y += p.vy * dt p.vy += gravity * dt p.vx *= 0.99 p.life -= dt / p.maxLife return p.life > 0 }) } draw(ctx: CanvasRenderingContext2D) { for (const p of this.particles) { ctx.save() ctx.globalAlpha = p.life * p.life ctx.fillStyle = p.color ctx.beginPath() ctx.arc(p.x, p.y, p.radius * p.life, 0, Math.PI * 2) ctx.fill() ctx.restore() } } } Пример компонента: ParticleCanvas — интерактивная система частиц с гравитацией, автоматической эмиссией и кликом мыши.
Подробнее о шейдерах
Для нестандартных эффектов (свечение, маски, волны) пишем кастомные шейдеры GLSL. Они выполняются на GPU, что экономит ресурсы CPU и повышает FPS. Шейдеры интегрируются через Three.js ShaderMaterial или нативный WebGL.Почему Three.js — стандарт для 3D-анимаций на сайте?
Для сложных 3D-сцен на фоне сайта используем Three.js — он даёт готовые шейдеры, постпроцессинг и совместимость с WebGL 2.0. Мы пишем кастомные материалы и анимации, контролируя память через BufferGeometry.
// components/ThreeBackground.tsx 'use client' import { useEffect, useRef } from 'react' import * as THREE from 'three' export function ThreeBackground() { const mountRef = useRef<HTMLDivElement>(null) useEffect(() => { const mount = mountRef.current! const width = mount.clientWidth const height = mount.clientHeight const scene = new THREE.Scene() const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000) camera.position.z = 50 const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true }) renderer.setSize(width, height) renderer.setPixelRatio(window.devicePixelRatio) mount.appendChild(renderer.domElement) const count = 3000 const positions = new Float32Array(count * 3) for (let i = 0; i < count * 3; i++) { positions[i] = (Math.random() - 0.5) * 200 } const geometry = new THREE.BufferGeometry() geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3)) const material = new THREE.PointsMaterial({ size: 0.3, color: 0x3b82f6, transparent: true, opacity: 0.7, }) const points = new THREE.Points(geometry, material) scene.add(points) let rafId: number const animate = () => { rafId = requestAnimationFrame(animate) points.rotation.x += 0.0003 points.rotation.y += 0.0005 renderer.render(scene, camera) } animate() const handleResize = () => { const w = mount.clientWidth const h = mount.clientHeight camera.aspect = w / h camera.updateProjectionMatrix() renderer.setSize(w, h) } window.addEventListener('resize', handleResize) return () => { cancelAnimationFrame(rafId) window.removeEventListener('resize', handleResize) renderer.dispose() mount.removeChild(renderer.domElement) } }, []) return <div ref={mountRef} className="absolute inset-0 -z-10" /> } Как Canvas-анимации влияют на Core Web Vitals?
Правильно спроектированная Canvas-анимация не ухудшает LCP, CLS и INP. Мы используем requestAnimationFrame и Suspense для ленивой загрузки, чтобы не блокировать основной поток. В результате анимация стартует только после интерактива страницы, не замедляя первый рендер.
Процесс работы
- Аналитика — изучаем дизайн, требования к производительности, целевые устройства.
- Прототипирование — создаём MVP анимации с базовыми параметрами.
- Реализация — пишем движок с паттернами Repository, подключаем React-обвязку.
- Оптимизация — режем draw calls, добавляем LOD, проверяем Core Web Vitals.
- Деплой — настраиваем CI/CD, тесты на реальных устройствах.
Сроки ориентировочно
| Тип анимации | Сроки |
|---|---|
| Простые частицы / волны | 1–2 дня |
| Интерактивная система (физика) | 3–5 дней |
| 3D-сцена с шейдерами | 1–2 недели |
| Комплексный проект (анимация + бизнес-логика) | 2–3 недели |
Что входит в услугу
- Исходный код (TS/React-компоненты, документация JSDoc)
- Оптимизация под 60 FPS на целевых устройствах
- Адаптация под мобильные и Retina
- Интеграция с вашим приложением (Next.js, Nuxt, CRA)
- Техническая поддержка 2 недели после сдачи
Сертифицированные специалисты Three.js и WebGL. Гарантия производительности 60 FPS. Закажите разработку Canvas-анимации в TrueTech и получите стабильные 60 FPS на любых устройствах.







