gotch4
gotch4

Reputation: 13299

Rendering HTML elements to <canvas>

Is there a way to have an arbitrary HTML element rendered in a canvas (and then access its buffer...).

Upvotes: 79

Views: 182489

Answers (6)

CpnCrunch
CpnCrunch

Reputation: 5071

Here is code to render arbitrary HTML into a canvas:

function render_html_to_canvas(html, ctx, x, y, width, height) {
    var xml = html_to_xml(html);
    xml = xml.replace(/\#/g, '%23');
    var data = "data:image/svg+xml;charset=utf-8,"+'<svg xmlns="http://www.w3.org/2000/svg" width="'+width+'" height="'+height+'">' +
                        '<foreignObject width="100%" height="100%">' +
                        xml+
                        '</foreignObject>' +
                        '</svg>';

    var img = new Image();
    img.onload = function () {
        ctx.drawImage(img, x, y);
    }
    img.src = data;
}

function html_to_xml(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.body);
    return html;
}

example:

const ctx = document.querySelector('canvas').getContext('2d');
const html = `
<p>this
<p>is <span style="color:red; font-weight: bold;">not</span>
<p><i>xml</i>!
<p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABWElEQVQ4jZ2Tu07DQBBFz9jjvEAQqAlQ0CHxERQ0/AItBV9Ew8dQUNBQIho6qCFE4Nhex4u85OHdWAKxzfWsx0d3HpazdGITA4kROjl0ckFrnYJmQlJrKsQZxFOIMyEqIMpADGhSZpikB1hAGsovdxABGuepC/4L0U7xRTG/riG3J8fuvdifPKnmasXp5c2TB1HNPl24gNTnpeqsgmj1eFgayoHvRDWbLBOKJbn9WLGYflCCpmM/2a4Au6/PTjdH+z9lCJQ9vyeq0w/ve2kA3vaOnI6k4Pz+0Y24yP3Gapy+Bw6qdfsCRZfWSWgclCCVXTZu5LZFXKJJ2sepW2KYNCENB3U5pw93zLoDjNK6E7rTFcgbkGYJtiLckxCiw4W1OURsxUE5BokQiQj3JIToVtKwlhsurq+YDYbMBjuU/W3KtT3xIbrpAD7E60lwQohuaMtP8ldI0uMbGfC1r1zyWPUAAAAASUVORK5CYII=">`;
render_html_to_canvas(html, ctx, 0, 0, 300, 150);


function render_html_to_canvas(html, ctx, x, y, width, height) {
  var data = "data:image/svg+xml;charset=utf-8," + '<svg xmlns="http://www.w3.org/2000/svg" width="' + width + '" height="' + height + '">' +
    '<foreignObject width="100%" height="100%">' +
    html_to_xml(html) +
    '</foreignObject>' +
    '</svg>';

  var img = new Image();
  img.onload = function() {
    ctx.drawImage(img, x, y);
  }
  img.src = data;
}

function html_to_xml(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.body);
  return html;
}
<canvas></canvas>

Upvotes: 33

Suresh Mahawar
Suresh Mahawar

Reputation: 1598

Take a look at MDN's article on Drawing DOM objects into a canvas:

Given HTML loaded into a JS string value, it can render it to a <canvas> or <img> element. This is done with the help of SVG's <foreignObject> element.

Here are working examples in Javascript of rendering a HTML string to either a <canvas> element, or to an <img /> element:

Render HTML to <canvas> Element:

const renderThisHtml = `<em>I</em> like <span style="color:white; text-shadow:0 0 2px blue;">cheese</span> 🧀`;

const butMakeItLargerForStackOverflowDemoPurposes = `<span style="font-size: 30px">${renderThisHtml}</span>`;

renderHtmlToCanvas( document.getElementById( 'myCanvas' ), butMakeItLargerForStackOverflowDemoPurposes );

function renderHtmlToCanvas( canvas, html ) {

    const ctx = canvas.getContext( '2d' );

    const svg = `
<svg xmlns="http://www.w3.org/2000/svg" width="${canvas.width}" height="${canvas.height}">
<foreignObject width="100%" height="100%">
    <div xmlns="http://www.w3.org/1999/xhtml">${html}</div>
</foreignObject>
</svg>`;

    const svgBlob = new Blob( [svg], { type: 'image/svg+xml;charset=utf-8' } );
    const svgObjectUrl = URL.createObjectURL( svgBlob );

    const tempImg = new Image();
    tempImg.addEventListener( 'load', function() {
        ctx.drawImage( tempImg, 0, 0 );
        URL.revokeObjectURL( svgObjectUrl );
    } );

    tempImg.src = svgObjectUrl;
}
<canvas id="myCanvas" style="border:2px solid black;" width="200" height="200"></canvas>


Render HTML to <img /> Image Element:

You can also render the HTML to an <img> element in your document with a few tweaks:

const renderThisHtml = `<em>I</em> like <span style="color:white; text-shadow:0 0 2px blue;">cheese</span> 🧀`;

const butMakeItLargerForStackOverflowDemoPurposes = `<span style="font-size: 30px">${renderThisHtml}</span>`;

renderHtmlToImg( document.getElementById( 'myImg' ), butMakeItLargerForStackOverflowDemoPurposes );

function renderHtmlToImg( img, html ) {

    const svg = `
<svg xmlns="http://www.w3.org/2000/svg" width="${img.width}" height="${img.height}">
<foreignObject width="100%" height="100%">
    <div xmlns="http://www.w3.org/1999/xhtml">${html}</div>
</foreignObject>
</svg>`;

    const svgBlob = new Blob( [svg], { type: 'image/svg+xml;charset=utf-8' } );
    const svgObjectUrl = URL.createObjectURL( svgBlob );

    const oldSrc = img.src;
    if( oldSrc && oldSrc.startsWith( 'blob:' ) ) { // See https://stackoverflow.com/a/75848053/159145
        URL.revokeObjectURL( oldSrc );
    }

    img.src = svgObjectUrl;
}
<img id="myImg" style="border:2px solid black;" width="200" height="200" />

Upvotes: 55

Mohamed El Prince
Mohamed El Prince

Reputation: 41

RasterizeHTML is a very good project, but if you need to access the canvas it wont work on chrome. due to the use of <foreignObject>.

If you need to access the canvas then you can use html2canvas

I am trying to find another project as html2canvas is very slow in performance

Upvotes: 4

user10898116
user10898116

Reputation:

The CSS element() function may eventually help some people here, even though it's not a direct answer to the question. It allows you to use an element (and all children, including videos, cross-domain iframes, etc.) as a background image (and anywhere else that you'd normally use url(...) in your CSS code). Here's a blog post that shows what you can do with it.

It has been implemented in Firefox since 2011, and is being considered in Chromium/Chrome (don't forget to give the issue a star if you care about this functionality).

Upvotes: 5

Mikko Ohtamaa
Mikko Ohtamaa

Reputation: 83676

You won't get real HTML rendering to <canvas> per se currently, because canvas context does not have functions to render HTML elements.

There are some emulations:

html2canvas project http://html2canvas.hertzen.com/index.html (basically a HTML renderer attempt built on Javascript + canvas)

HTML to SVG to <canvas> might be possible depending on your use case:

https://github.com/miohtama/Krusovice/blob/master/src/tools/html2svg2canvas.js

Also if you are using Firefox you can hack some extended permissions and then render a DOM window to <canvas>

https://developer.mozilla.org/en-US/docs/HTML/Canvas/Drawing_Graphics_with_Canvas?redirectlocale=en-US&redirectslug=Drawing_Graphics_with_Canvas#Rendering_Web_Content_Into_A_Canvas

Upvotes: 49

Richard Otvos
Richard Otvos

Reputation: 196

According to the HTML specification you can't access the elements of the Canvas. You can get its context, and draw in it manipulate it, but that is all.

BUT, you can put both the Canvas and the html element in the same div with a aposition: relative and then set the canvas and the other element to position: absolute. This ways they will be on the top of each other. Then you can use the left and right CSS properties to position the html element.

If the element doesn't shows up, maybe the canvas is before it, so use the z-index CSS property to bring it before the canvas.

Upvotes: 1

Related Questions