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

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

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

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

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

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

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

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

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

Для отрисовки снежинок мы создатим холст (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 и задаем ему фиксированное расположение, старясь разместить его так, чтобы он не мещал остальным элементам.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Тут появляется дополнительная переменная 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' );

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)];

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 .

📎📎📎📎📎📎📎📎📎📎