KnigaRead.com/
KnigaRead.com » Компьютеры и Интернет » Программирование » Марейн Хавербеке - Выразительный JavaScript

Марейн Хавербеке - Выразительный JavaScript

На нашем сайте KnigaRead.com Вы можете абсолютно бесплатно читать книгу онлайн Марейн Хавербеке, "Выразительный jаvascript" бесплатно, без регистрации.
Перейти на страницу:

Аргумент 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;

  });

  return elt("span", null, "Color: ", input);

};

При смене значения поля color значения свойств контекста холста fillStyle и strokeStyle заменяются на новое значение.

Настройка размера кисти работает сходным образом.

controls.brushSize = function(cx) {

  var select = elt("select");

  var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100];

  sizes.forEach(function(size) {

    select.appendChild(elt("option", {value: size},

                           size + " pixels"));

  });

  select.addEventListener("change", function() {

    cx.lineWidth = select.value;

  });

  return elt("span", null, "Brush size: ", select);

};

Код создаёт варианты размеров кистей из массива, и убеждается в том, что свойство холста lineWidth обновлено при выборе кисти.

Сохранение

Чтобы объяснить, как работает ссылка на сохранение, сначала мне нужно рассказать про URL с данными. В отличие от обычных http: и https:, URL с данными не указывают на ресурс, а содержат весь ресурс в себе. Это URL с данными, содержащий простой HTML документ:

data:text/html,<h1 style="color:red">Hello!</h1>

Такие URL полезны для разных вещей, как, например, включение небольших картинок прямо в файл стилей. Они также позволяют нам ссылаться на создаваемые файлы на стороне клиента, в браузере, не перемещая их сперва на какой-либо сервер.

У элемента холста есть удобный метод toDataURL, который возвращает URL с данными, содержащий картинку на холсте в виде графического файла. Но нам не следует обновлять ссылку для сохранения при каждом изменении картинки. В случае больших картинок перемещение данных в URL занимает много времени. Вместо этого мы подключаем обновление к ссылке, чтоб она обновляла свой атрибут href каждый раз, когда она получает фокус с клавиатуры или над ней появляется курсор мыши.

controls.save = function(cx) {

  var link = elt("a", {href: "/"}, "Save");

  function update() {

    try {

      link.href = cx.canvas.toDataURL();

    } catch (e) {

      if (e instanceof SecurityError)

        link.href = "javascript:alert("

          JSON.stringify("Can't save: " + e.toString()) + ")";

      else

        throw e;

    }

  }

  link.addEventListener("mouseover", update);

  link.addEventListener("focus", update);

  return link;

};

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

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

Но здесь мы опять сталкиваемся с деталями реализации песочницы в браузере. Когда картинка грузится с URL с другого домена, если ответ сервера не содержит заголовок, разрешающий использование ресурса с других доменов (см. главу 17), холст будет содержать информацию, которая будет видна пользователю, но не видна скрипту.

Мы могли запросить картинку с приватной информацией (график изменений банковского счёта). Если бы скрипт мог получить к ней доступ, он мог бы шпионить за пользователем.

Для предотвращения таких утечек информации, когда картинка, невидимая скрипту, будет загружена на холст, браузеры пометят его как «испорчен». Пиксельные данные, включая URL с данными, нельзя будет получить с «испорченного» холста. На него можно писать, но с него нельзя читать.

Поэтому нам нужна обработка try/catch в функции update для ссылки сохранения. Когда холст «портится», вызов toDataURL выбросит исключение, являющееся экземпляром SecurityError. В этом случае мы перенаправляем ссылку на ещё один вид URL с протоколом javascript:. Такие ссылки просто выполняют скрипт, стоящий после двоеточия, и наша ссылка покажет предупреждение, сообщающее о проблеме.

Загрузка картинок

Последние два элемента управления используются для загрузки картинок с локального диска и с URL. Нам потребуется вспомогательная функция, которая пробует загрузить картинку с URL и заменить ею содержимое холста.

function loadImageURL(cx, url) {

  var image = document.createElement("img");

  image.addEventListener("load", function() {

    var color = cx.fillStyle, size = cx.lineWidth;

    cx.canvas.width = image.width;

    cx.canvas.height = image.height;

    cx.drawImage(image, 0, 0);

    cx.fillStyle = color;

    cx.strokeStyle = color;

    cx.lineWidth = size;

  });

  image.src = url;

}

Нам надо поменять размер холста, чтобы он соответствовал картинке. Почему-то при смене размера холста его контекст забывает все настройки (fillStyle и lineWidth), в связи с чем функция сохраняет их и загружает обратно после обновления размера холста.

Элемент управления для загрузки локального файла использует технику FileReader из главы 18. Кроме используемого здесь метода readAsText у таких объектов есть метод под названием readAsDataURL – а это то, что нам нужно. Мы загружаем файл, который пользователь выбирает, как URL с данными, и передаём его в loadImageURL для вывода на холст.

controls.openFile = function(cx) {

  var input = elt("input", {type: "file"});

  input.addEventListener("change", function() {

    if (input.files.length == 0) return;

    var reader = new FileReader();

    reader.addEventListener("load", function() {

      loadImageURL(cx, reader.result);

    });

    reader.readAsDataURL(input.files[0]);

  });

  return elt("div", null, "Open file: ", input);

};

Загружать файл с URL ещё проще. Но с текстовым полем мы не знаем, закончил ли пользователь набирать в нём URL, поэтому мы не можем просто слушать события “change”. Вместо этого мы обернём поле в форму и среагируем, когда она будет отправлена – либо по нажатии Enter, либо по нажатии кнопки load.

controls.openURL = function(cx) {

  var input = elt("input", {type: "text"});

  var form = elt("form", null,

                 "Open URL: ", input,

                 elt("button", {type: "submit"}, "load"));

  form.addEventListener("submit", function(event) {

    event.preventDefault();

    loadImageURL(cx, form.querySelector("input").value);

  });

  return form;

};

Теперь мы определили все элементы управления, требующиеся нашей программе, но нужно добавить ещё несколько инструментов.

Перейти на страницу:
Прокомментировать
Подтвердите что вы не робот:*