Предновогоднее развлечение на HTML5 Canvas по украшению сайта снежинками (ну и просто интресный пример посмотреть, как работает Canvas).
В своем рассказе я буду отталкиваться от кода Giorgio Sardo, который в свою очередь базируется на коде David Flanagan.
Все, что описано ниже, вы можете попробовать непосредственно здесь, на Хабре в любом современном браузере со средствами разработки, просто запустив консоль JavaScript. В IE9 достаточно нажать F12 и, если вы хотите тестировать прямо на этой странице, не забудьте перевести браузер в режим Internet Explorer 9 Standards (Alt + 9), т.к. по умолчанию Хабр требует режима IE8.
Прежде всего, надо начать с того, что нужно убедиться, что браузер поддерживает Canvas, для этого нужно создать элемент Canvas и попробовать добраться до контекста работы:
В первом случае можно двигаться дальше и запускать снежинки.
Для отрисовки снежинок мы создатим холст (Canvas) на весь экран:
В данном случае мы создаем новый элемент Canvas и задаем ему фиксированное расположение, старясь разместить его так, чтобы он не мещал остальным элементам.
Далее мы получаем контекст для отрисовки:
Думаю, хабраюзерам, должно быть хорошо известно, что такое Снежинка Коха, поэтому ограничусь картинкой:
Штука эта фрактальная и удобно рисуется рекурсивно. Чтобы отрисовать треугольник, нужно к каждому из его ребер применить последовательно один и тот же паттерн отрисовки:
Давайте начнем с того, что попробуем отрисовать одну линию. При отрисовке, что удобно, мы можем применять трансформации (масштабирование и повороты), при которых каждая локальная отрисовка будет выглядеть как отрисовка прямой горизонтальной линии. То есть мы масштабируем и поворачиваем контекст (меняем матрицу трансформации) вместо поворота отрисовки линии.
Для сохранения и восстановления состояния матрицы трансформации используются соответственно функции save() и restore().
По ходу работы нам понадобится конвертировать градусы в радианы (хотя при желании можно и сразу в радианах писать):
Рекурсивная функция для отрисовки одного ребра выглядит так:
Для запуска отрисовки можно использовать такую функцию:
Обратите внимание, что для отрисовки нужно запустить создание пути, если нужно, закрыть его и только потом сказать, что нужно сделать закраску областей и прорисовку линий. Результат:
Если добавить еще несколько ребер с соответствующими поворотами, получим снежинку:
Результат:
Дальше идея довольно прозрачная: 1) создаем пул снежинок, повесив добавление снежинок на таймер, 2) по таймеру меняем положение снежинок и делаем отрисовку.
Дополнительная функция для случайных значений, массив снежинок и максимальное количество. Задаем таймер:
И собственно само создание снежинок (в нужный момент создание новых снежинок останавливается отчисткой таймера):
Тут появляется дополнительная переменная invalidateMeasure, которая устанавливается в true при изменении размера экрана. Ставим таймер на обновление положения и собственно функция перемещения (очищаем экран, обновляем положение –> рисуем снежинки).
Дальше можно добавить еще несколько дополнительных деталей: случайный поворот снежинки и случайный цвет снежинки + детализация в зависимости от размера:
Копируйте код, запускайте из консоли и получайте снегопад на сайте.
В своем рассказе я буду отталкиваться от кода 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.
Копируйте код, запускайте из консоли и получайте снегопад на сайте.