Please note, this is a STATIC archive of website developer.mozilla.org from 03 Nov 2016, cach3.com does not collect or store any user information, there is no "phishing" involved.

Revision 1038538 of Drawing DOM objects into a canvas

  • Revision slug: Web/API/Canvas_API/Drawing_DOM_objects_into_a_canvas
  • Revision title: Drawing DOM objects into a canvas
  • Revision id: 1038538
  • Created:
  • Creator: fred.wang
  • Is current revision? No
  • Comment

Revision Content

{{CanvasSidebar}}

Before you start

To understand this article, it is recommended to be comfortable with {{Glossary("JavaScript")}}, the {{Glossary("Canvas")}} {{Glossary("API")}} and the {{Glossary("DOM")}} {{Glossary("API")}}

It's even better if you are also familiar with {{Glossary("SVG")}}. 

Although it's not trivial (for security reasons), it's possible to draw {{Glossary("DOM")}} content—such as {{Glossary("HTML")}}—into a {{Glossary("canvas")}}. This article, derived from this blog post by Robert O'Callahan, covers how you can do it securely, safely, and in accordance with the specification.

An overview

You can't just draw HTML into a canvas. Instead, you need to use an {{Glossary("SVG")}} image containing the content you want to render. To draw HTML content, you'd use a {{SVGElement("foreignObject")}} element containing the HTML, then draw that SVG image into your canvas.

Step-by-step

The only really tricky thing here—and that's probably an overstatement—is creating the SVG for your image. All you need to do is create a string containing the {{Glossary("XML")}} for the SVG and construct a {{domxref("Blob")}} with the following parts.

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

By using an object {{Glossary("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="https://www.w3.org/2000/svg" width="200" height="200">' +
           '<foreignObject width="100%" height="100%">' +
           '<div xmlns="https://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

{{EmbedLiveSample("Example","210","210","https://mdn.mozillademos.org/files/3616/example.png")}}

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

Then we create a new HTML {{HTMLElement("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 {{HTMLElement("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 {{Glossary("DOM")}} from other scripts, and DOM {{Glossary("Element","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 {{HTMLElement("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 {{Glossary("SVG")}} must be valid {{Glossary("XML")}}, you need to parse {{Glossary("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

Revision Source

<div>{{CanvasSidebar}}</div>

<div class="note">
<p><strong>Before you start</strong></p>

<p>To understand this article, it is recommended to be comfortable with {{Glossary("JavaScript")}}, the {{Glossary("Canvas")}} {{Glossary("API")}} and the&nbsp;{{Glossary("DOM")}} {{Glossary("API")}}</p>

<p>It's even better if you are also familiar with&nbsp;{{Glossary("SVG")}}.&nbsp;</p>
</div>

<p>Although it's not trivial (for security reasons), it's possible to draw {{Glossary("DOM")}} content—such as {{Glossary("HTML")}}—into a {{Glossary("canvas")}}. This article, derived from <a href="https://robert.ocallahan.org/2011/11/drawing-dom-content-to-canvas.html" title="https://robert.ocallahan.org/2011/11/drawing-dom-content-to-canvas.html">this blog post</a> by Robert O'Callahan, covers how you can do it securely, safely, and in accordance with the specification.</p>

<h2 id="An_overview">An overview</h2>

<p>You can't just draw HTML into a canvas. Instead, you need to use an {{Glossary("SVG")}} image containing the content you want to render. To draw HTML content, you'd use a {{SVGElement("foreignObject")}} element containing the HTML, then draw that SVG image into your canvas.</p>

<h2 id="Step-by-step">Step-by-step</h2>

<p>The only really tricky thing here—and that's probably an overstatement—is creating the SVG for your image. All you need to do is create a string containing the {{Glossary("XML")}} for the SVG and construct a {{domxref("Blob")}} with the following parts.</p>

<ol>
 <li>The MIME media type of the blob should be "image/svg+xml".</li>
 <li>The {{SVGElement("svg")}} element.</li>
 <li>Inside that, the {{SVGElement("foreignObject")}} element.</li>
 <li>The (well-formed) HTML itself, nested inside the {{SVGElement("foreignObject")}}.</li>
</ol>

<p>By using an object {{Glossary("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.</p>

<h2 id="Example">Example</h2>

<h3 id="HTML">HTML</h3>

<pre class="brush:html">
&lt;canvas id="canvas" style="border:2px solid black;" width="200" height="200"&gt;
&lt;/canvas&gt;
</pre>

<h3 id="JavaScript">JavaScript</h3>

<pre class="brush: js">
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');

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

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;</pre>

<p>The example above will produce the following</p>

<p>{{EmbedLiveSample("Example","210","210","https://mdn.mozillademos.org/files/3616/example.png")}}</p>

<p>The <code>data</code> variable is set up with the content of the {{Glossary("SVG")}} image (which in turn includes the {{Glossary("HTML")}}) we want to draw into our {{Glossary("canvas")}}.</p>

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

<h2 id="Security">Security</h2>

<p>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 <a href="/en-US/docs/SVG/SVG_as_an_Image#Restrictions" title="SVG/SVG_as_an_Image#Restrictions">very restrictive</a>. 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 {{HTMLElement("iframe")}}s have to be inlined as <code>data:</code> URIs.</p>

<p>In addition, you can't include script in an SVG image, so there's no risk of access to the {{Glossary("DOM")}} from other scripts, and DOM {{Glossary("Element","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 {{HTMLElement("input")}} element) and render it, then pull that information out by reading the pixels.</p>

<p>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.</p>

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

<h2 id="Drawing_HTML">Drawing HTML</h2>

<p>Since {{Glossary("SVG")}} must be valid {{Glossary("XML")}}, you need to parse {{Glossary("HTML")}} to get the well-formed output of the HTML parser. The following code is the easiest way to parse HTML.</p>

<pre class="brush:js">
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 
// &lt;foreignObject&gt; in the DOM
doc.documentElement.setAttribute("xmlns", doc.documentElement.namespaceURI);

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

<h2 id="See_also">See also</h2>

<ul>
 <li><a href="/en-US/docs/Web/API/Canvas_API" title="HTML/Canvas">Canvas</a></li>
 <li><a href="/en-US/docs/Web/API/Canvas_API/Tutorial" title="Canvas tutorial">Canvas tutorial</a></li>
 <li><a href="https://robert.ocallahan.org/2011/11/drawing-dom-content-to-canvas.html" title="https://robert.ocallahan.org/2011/11/drawing-dom-content-to-canvas.html">Drawing DOM content to canvas</a> (blog post)</li>
 <li><a href="https://cburgmer.github.com/rasterizeHTML.js/" title="https://cburgmer.github.com/rasterizeHTML.js/">rasterizeHTML.js</a>, an implementation following this post</li>
 <li><a href="https://fred-wang.github.io/TeXZilla/examples/toImageWebGL.html">Drawing math equations into a canvas</a>, using TeXZilla.</li>
</ul>
Revert to this revision