Реалізація Canvas-анімацій на сайті

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Реалізація Canvas-анімацій на сайті
Складна
~3-5 робочих днів
Часті питання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    874
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851

Реалізація Canvas-анімацій на сайті

Canvas-анімації — це прямий рендеринг через 2D або WebGL контекст браузера. На відміну від SVG та DOM-анімацій, Canvas перерисовує весь кадр цілком при кожному тику — це дає максимальну продуктивність для тисяч об'єктів, але потребує ручного управління відрисовкою. Застосування: частинки, фізичні симуляції, процедурні ефекти, ігрові механіки, візуалізація даних у реальному часі.

Архітектура Canvas-анімації

Стандартний цикл: ініціалізація → requestAnimationFrame → очищення кадру → відрисовка об'єктів → оновлення стану → наступний кадр.

// lib/canvas-engine.ts
export interface AnimationContext {
  canvas: HTMLCanvasElement
  ctx: CanvasRenderingContext2D
  width: number
  height: number
  dpr: number  // device pixel ratio
  dt: number   // дельта часу в секундах
}

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()

    // Висока роздільна здатність для retina
    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) // обмежено 100ms
    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-хук для Canvas

// 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     // 0–1
  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 // px/s²

    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()
    }
  }
}
// components/ParticleCanvas.tsx
import { useRef, useCallback } from 'react'
import { useCanvas } from '../hooks/useCanvas'
import { ParticleSystem } from '../lib/particle-system'

export function ParticleCanvas() {
  const systemRef = useRef(new ParticleSystem(800))

  const render = useCallback(({ ctx, width, height, dt }: AnimationContext) => {
    // Очищення з напівпрозорим слідом (ефект motion blur)
    ctx.fillStyle = 'rgba(15, 15, 25, 0.15)'
    ctx.fillRect(0, 0, width, height)

    systemRef.current.update(dt)
    systemRef.current.draw(ctx)

    // Автоматична емісія в центрі
    if (Math.random() < 0.3) {
      systemRef.current.emit(
        width / 2 + (Math.random() - 0.5) * 100,
        height / 2
      )
    }
  }, [])

  const canvasRef = useCanvas(render)

  const handleClick = (e: React.MouseEvent<HTMLCanvasElement>) => {
    const rect = canvasRef.current!.getBoundingClientRect()
    systemRef.current.emit(e.clientX - rect.left, e.clientY - rect.top, 20)
  }

  return (
    <canvas
      ref={canvasRef}
      className="w-full h-full bg-[#0f0f19] cursor-crosshair"
      onClick={handleClick}
    />
  )
}

WebGL через Three.js: наступний рівень

Для складних 3D-сцен на фоні сайту:

npm install three @types/three
// 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" />
}

Off-screen Canvas (Web Worker)

Для дуже важких обчислень — переносимо рендеринг в Worker через OffscreenCanvas:

// main thread
const canvas = document.getElementById('my-canvas') as HTMLCanvasElement
const offscreen = canvas.transferControlToOffscreen()

const worker = new Worker(new URL('./canvas-worker.ts', import.meta.url))
worker.postMessage({ canvas: offscreen, width: canvas.width, height: canvas.height }, [offscreen])
// canvas-worker.ts
self.onmessage = (e) => {
  const { canvas, width, height } = e.data
  const ctx = canvas.getContext('2d')!
  // Весь рендеринг тут, у Worker
}

Типові строки

Проста Canvas-анімація (частинки, хвилі) — 1–2 робочих дні. Повноцінна система частинок з фізикою, інтерактивністю та оптимізацією — 3–5 днів. Three.js сцена з шейдерами, постпроцесингом та адаптивним масштабуванням — від 1 тижня.