Выбор инструмента

Первый элемент управления, который мы добавим – элемент <select>, позволяющий выбирать инструмент рисования. Как и в случае с controls, мы будем использовать объект для сбора необходимых инструментов, чтобы не надо было описывать их работу в коде по отдельности, и чтобы можно было легко добавлять новые. Этот объект связывает названия инструментов с функцией, которая вызывается при их выборе и при клике на холсте.

var tools = Object.create(null);

controls.tool = function(cx) {

  var select = elt("select");

  for (var name in tools)

    select.appendChild(elt("option", null, name));

  cx.canvas.addEventListener("mousedown", function(event) {

    if (event.which == 1) {

      tools[select.value](event, cx);

      event.preventDefault();

    }

  });

  return elt("span", null, "Tool: ", select);

};

В поле tool есть элементы <option> для всех определённых нами инструментов, а обработчик события "mousedown" на холсте берёт на себя обязанность вызывать функцию текущего инструмента, передавая ей объекты event и context. Также он вызывает preventDefault, чтобы зажатие и перетаскивание мыши не вызывало выделения участков страницы.

Самый простой инструмент – линия, который рисует линии за мышью. Чтобы рисовать линию, нам надо сопоставить координаты курсора мыши с координатами точек на холсте. Вскользь упомянутый в 13 главе метод getBoundingClientRect может нам в этом помочь. Он говорит, где показывается элемент, относительно левого верхнего угла экрана. Свойства события мыши clientX и clientY также содержат координаты относительно этого угла, поэтому мы можем вычесть верхний левый угол холста из них и получить позицию относительно этого угла.

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 по умолчанию), и стирать их. Работающий, хотя и примитивный, прототип программы.

Более 800 000 книг и аудиокниг! 📚

Получи 2 месяца Литрес Подписки в подарок и наслаждайся неограниченным чтением

ПОЛУЧИТЬ ПОДАРОК