Живые рисунки JavaScript Canvas

live-canva-hoops

Красивые рисунки на JS Canvas — это здорово. Но гораздо лучше, когда рисунок «живой», то есть прорисовывается в режиме реального времени.

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

Общий подход к созданию «живых» рисунков на JS Canvas выглядит так:

С помощью глобальных переменных задаем основные параметры: начальные координаты, смещения, углы и прочее. Эти значения будут в процессе изменяться из рисующей функции.
Определяем функцию, которая рисует что-то по текущим параметрам, а затем просчитывает и изменяет параметры для следующего прохода.
Запускаем эту функцию нужное число раз с некоторой задержкой.
Примечание: если рисунок конечный, то вызов рисующей функции лучше организовать из нее самой с помощью setTimeout с учетом условия окончания. В противном случае можно воспользоваться setInterval.
Рассмотрим это на практике.

Линия
Про инициализацию канвы читайте в предыдущих статьях, здесь я комментировать ее не буду, ибо все просто.

var canva = document.getElementById(‘canvas’);
canva.width = window.innerWidth;
canva.height = window.innerHeight;
var ctx = canva.getContext(‘2d’);

Для начала простой пример «живой линии»:

var x = 0, y = 100, // начальные координаты
dx = 3, dy = 0; // смещение за отрисовку

function draw() {
ctx.beginPath();
ctx.moveTo(x, y); // установили начальную точку
// изменяем координаты
x += dx;
y += dy;
ctx.lineTo(x, y); // рисуем до новых координат
ctx.stroke();
if (x <= canva.width) // условие работы - пока не достигнем правого края экрана setTimeout(draw, 10); } draw(); // первый запуск

Волна
Просто линия — это скучно. Гораздо более интересным представляется композиция из нескольких элементов, которые связаны друг с другом.
Вот, например, волна: две синусоиды на разных уровнях, вторая смещена относительно первой, каждая из точек которых связана с соответствующей точкой другой синусоиды.

Как это сделать? Для начала определим вспомогательные функции, которые будут рисовать точки и линии по заданным координатам:

// отрисовка линии заданной ширины
function line(x1, y1, x2, y2, wh) {
ctx.beginPath();
ctx.lineWidth = wh;
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
}

// отрисовка точки в заданном месте заданного размера
// (точка заданного размера — прикольно, да?)
function dot(x1, y1, r) {
ctx.beginPath();
ctx.arc(x1, y1, r, 0, Math.PI * 2, true);
ctx.fill();
}

А теперь организуем композицию.

// волна с заданными параметрами
function Wave(params) {
// начальное положение
var x1 = params.start1.x, y1 = params.start1.y, dx1 = params.incX;
var x2 = params.start2.x, y2 = params.start2.y, dx2 = params.incX;

function draw() {
// точки по вычисленным ранее координатам
dot(x1, y1, 1);
dot(x2, y2, 1);
// соединяем эти точки
line(x1, y1, x2, y2, 0.1);
// вычисляем координаты следующих точек
x1 += dx1;
x2 += dx2;
y1 = Math.floor(params.ampl1 * Math.sin(x1 * params.freq1)) + 200;
y2 = Math.floor(params.ampl2 * Math.sin(x2 * params.freq2 + params.shift)) + 200;
// условие продолжения работы
if ((x1 < canva.width) || (x2 < canva.width)) setTimeout(draw, params.timeout); } draw();

Пример вызова функции с параметрами:

Wave({
start1: {x: 0, y: 50}, // координаты начала первой синусоиды
// координаты начала второй синусоиды, смещены за границы канвы
start2: {x: Math.floor(Math.PI * -100), y: 200 },
incX: 3, // расстояние между точками
ampl1: 100, // растягивание по вертикали
ampl2: 100,
shift: 3 *Math.PI / 4, // смещение второй синусоиды относительно первой
// чатсоты
freq1: 0.01,
freq2: 0.01,
// задержка
timeout: 10
});
}

Первая линия подчиняется закону A * cos(φ * f)
Вторая линия — A * coscos(φ * f + shift)
A — амплитуда, φ — угол в радианах, f — частота, shift — смещение второй синусоиды относительно первой.

«Пяльца»
Вот еще пример: два связанных кольца. Если их отрисовывать в одном направлении, то получится техмерная трубка. Если в противоположном — опутанные нитками пяльца.

// две окружности, соответствующие точки связаны попарно
function Circles(params) {

var x1 = 0, y1 = 0,
x2 = 0, y2 = 0;

var fi1 = 0, fi2 = 0;

function draw() {
// по часовой — из угла вычитаем
// против — к углу прибавляем
fi1 = (params.clockwise1) ? fi1 — params.angleInc : fi1 + params.angleInc;
fi2 = (params.clockwise2) ? fi2 — params.angleInc : fi2 + params.angleInc;
// вычисление координат окружности
x1 = Math.floor(Math.sin(fi1) * params.radius1 + params.center1.x);
y1 = Math.floor(Math.cos(fi1) * params.radius1 + params.center1.y);
x2 = Math.floor(Math.sin(fi2) * params.radius2 + params.center2.x);
y2 = Math.floor(Math.cos(fi2) * params.radius2 + params.center2.y);

dot(x1, y1, 1);
dot(x2, y2, 1);
line(x1, y1, x2, y2, 0.1);

// дорисовываем все 360 градусов (2 * PI)
if ((!params.clockwise1) && (fi1 <= 2 * Math.PI) || (params.clockwise1) && (fi1 >= -2 * Math.PI))
setTimeout(draw, params.timeout);
}

draw();
}

Нарисуем трехмерную трубу:

Circles({
center1: { x: 200, y: 200 },
center2: { x: 600, y: 400 },
radius1: 150, // радиус первого кольца
radius2: 150, // радиус второго кольца
angleInc: 0.03, // приращение угла
timeout: 10,
clockwise1: false, // против часовой стрелки
clockwise2: false // против часовой стрелки
});

Таким образом можно создавать красивые динамичные фоны для сайтов, оформление различных промо-сайтов, элементы типа индикатора загрузки. И все же для этих целей больше подойдут «бесконечные» рисунки, которые не определены какой-то математической функцией, а генерируются случайным образом. Но об этом в другой раз.

Читайте так же:
Оставить комментарий

Последние публикации