Дайджесты новостей
Процедурная генерация мерцающего пламени на HTML5 Canvas путем сложения трех некратных синусоид и кривых Безье.

Магия трёх синусоид: процедурная анимация пламени на HTML5 Canvas

Анимация органических природных явлений — огня, дыма, колыхания воды — традиционно требует применения тяжелой артиллерии: систем частиц, шейдеров WebGL или покадровых спрайтов. Однако когда огонь нужен для компактного микровзаимодействия или пасхалки, такие инструменты избыточно расходуют ресурсы процессора и батареи.

Испанский дизайнер и специалист по креативному кодингу Кармен Ансио (Carmen Ansio) предложила математически изящное решение: реалистичную симуляцию горящей спички на стандартном двухмерном контексте HTML5 Canvas (CanvasRenderingContext2D). Физика мерцания и дыхания пламени занимает пару десятков строк чистого JavaScript без сторонних библиотек и шума Перлина.

Математика мерцания: некратные частоты и неповторяющийся шум

Анимация огня с помощью одной функции Math.sin(t) выглядит механически: человеческий глаз мгновенно считывает равномерное движение маятника.

Секрет живой органики заключается в интерференции нескольких волн с некратными частотами:

  • Базовая гармоника Math.sin(t * 1.7) создает широкое раскачивание языка пламени.
  • Вторая гармоника с частотой 3.3 и коэффициентом 0.5 добавляет легкий трепет (flutter).
  • Третья гармоника с высокой частотой 7.1 и коэффициентом 0.25 вносит быстрое дрожание кончика (jitter).

Сумма трех некратных синусоид образует псевдослучайный шум, повторяющийся лишь раз в 10,5 секунд. Высота пламени пульсирует по независимой синусоиде с периодом 7 секунд. Полный цикл совпадения фаз растягивается на 21 секунду — а поскольку виртуальная спичка сгорает за 13 секунд, зритель видит уникальный рисунок живого огня.

// Генератор процедурного мерцания на базе трех синусоид
function noise(t) {
  return (
    Math.sin(t * 1.7) +
    Math.sin(t * 3.3 + 1.5) * 0.5 +
    Math.sin(t * 7.1 + 0.3) * 0.25
  );
}

// Отрисовка капли пламени через четыре квадратичные кривые Безье
function drawTeardrop(ctx, sway, h, w, fillStyle) {
  ctx.beginPath();
  ctx.moveTo(0, 0); // Основание капли у спичечной головки
  ctx.quadraticCurveTo(-w, 0, -w, -h * 0.3); // Левая выпуклость
  ctx.quadraticCurveTo(-w * 0.55, -h * 0.75, sway, -h); // Подвижная вершина
  ctx.quadraticCurveTo(w * 0.55, -h * 0.75, w, -h * 0.3); // Правая выпуклость
  ctx.quadraticCurveTo(w, 0, 0, 0); // Возврат в основание
  ctx.fillStyle = fillStyle;
  ctx.fill();
}

Геометрия капли: кривые Безье и коническое затухание

Форма огня строится замкнутым контуром из четырех квадратичных кривых Безье через метод ctx.quadraticCurveTo(). Основание и боковые опорные точки жестко зафиксированы — колеблется исключительно верхняя вершина капли (sway).

Чтобы пламя казалось объемным, отрисовываются три вложенных слоя с эффектом конического затухания (Tapered Sway):

  • Внешняя полупрозрачная оболочка колеблется со 100% расчетной амплитуды.
  • Средний желтый ореол отклоняется на 80%.
  • Горячее белое ядро пламени отклоняется лишь на 60%.

Разница в отклонении создает эффект вращения пламени вокруг головки спички как вокруг естественного шарнира.

const canvas = document.getElementById('flame-canvas');
const ctx = canvas.getContext('2d');
let lastTime = performance.now();
const prefersReduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;

function renderFrame(now) {
  const dt = (now - lastTime) / 1000;
  lastTime = now;
  const t = now / 1000;

  ctx.clearRect(0, 0, canvas.width, canvas.height);

  const sway = prefersReduced ? 0 : noise(t * 6) * 8;
  const height = 70 + (prefersReduced ? 0 : noise(t * 9 + 3) * 16);

  ctx.save();
  ctx.translate(canvas.width / 2, canvas.height / 2 + 40);

  // Послойная отрисовка вложенных капель
  drawTeardrop(ctx, sway, height * 1.15, 26, 'rgba(255, 110, 40, 0.55)');   // Оболочка
  drawTeardrop(ctx, sway * 0.8, height, 18, 'rgba(255, 175, 60, 0.85)');    // Средний слой
  drawTeardrop(ctx, sway * 0.6, height * 0.62, 10, 'rgba(255, 240, 190, 0.95)'); // Ядро

  ctx.restore();
  requestAnimationFrame(renderFrame);
}

requestAnimationFrame(renderFrame);

Цикл отрисовки: дельта времени, 60 fps и доступность

Код учитывает стандарты производительности. Все расчеты движения масштабируются на дельту времени dt, гарантируя одинаковую скорость анимации на экранах 60 Гц и 120 Гц.

При включенном режиме prefers-reduced-motion анимация отключает колебания и искры, плавно проявляя пламя через прозрачность. Подход Кармен Ансио демонстрирует силу чистого кода: знание базовой тригонометрии способно заменить сотни килобайт внешних графических библиотек.