function relativePos(event, element) {
var rect = element.getBoundingClientRect();
return {x: Math.floor(event.clientX - rect.left),
y: Math.floor(event.clientY - rect.top)};
}
Несколько инструментов рисования должны слушать событие "mousemove"
, пока кнопка мыши нажата. Функция trackDrag
регистрирует и убирает событие для данных ситуаций.
function trackDrag(onMove, onEnd) {
function end(event) {
removeEventListener("mousemove", onMove);
removeEventListener("mouseup", end);
if (onEnd)
onEnd(event);
}
addEventListener("mousemove", onMove);
addEventListener("mouseup", end);
}
У неё два аргумента. Один – функция, которая вызывается при каждом событии "mousemove"
, а другая – функция, которая вызывается при отпускании кнопки. Каждый аргумент может быть не задан.
Инструмент для рисования линий использует две вспомогательные функции для самого рисования.
tools.Line = function(event, cx, onEnd) {
cx.lineCap = "round";
var pos = relativePos(event, cx.canvas);
trackDrag(function(event) {
cx.beginPath();
cx.moveTo(pos.x, pos.y);
pos = relativePos(event, cx.canvas);
cx.lineTo(pos.x, pos.y);
cx.stroke();
}, onEnd);
};
Функция сначала устанавливает свойство контекста lineCap
в “round”
, из-за чего концы нарисованного пути становятся закруглёнными, а не квадратными, как это происходит по умолчанию. Этот трюк обеспечивает непрерывность линий, когда они нарисованы в несколько приёмов. Если рисовать линии большой ширины, вы увидите разрывы в углах линий, если будете использовать установку lineCap
по умолчанию.
Затем, по каждому событию "mousemove"
, которое случается, пока кнопка нажата, рисуется простая линия между старой и новой позициями мыши, с использованием тех значений параметров strokeStyle
и lineWidth
, которые заданы в данный момент.
Аргумент onEnd
просто передаётся дальше, в trackDrag
. При обычном вызове третий аргумент передаваться не будет, и при использовании функции он будет содержать undefined
, поэтому в конце перетаскивания ничего не произойдёт. Но он поможет нам организовать ещё один инструмент, ластик erase
, используя очень небольшое дополнение к коду.
tools.Erase = function(event, cx) {
cx.globalCompositeOperation = "destination-out";
tools.Line(event, cx, function() {
cx.globalCompositeOperation = "source-over";
});
};
Свойство globalCompositeOperation
влияет на то, как операции рисования на холсте меняют цвет пикселей. По умолчанию, значение свойства "source-over"
, что означает, что цвет, которым рисуют, накладывается поверх существующего. Если цвет непрозрачный, он просто заменит существующий, но если он частично прозрачный, они будут смешаны.
Инструмент “erase”
устанавливает globalCompositeOperation
в "destination-out"
, что имеет эффект ластика, и делает пиксели снова прозрачными.
Вот у нас уже есть два инструмента для рисования. Мы можем рисовать чёрные линии в один пиксель шириной (это задано значениями свойств холста strokeStyle
и lineWidth
по умолчанию), и стирать их. Работающий, хотя и примитивный, прототип программы.
Цвет и размер кисти
Предполагая, что пользователи захотят рисовать не только чёрным цветом и не только одним размером кисти, добавим элементы управления для этих настроек.
В главе 18 я обсуждал разные варианты полей формы. Среди них не было полей для выбора цвета. По традиции у браузеров нет встроенных полей для выбора цвета, но за последнее время в стандарт включили несколько новых типов полей форм. Один из них — <input type="color">
. Среди других — "date"
, "email"
, "url"
и "number"
. Пока ещё их поддерживают не все. Для тега <input>
тип по умолчанию – “text”
, и при использовании нового тега, который ещё не поддерживается браузером, браузеры будут обрабатывать его как текстовое поле. Значит, пользователям с браузерами, которые не поддерживают инструмент для выбора цвета, необходимо будет вписывать название цвета вместо того, чтобы выбирать его через удобный элемент управления.
controls.color = function(cx) {
var input = elt("input", {type: "color"});
input.addEventListener("change", function() {
cx.fillStyle = input.value;
cx.strokeStyle = input.value;