Рисование DOM объектов в canvas

Перевод не завершен. Пожалуйста, помогите перевести эту статью с английского.

Прежде чем начать 
Для понимания этой статьи рекомендуется знать JavaScript, Canvas API и DOM 
Ещё лучше, если Вы знакомы и с SVG

Несмотря на то, что это не очень тривиально (по причинам безопасности), существует возможность вставить DOM-объекты, такие как HTML - в canvas. В статье, взятой из поста  Роберта О'Каллахана описано, как это можно сделать надежно, безопасно и в соответствии со спицификацией. 

Обзор

Просто так взять и вставить HTML в canvas невозможно. Вместо этого вам придётся использовать SVG, содержащий контент, который вы хотите отобразить. Чтобы нарисовать HTML-контент, вначале нужно использовать <foreignObject>, содержащий HTML, а затем рисовать SVG-графику внутри canvas.

Шаг за шагом

Единственный подвох здесь — и это возможно преувеличение, — создание SVG  вашей графики. Всё, что вам нужно сделать — это создать строку, содержащую XML для SVG и построить Blob, состоящий из следующих частей:

  1. MIME-тип Blob-а  должен быть "image/svg+xml".
  2. <svg> элемент.
  3. Внутри должен быть <foreignObject> элемент.
  4. Сам (хорошо отформатированный) HTML, вложенный в <foreignObject>.

Используя объекты URL как описано выше,  мы можем встроить наш HTML вместо того, чтобы загружать его из внешнего источника. Вы можете, конечно, использовать и внешний источник, если источник совпадает с оригинальным документом.

Пример

HTML

<canvas id="canvas" style="border:2px solid black;" width="200" height="200">
</canvas>

JavaScript

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');

var data = '<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">' +
           '<foreignObject width="100%" height="100%">' +
           '<div xmlns="http://www.w3.org/1999/xhtml" style="font-size:40px">' +
             '<em>I</em> like ' + 
             '<span style="color:white; text-shadow:0 0 2px blue;">' +
             'cheese</span>' +
           '</div>' +
           '</foreignObject>' +
           '</svg>';

var DOMURL = window.URL || window.webkitURL || window;

var img = new Image();
var svg = new Blob([data], {type: 'image/svg+xml'});
var url = DOMURL.createObjectURL(svg);

img.onload = function() {
  ctx.drawImage(img, 0, 0);
  DOMURL.revokeObjectURL(url);
}

img.src = url;

Этот пример изображен ниже.

ScreenshotLive sample

Переменной data присвоено содержание SVG-картинки, которую, в свою очередь, мы хотим нарисовать внутри canvas.

Затем мы создаем новый HTML элемент <img>, вызывая new Image(), добавляем в него data, выделяем объект URL, и рисуем картинку в контексте canvas, вызывая  drawImage() на загрузку картинки.

Безопасность

Вы можете задаться вопросом, будет ли это безопасно в свете опасений относительно возможности чтения конфиденциальных данных из элемента canvas. Ответ таков: это решение полагается на тот факт, что реализация SVG-изображений очень строгая. SVG-картинки не позволяют загружать никакие внешние ресурсы, например, даже те, что пришли с того же домена. Такие реусрсы как растровые изображения (например, JPEG) или <iframe>-ы должны быть встроены как data: URI-s.

В добавок к этому, вы не можете включать скрипт в SVG-изображение, поэтому нет риска доступа к DOM из других скриптов, и DOM elements в SVG-картинках не могут принимать события ввода, не оставляя таким образом способа загрузить привилегированную информацию в элемент управления формой (такую как полный путь до файла <input> элемент) и срендерить его, а затем вытащить эту информацию, читая пиксели.

Стили посещенных страниц не применяются к ссылкам, отображаемым в изображениях SVG, поэтому данные истории не могут быть восстановлены, а родные темы не отображаются в изображениях SVG, что затрудняет определение платформы пользователя.

Полученный canvas должен быть чистым; это означает, что вы не можете вызвать toBlob(function(blob){…}), чтобы вернуть blob для canvas, или toDataURL(), чтобы вернуть Base64-encoded data: URI.

Рисование в HTML

Поскольку SVG должен быть валидным XML, вам потребуется парсить HTML, чтобы получить хорошо отформатированный результат от HTML-парсера. Ниже представлен код, демонстрирующий самый простой путь парсинга html.

var doc = document.implementation.createHTMLDocument('');
doc.write(html);

// Вы должны вручную установить xmlns, намереваясь немедленно сериализовать 
// HTML-документ в строку, а не добавлять его к 
// <foreignObject> в DOM
doc.documentElement.setAttribute('xmlns', doc.documentElement.namespaceURI);

// Получить хорошо сформированную разметку
html = (new XMLSerializer).serializeToString(doc);

Смотрите также

  • Canvas
  • Canvas tutorial
  • Drawing DOM content to canvas (blog post)
  • rasterizeHTML.js реализация после этого поста
  • Drawing math equations into a canvas, с помощью TeXZilla. 

    Обработчик событий наблюдателя не замечает изменений в значении «x», поскольку значение dom поддерживается в браузере только в том случае, когда браузеру требуется эта характеристика.
    Решение состоит в том, чтобы вместо этого использовать событие setInterval, вызываемое каждые несколько миллисекунд. Также необходимо заранее обновить функцию draw.

Метки документа и участники

Внесли вклад в эту страницу: warsan, bad4iz, komuroe
Обновлялась последний раз: warsan,