Выбрать главу

Строим DOM

Интерфейс программы состоит из более чем 30 элементов DOM. Нужно их как-то собрать вместе.

Очевидным форматом для сложных структур DOM является HTML. Но разделять программу на HTML и скрипт неудобно – для элементов DOM понадобится множество обработчиков событий или других необходимых вещей, которые надо будет как-то обрабатывать из скрипта. Для этого придётся делать много вызовов querySelector и им подобных, чтобы найти нужный элемент DOM для работы.

Было бы удобно определять части DOM рядом с теми частями кода JavaScript, которые ими управляют. Поэтому я решил создавать всю конструкцию DOM прямо в JavaScript. Как мы видели в главе 13, встроенный интерфейс для создания структур DOM ужасно многословен. Поскольку нам придётся создать много конструкций, нам понадобится вспомогательная функция.

Эта функция – расширенная версия функции elt из главы 13. Она создаёт элемент с заданным именем и атрибутами, и добавляет все остальные аргументы, которые получает, в качестве дочерних узлов, автоматически преобразовывая строки в текстовые узлы.

function elt(name, attributes) {

  var node = document.createElement(name);

  if (attributes) {

    for (var attr in attributes)

      if (attributes.hasOwnProperty(attr))

        node.setAttribute(attr, attributes[attr]);

  }

  for (var i = 2; i < arguments.length; i++) {

    var child = arguments[i];

    if (typeof child == "string")

      child = document.createTextNode(child);

    node.appendChild(child);

  }

  return node;

}

Так мы легко и просто создаём элементы, не раздувая код до размеров лицензионного соглашения.

Основание

Ядро нашей программы – функция createPaint, добавляющая интерфейс рисования к элементу DOM, который передаётся в качестве аргумента. Так как мы создаём программу последовательно, мы определяем объект controls, который будет содержать функции для инициализации разных элементов управления под картинкой.

var controls = Object.create(null);

function createPaint(parent) {

  var canvas = elt("canvas", {width: 500, height: 300});

  var cx = canvas.getContext("2d");

  var toolbar = elt("div", {class: "toolbar"});

  for (var name in controls)

    toolbar.appendChild(controls[name](cx));

  var panel = elt("div", {class: "picturepanel"}, canvas);

  parent.appendChild(elt("div", null, panel, toolbar));

}

У каждого элемента управления есть доступ к контексту рисования на холсте, а через него – к элементу <canvas>. Основное состояние программы хранится в этом холсте – он содержит текущую картинку, выбранный цвет (в свойстве fillStyle) и размер кисти (в свойстве lineWidth).

Мы обернём холст и элементы управления в элементы <div> с классами, чтобы можно было добавить им стили, например серую рамку вокруг картинки.

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

Первый элемент управления, который мы добавим – элемент <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, "Tooclass="underline" ", select);

};

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

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