Pull to refresh
0
Microsoft
Microsoft — мировой лидер в области ПО и ИТ-услуг

Запускаем фрактальные снежинки на HTML5 Canvas

Reading time6 min
Views39K
Предновогоднее развлечение на HTML5 Canvas по украшению сайта снежинками (ну и просто интресный пример посмотреть, как работает Canvas).

В своем рассказе я буду отталкиваться от кода Giorgio Sardo, который в свою очередь базируется на коде David Flanagan.



Все, что описано ниже, вы можете попробовать непосредственно здесь, на Хабре в любом современном браузере со средствами разработки, просто запустив консоль JavaScript. В IE9 достаточно нажать F12 и, если вы хотите тестировать прямо на этой странице, не забудьте перевести браузер в режим Internet Explorer 9 Standards (Alt + 9), т.к. по умолчанию Хабр требует режима IE8.

Проверка поддержки Canvas



Прежде всего, надо начать с того, что нужно убедиться, что браузер поддерживает Canvas, для этого нужно создать элемент Canvas и попробовать добраться до контекста работы:

if (document.createElement('canvas').getContext) {
  ...
}
else {
  ...
}


В первом случае можно двигаться дальше и запускать снежинки.

Создаем холст



Для отрисовки снежинок мы создатим холст (Canvas) на весь экран:

var canvas = document.createElement('canvas');
canvas.style.position = 'fixed';
canvas.style.top = '0px';
canvas.style.left = '0px';
canvas.style.zIndex = '-10';
canvas.width = document.body.offsetWidth;
canvas.height = window.innerHeight;

document.body.insertBefore(canvas, document.body.firstChild);


В данном случае мы создаем новый элемент Canvas и задаем ему фиксированное расположение, старясь разместить его так, чтобы он не мещал остальным элементам.

Далее мы получаем контекст для отрисовки:

var sky = canvas.getContext('2d');


Снежинка Коха



Думаю, хабраюзерам, должно быть хорошо известно, что такое Снежинка Коха, поэтому ограничусь картинкой:



Штука эта фрактальная и удобно рисуется рекурсивно. Чтобы отрисовать треугольник, нужно к каждому из его ребер применить последовательно один и тот же паттерн отрисовки:



Давайте начнем с того, что попробуем отрисовать одну линию. При отрисовке, что удобно, мы можем применять трансформации (масштабирование и повороты), при которых каждая локальная отрисовка будет выглядеть как отрисовка прямой горизонтальной линии. То есть мы масштабируем и поворачиваем контекст (меняем матрицу трансформации) вместо поворота отрисовки линии.

Для сохранения и восстановления состояния матрицы трансформации используются соответственно функции save() и restore().

По ходу работы нам понадобится конвертировать градусы в радианы (хотя при желании можно и сразу в радианах писать):

var deg = Math.PI / 180;


Рекурсивная функция для отрисовки одного ребра выглядит так:

function leg(n, len) {
  sky.save();       // Сохраняем текущую трансформацию
  if (n == 0) {      // Нерекурсивный случай - отрисовываем линию
    sky.lineTo(len, 0);    }
  else {
    sky.scale(1 / 3, 1 / 3);  // Уменьшаем масштаб в 3 раза
        leg(n – 1, len);             sky.rotate(60 * deg);
        leg(n – 1, len);              sky.rotate(-120 * deg);
        leg(n – 1, len);              sky.rotate(60 * deg);          leg(n – 1, len);        }
  sky.restore();      // Восстанавливаем трансформацию
  sky.translate(len, 0); // переходим в конец ребра
}


Для запуска отрисовки можно использовать такую функцию:

function drawFlake(x, y, len, n, stroke, fill) {
  sky.save();    sky.strokeStyle = stroke;
  sky.fillStyle = fill;
  sky.beginPath();
  sky.translate(x, y);
  sky.moveTo(0, 0);    leg(n, len);         sky.closePath();
  sky.fill();
  sky.stroke();
  sky.restore();
}


Обратите внимание, что для отрисовки нужно запустить создание пути, если нужно, закрыть его и только потом сказать, что нужно сделать закраску областей и прорисовку линий. Результат:



Если добавить еще несколько ребер с соответствующими поворотами, получим снежинку:

function drawFlake(x, y, len, n, stroke, fill) {
  sky.save();        sky.strokeStyle = stroke;
  sky.fillStyle = fill;
  sky.beginPath();
  sky.translate(x, y);
  sky.moveTo(0, 0);    leg(n, len);         sky.rotate(-120 * deg);
  leg(n, len);         sky.rotate(-120 * deg);
  leg(n, len);
         sky.closePath();
  sky.fill();
  sky.stroke();
  sky.restore();
}


Результат:



Создание и перемещение снежинок



Дальше идея довольно прозрачная: 1) создаем пул снежинок, повесив добавление снежинок на таймер, 2) по таймеру меняем положение снежинок и делаем отрисовку.

Добавление снежинок


Дополнительная функция для случайных значений, массив снежинок и максимальное количество. Задаем таймер:

var rand = function (n) { return Math.floor(n * Math.random()); }
var flakes = [];       var maxflakes = 20;
var snowspeed = 500;
var snowingTimer = setInterval(createSnowflake, snowspeed);


И собственно само создание снежинок (в нужный момент создание новых снежинок останавливается отчисткой таймера):

function createSnowflake() {
  var order = 3;
  var size = 10 + rand(50);
  var x = rand(document.body.offsetWidth);
  var y = window.pageYOffset;

  flakes.push({ x: x, y: y, vx: 0, vy: 3 + rand(3), size: size, order: order, stroke: "#99f", fill: "transparent" });
  if (flakes.length > maxflakes) clearInterval(snowingTimer);
}


Перемещение снежинок


Тут появляется дополнительная переменная invalidateMeasure, которая устанавливается в true при изменении размера экрана. Ставим таймер на обновление положения и собственно функция перемещения (очищаем экран, обновляем положение –> рисуем снежинки).

var scrollspeed = 64;
setInterval(moveSnowflakes, scrollspeed);

function moveSnowflakes() {
  sky.clearRect(0, 0, canvas.width, canvas.height);

  var maxy = canvas.height;

  for (var i = 0; i < flakes.length; i++) {
   var flake = flakes[i];
   flake.y += flake.vy;
   flake.x += flake.vx;

   if (flake.y > maxy) flake.y = 0;
   if (invalidateMeasure) {
     flake.x = rand(canvas.width);
   }

   drawFlake(flake.x, flake.y, flake.size, flake.order, flake.stroke, flake.fill);

   // Иногда меняем боковой ветер
   if (rand(4) == 1) flake.vx += (rand(11) - 5) / 10;
   if (flake.vx > 2) flake.vx = 2;
   if (flake.vx < -2) flake.vx = -2;
  }
  if (invalidateMeasure) invalidateMeasure = false;
}


Финальный код



Дальше можно добавить еще несколько дополнительных деталей: случайный поворот снежинки и случайный цвет снежинки + детализация в зависимости от размера:

(function () {
  if (document.createElement('canvas').getContext) {
    if (document.readyState === 'complete')
      snow();
    else
      window.addEventListener('DOMContentLoaded', snow, false);
  }
  else {
  return;
  }

  var deg = Math.PI / 180;
  var maxflakes = 20;     var flakes = [];         var scrollspeed = 64;    var snowspeed = 500;  
  var canvas, sky;
  var snowingTimer;
  var invalidateMeasure = false;

     var strokes = ["#6cf", "#9cf", "#99f", "#ccf", "#66f", "#3cf"];

  function rand (n) {
    return Math.floor(n * Math.random());
  }

  // Запуск снегопада
  function snow() {
    canvas = document.createElement('canvas');
    canvas.style.position = 'fixed';
    canvas.style.top = '0px';
    canvas.style.left = '0px';
    canvas.style.zIndex = '-10';

    document.body.insertBefore(canvas, document.body.firstChild);
    sky = canvas.getContext('2d');

    ResetCanvas();

    snowingTimer = setInterval(createSnowflake, snowspeed);
    setInterval(moveSnowflakes, scrollspeed);
    window.addEventListener('resize', ResetCanvas, false);
  }

  // Сброс размеров Canvas
  function ResetCanvas() {
    invalidateMeasure = true;
    canvas.width = document.body.offsetWidth;
    canvas.height = window.innerHeight;
  }

  // Отрисовка кривой Коха
  function leg(n, len) {
    sky.save();       // Сохраняем текущую трансформацию
    if (n == 0) {      // Нерекурсивный случай - отрисовываем линию
      sky.lineTo(len, 0);      }
    else {
      sky.scale(1 / 3, 1 / 3);  // Уменьшаем масштаб в 3 раза
        leg(n - 1, len);            sky.rotate(60 * deg);
        leg(n - 1, len);              sky.rotate(-120 * deg);
        leg(n - 1, len);              sky.rotate(60 * deg);          leg(n - 1, len);          }
    sky.restore();      // Восстанавливаем трансформацию
    sky.translate(len, 0); // переходим в конец ребра
  }

  // Отрисовка снежинки Коха
  function drawFlake(x, y, angle, len, n, stroke, fill) {
    sky.save();      sky.strokeStyle = stroke;
    sky.fillStyle = fill;
    sky.beginPath();
    sky.translate(x, y);
    sky.moveTo(0, 0);      sky.rotate(angle);
    leg(n, len);
    sky.rotate(-120 * deg);
    leg(n, len);           sky.rotate(-120 * deg);
    leg(n, len);           sky.closePath();
    sky.fill();
    sky.stroke();
    sky.restore();
  }

  // Создание пула снежинок
  function createSnowflake() {
    var order = 2+rand(2);
    var size = 10*order+rand(10);
    var x = rand(document.body.offsetWidth);
    var y = window.pageYOffset;
    var stroke = strokes[rand(strokes.length)];

    flakes.push({ x: x, y: y, vx: 0, vy: 3 + rand(3), angle:0, size: size, order: order, stroke: stroke, fill: 'transparent' });

    if (flakes.length > maxflakes) clearInterval(snowingTimer);
  }

  // Перемещение снежинок
  function moveSnowflakes() {
    sky.clearRect(0, 0, canvas.width, canvas.height);

    var maxy = canvas.height;

    for (var i = 0; i < flakes.length; i++) {
     var flake = flakes[i];

     flake.y += flake.vy;
     flake.x += flake.vx;

     if (flake.y > maxy) flake.y = 0;
     if (invalidateMeasure) {
       flake.x = rand(canvas.width);
     }

     drawFlake(flake.x, flake.y, flake.angle, flake.size, flake.order, flake.stroke, flake.fill);

     // Иногда меняем боковой ветер
     if (rand(4) == 1) flake.vx += (rand(11) - 5) / 10;
     if (flake.vx > 2) flake.vx = 2;
     if (flake.vx < -2) flake.vx = -2;
     if (rand(3) == 1) flake.angle = (rand(13) - 6) / 271;
    }
    if (invalidateMeasure) invalidateMeasure = false;
  }
} ());


* This source code was highlighted with Source Code Highlighter.


Копируйте код, запускайте из консоли и получайте снегопад на сайте.
Tags:
Hubs:
Total votes 97: ↑71 and ↓26+45
Comments55

Articles

Information

Website
www.microsoft.com
Registered
Founded
Employees
Unknown
Location
США