Продвинутое рисование и события

Заливка изображением

В главе 1 мы узнали, что Canvas может заливать фигуры цветом и градиентом. Вы также можете залить фигуру изображениями, заданными как узор. Повторением узора можно управлять подобно фоновым изображениям в CSS.

Как и градиенты, узоры рисуется относительно текущей системы координат. Вот почему я сделал сдвиг на 200 пикселей вправо перед рисованием второго прямоугольника. Попробуйте перетащить значения, чтобы увидеть, как это работает.

var pat1 = ctx.createPattern(img,'repeat');    
ctx.fillStyle = pat1;
ctx.fillRect(0,0,100,100);    

var pat2 = ctx.createPattern(img,'repeat-y');    
ctx.fillStyle = pat2;
ctx.translate(200,0);
ctx.fillRect(0,0,100,100);

Обратите внимание, что заливка изображением работает, только если изображение уже загружено, так что не забудьте делать рисование из функции обратного вызова onload.

Прозрачность

Canvas API позволяет управлять прозрачностью через свойство globalAlpha. В следующей демонстрации рисуется два красных перекрывающихся квадрата, через которые видны изменения globalAlpha перед каждой операцией рисования.

ctx.fillStyle = 'red';
// делим на 100 чтобы получить дробь между 0 и 1
ctx.globalAlpha = 50/100; 
ctx.fillRect(0,0,50,50);
ctx.globalAlpha = 30/100;
ctx.fillRect(25,25,50,50);
ctx.globalAlpha = 1.0;

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

Трансформация

В главе о диаграммах мы рисовали один и тот же прямоугольник снова и снова только с разными координатами х и у. Вместо модификации этих координат мы могли бы использовать функцию translate. Каждую итерацию цикла мы можем вызывать translate со 100 пикселями для перемещения следующего столбца вправо.

ctx.fillStyle = "red";
for(var i=0; i<data.length; i++) {
    var dp = data[i];
    ctx.translate(100, 0);
    ctx.fillRect(0,0,50,dp);
}

Попробуйте передвигать переменную х, чтобы увидеть, как эффект проявляет себя на графике.

Как и большинство двумерных API, Canvas поддерживает стандартные функции трансформации translate, rotate и scale. Это позволяет рисовать фигуры и трансформировать их на экране без ручного вычисления новых точек. Canvas решает математику за вас. Вы также можете комбинировать трансформации, вызывая их по очереди. Например, чтобы нарисовать прямоугольник, сдвинуть его в центр, а затем повернуть на 30 градусов вы можете сделать следующее:

ctx.fillStyle = "red";
ctx.translate(50,50);
// преобразуем углы в радианы
var rads = 30 * Math.PI*2.0/360.0;
ctx.rotate(rads)
ctx.fillRect(0,0,100,100);

Каждый раз, когда вы вызываете translate, rotate или scale она добавляется к предыдущей трансформации. Со временем это может привести к путанице, конечно. Вы можете отменить трансформацию так:

for(var i=0; i<data.length; i++) {
  c.translate(40+i*100, 460-dp*4);
  var dp = data[i];
  c.fillRect(0,0,50,dp*4);
  c.translate(-40-i*100, -450+dp*4);
}

Но приходится писать много надоедливого кода. Если вы однажды забыли отменить его, то окажетесь взвинченным и потратите часы на просмотр кода ради этой одной ошибки. Вместо этого Canvas предлагает API для сохранения состояний.

Сохранение состояния

Объект context2D представляет текущее состояние рисунка. В этой книге я всегда использую переменную ctx для хранения этого контекста. Состояние включает в себя текущую трансформацию, цвета заливки и обводки, текущий шрифт и некоторые другие переменные. Вы можете сохранить это состояние переместив его в стек с помощью функции save(). После сохранения состояния можно вносить изменения, а затем восстановить в прежнее состояние функцией restore(). Canvas заботится о бухгалтерии за вас. Вот предыдущий пример переписанный с сохранением состояния. Обратите внимание, что нам не надо делать шаг для отмены translate.

for(var i=0; i<data.length; i++) {
  c.save();
  c.translate(40+i*100, 460-dp*4);
  var dp = data[i];
  c.fillRect(0,0,50,dp*4);
  c.restore();
}

Маскирование

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

// рисуем прямоугольник
ctx.fillStyle = 'red';
ctx.fillRect(0,0,400,100);

// создаём треугольный контур
ctx.beginPath();
ctx.moveTo(200,50);
ctx.lineTo(250,150);
ctx.lineTo(150,150);
ctx.closePath();

// обводим треугольник, чтобы его увидеть
ctx.lineWidth = 10;
ctx.stroke();

// используем треугольник как маску
ctx.clip();
// заливаем прямоугольник жёлтым цветом
ctx.fillStyle = 'yellow';
ctx.fillRect(0,0,400,100);

Обратите внимание, что пересечение красного прямоугольника и треугольника заливается жёлтым цветом. Заметьте также, что нижняя часть треугольника имеет толстую границу, а верхняя часть имеет более тонкую границу. Это происходит потому, что граница центрируется по реальным геометрическим краям треугольной фигуры. Жёлтый накрывает внутреннюю границу, когда она обрезается треугольником, но за пределами граница остаётся не накрытой.

События

Canvas не определяет никаких новых событий. Вы можете отслеживать те же события мыши и касания, с которыми вы работали где-то ещё. Это и хорошо и плохо.

Canvas только выглядит прямоугольной областью пикселей в браузере. Браузер не знает ни о каких-либо нарисованных вами фигур. Если вы перетащите курсор мыши за пределы холста, то браузер отправит вам стандартные события перетаскивания для холста целиком, а не к чему-то конкретному внутри холста. Это означает, что если вы хотите делать особые штуки, вроде создания кнопок или инструментов рисования, то вам самостоятельно придётся обрабатывать события путём преобразования исходных событий мыши браузера в собственную модель данных.

Вычисление, какая фигура сейчас находится под курсором мыши, может быть довольно трудным. К счастью у Canvas есть API для помощи в этом: isPointInPath. Эта функция сообщит вам, если заданная координата располагается внутри текущего контура. Вот простой пример:

c.beginPath();
c.arc(
  100,100, 40,  // окружность радиусом 40 пикселей с центром в 100,100
  0,Math.PI*2,  // от 0 до 360 градусов для заливки окружности
);
c.closePath();
var a = c.isPointInPath(80,0);     // возвращает true
var b = c.isPointInPath(200,100);  // возвращает false

Другой вариант заключается в использовании библиотеки, такой как Amino, которая позволяет работать в терминах фигур вместо пикселей. Она будет обрабатывать события и перерисовывать за вас.

Автор и редакторы

Автор: Джош Мариначи
Последнее изменение: 11.03.2020
Редакторы: Влад Мержевич