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

  });

  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;