Запускаем фрактальные снежинки на 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 .