Join MDN and developers like you at Mozilla's View Source conference, 12-14 September in Berlin, Germany. Learn more at https://viewsourceconf.org

Drawing DOM objects into a canvas

Antes de comenzar

Para entender este artículo, es recomendable estar familiarizado con JavaScript, la API de Canvas  y la API de  DOM

Incluso es mejor si también estás familiarizado con  SVG

Aunque no es trivial (por razones de seguridad), es posible dibujar contenido DOM —como  HTML—dentro de canvas. Este artículo, derivado de this blog post de Robert O'Callahan, abarca cómo puedes hacerlo de forma segura y de acuerdo a la especificación.

Una mirada rápida

No puedes simplemente dibujar HTML dentro de canvas. En lugar de eso, necesitas usar una imagen SVG  que contenga lo que deseas mostrar. Para dibujar contenido HTML, usarías un elemento <foreignObject> que contenga HTML, despues dibujar esa imagen SVG dentro de canvas.

Paso a paso

La única cuestión difícil aquí—y probablemente es una exageración—es crear el SVG para tu imagen. Lo que necesitas hacer es crear un string que contenga  el XML para el SVG y construir un  Blob con las siguientes partes:

  1. The MIME media type of the blob should be "image/svg+xml".
  2. The <svg> element.
  3. Inside that, the <foreignObject> element.
  4. The (well-formed) HTML itself, nested inside the <foreignObject>.

By using a object URL as described above, we can inline our HTML instead of having to load it from an external source. You can, of course, use an external source if you prefer, as long as the origin is the same as the originating document.

Example

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;charset=utf-8'});
var url = DOMURL.createObjectURL(svg);

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

img.src = url;

The example above will produce the following

ScreenshotLive sample

 

The data variable is set up with the content of the SVG image (which in turn includes the HTML) we want to draw into our canvas.

Then we create a new HTML <img> element by calling new Image(), append data, allocate an object URL, and draw the image into the context by calling drawImage() on load.

Security

You might wonder how this can be secure, in light of concerns about the possibility of reading sensitive data out of the canvas. The answer is this: this solution relies on the fact that the implementation of SVG images is very restrictive. SVG images aren't allowed to load any external resources, for example, even ones that appear to be from the same domain. Resources such as raster images (such as JPEG images) or <iframe>s have to be inlined as data: URIs.

In addition, you can't include script in an SVG image, so there's no risk of access to the DOM from other scripts, and DOM elements in SVG images can't receive input events, so there's no way to load privileged information into a form control (such as a full path into a file <input> element) and render it, then pull that information out by reading the pixels.

Visited-link styles aren't applied to links rendered in SVG images, so history information can't be retrieved, and native themes aren't rendered in SVG images, which makes it harder to determine the user's platform.

The resulting canvas should be origin clean, meaning you can call toBlob(function(blob){…}) to return a blob for the canvas, or toDataURL() to return a Base64-encoded data: URI.

Drawing HTML

Since SVG must be valid XML, you need to parse HTML to get the well-formed output of the HTML parser. The following code is the easiest way to parse HTML.

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

// You must manually set the xmlns if you intend to immediately serialize the HTML
// document to a string as opposed to appending it to a <foreignObject> in the DOM
doc.documentElement.setAttribute("xmlns", doc.documentElement.namespaceURI);

// Get well-formed markup
html = (new XMLSerializer).serializeToString(doc);

See also

Etiquetas y colaboradores del documento

 Colaboradores en esta página: dfromero, teoli, AaronRAM
 Última actualización por: dfromero,