Урок - Фрактальные снежинки

Главная » Курсы » Курс HTML5 Canvas » Урок - Фрактальные снежинки

Обучающий онлайн курс
HTML5 Canvas

Лицензия: Копирование запрещено.

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

Источник: http://habrahabr.ru/company/microsoft/blog/110237/
Предновогоднее развлечение на 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.

Копируйте код, запускайте из консоли и получайте снегопад на сайте.