62

It would be incredibly useful to be able to temporarily convert a regular element into a canvas. For example, say I have a styled div that I want to flip. I want to dynamically create a canvas, "render" the HTMLElement into the canvas, hide the original element and animate the canvas.

Can it be done?

Hash
  • 4,647
  • 5
  • 21
  • 39
davemyron
  • 2,483
  • 3
  • 24
  • 33

10 Answers10

56

There is a library that try to do what you say.

See this examples and get the code

http://hertzen.com/experiments/jsfeedback/

http://html2canvas.hertzen.com/

Reads the DOM, from the html and render it to a canvas, fail on some, but in general works.

Aristos
  • 66,005
  • 16
  • 114
  • 150
23

Take a look at this tutorial on MDN: https://developer.mozilla.org/en/HTML/Canvas/Drawing_DOM_objects_into_a_canvas (archived)

Its key trick was:

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;

That is, it used a temporary SVG image to include the HTML content as a "foreign element", then renders said SVG image into a canvas element. There are significant restrictions on what you can include in an SVG image in this way, however. (See the "Security" section for details — basically it's a lot more limited than an iframe or AJAX due to privacy and cross-domain concerns.)

natevw
  • 16,807
  • 8
  • 66
  • 90
  • THIS should be the accepted answer. Helped me wrote my API to DOM to canvas (and then to an image) from raw! – Panini Luncher Apr 26 '20 at 21:12
  • thanks for this answer. is this supported by all browsers? – Crashalot Jan 15 '21 at 06:15
  • the alternate link no longer works – darryn.ten Nov 26 '21 at 04:48
  • 1
    @darryn.ten Thanks, somebody added a link to a somewhat questionable mirror of the original content and now that seems to have moved too. There are similar "live" URLs but I think it's best to just reference the Archive.org version of the original content at this point and I've cleaned up my answer to that end. – natevw Nov 26 '21 at 21:49
19

Sorry, the browser won't render HTML into a canvas.

It would be a potential security risk if you could, as HTML can include content (in particular images and iframes) from third-party sites. If canvas could turn HTML content into an image and then you read the image data, you could potentially extract privileged content from other sites.

To get a canvas from HTML, you'd have to basically write your own HTML renderer from scratch using drawImage and fillText, which is a potentially huge task. There's one such attempt here but it's a bit dodgy and a long way from complete. (It even attempts to parse the HTML/CSS from scratch, which I think is crazy! It'd be easier to start from a real DOM node with styles applied, and read the styling using getComputedStyle and relative positions of parts of it using offsetTop et al.)

bobince
  • 528,062
  • 107
  • 651
  • 834
  • 4
    Well that's a bummer. It'd be great to be able to "freeze" an element and distort it in some manner and then be able to "unfreeze" it later. I disagree that it'd be a *new* security risk. It wouldn't be any different than being able to use JS to read the DOM and steal that info (and, of course, any sandbox constraints on JS would apply the same). Thanks for the confirmation of what I suspected would be the answer, bobince. – davemyron Apr 30 '10 at 21:07
  • 1
    Yeah, it's just that applying Same Origin restrictions would be considerably more tricky for general HTML content than for cross-document scripting. You'd have to keep a record of every rectangle on-screen that contained any third-party content, and deny a render request that intersected any of them — images, iframes, video/audio, elements with background images, all objects/embeds/applets whether third-party or not (since they have their own cross-site rules), SVG and so on. – bobince Apr 30 '10 at 22:13
  • 22
    I have to disagree on the security issue. If you have access to their page, you certainly have access to more information than can be gleaned from a .png of their page. If the page is available, a screenshot of the page opens no new security holes! – BentFX May 18 '11 at 18:17
  • 2
    You can include someone's page in a frame or other means of embedding without having any script access to it. I can ` – bobince May 18 '11 at 19:31
  • I'm sure those security issues can be addressed with a bit of thinking. For example, whether a page or HTML DOM can be captured could be made dependent on whether there is XSS content on that page, which is easy to see for the browser. – paniq May 19 '11 at 16:42
  • Is it easy? The browser would have to start recording where each embedded resource came from—not just those with DOM presence like iframes but also images, background images, videos, and plugins which have their own incompatible security models. (You can't just look at the src URLs as redirects might be in play.) – bobince May 19 '11 at 19:43
  • 2
    "To get a canvas from HTML, you'd have to basically write your own HTML renderer from scratch using drawImage and fillText, which is a potentially huge task." But was done neverheless, see the other answer provided by Aristos. It should be the accepted answer imo. – Daniel Baulig Aug 05 '11 at 10:59
  • While somewhat true (unless you draw a restricted set of HTML via SVG, see my answer), this answer is a huge red herring: the lack of a built-in mechanism to do this has very little to do with "security". The element has perfectly applicable tracking for whether it is "origin-clean" or not. If a canvas element is not origin clean, then you can not read its contents via JavaScript. – natevw May 07 '12 at 21:30
  • **Update**: I took back my downvote and need to take back some of my previous comment in light of a better reading of @bobince's comment above...while it's true the canvas element can track its origin clean state, it is determining whether the DOM being drawn is origin clean that may be a bit more difficult for browsers to implement. (However, most all the needed "origin" information would be available...the browser would just need to "walk" the source DOM to determine whether or not it should taint the canvas.) – natevw May 07 '12 at 21:37
  • "you could potentially extract privileged content from other sites", well that's not really the reason it's not done. in fact, its pretty easy to read content from other sites. – Octopus Dec 02 '15 at 00:09
11

You can use dom-to-image library (I'm the maintainer).
Here's how you could approach your problem:

var parent = document.getElementById('my-node-parent');
var node = document.getElementById('my-node');

var canvas = document.createElement('canvas');
canvas.width = node.scrollWidth;
canvas.height = node.scrollHeight;

domtoimage.toPng(node).then(function (pngDataUrl) {
    var img = new Image();
    img.onload = function () {
        var context = canvas.getContext('2d');

        context.translate(canvas.width, 0);
        context.scale(-1, 1);
        context.drawImage(img, 0, 0);

        parent.removeChild(node);
        parent.appendChild(canvas);
    };

    img.src = pngDataUrl;
});

And here is jsfiddle

tsayen
  • 2,875
  • 3
  • 18
  • 21
  • 1
    Thanks for this library. !! Made a small change in your jsfiddle by adding an

    ...

    in the HTML. Now, the output is getting cut. Please find the new jsfiddle [link]http://jsfiddle.net/2zLL7wvn/ Is there anything I'm doing wrong? Please help. Thanks,
    – junkle Sep 07 '16 at 13:48
  • @junkle seems to be because of the margin around the h1 tag, can fix by setting a height, removing the margin or converting margin to padding – xer21 Jul 20 '21 at 04:47
8

Building on top of the Mozdev post that natevw references I've started a small project to render HTML to canvas in Firefox, Chrome & Safari. So for example you can simply do:

rasterizeHTML.drawHTML('<span class="color: green">This is HTML</span>' 
                     + '<img src="local_img.png"/>', canvas);

Source code and a more extensive example is here.

cburgmer
  • 2,150
  • 1
  • 24
  • 18
5

No such thing, sorry.

Though the spec states:

A future version of the 2D context API may provide a way to render fragments of documents, rendered using CSS, straight to the canvas.

Which may be as close as you'll get.

A lot of people want a ctx.drawArbitraryHTML/Element kind of deal but there's nothing built in like that.

The only exception is Mozilla's exclusive drawWindow, which draws a snapshot of the contents of a DOM window into the canvas. This feature is only available for code running with Chrome ("local only") privileges. It is not allowed in normal HTML pages. So you can use it for writing FireFox extensions like this one does but that's it.

Simon Sarris
  • 62,212
  • 13
  • 141
  • 171
3

You could spare yourself the transformations, you could use CSS3 Transitions to flip <div>'s and <ol>'s and any HTML tag you want. Here are some demos with source code explain to see and learn: http://www.webdesignerwall.com/trends/47-amazing-css3-animation-demos/

Rafael Nogueira
  • 3,520
  • 1
  • 13
  • 13
  • 4
    I'm sure you mean well, but many people, like me, reach these questions through specific Google queries, and having someone tell me to do something different instead of the thing I'm asking for is not helpful. Rendering styled HTML to a canvas also means I can upload that HTML to a WebGL texture, and do stylized presentations of formatted content, with transformations and shaders. – paniq May 19 '11 at 16:39
  • Hi @paniq, just wanted you to present an alternative technique that actually works and is easy to grasp. Sorry that did bother you. – Jürgen Messing May 23 '11 at 09:43
  • 1
    Fair enough. Stack Overflow says I can take back my downvote if the original answer is being edited. So if you'll do that, I'll take it back. Sorry, was a bit grumpy. – paniq May 29 '11 at 12:01
  • Thanks for the insight, I think people wanting to turn HTML to canvas want effects that may exist already in the examples you posted. So I am glad you posted in this thread. – thedrs Jul 26 '12 at 13:20
1

the next code can be used in 2 modes, mode 1 save the html code to a image, mode 2 save the html code to a canvas.

this code work with the library: https://github.com/tsayen/dom-to-image

*the "id_div" is the id of the element html that you want to transform.

**the "canvas_out" is the id of the div that will contain the canvas so try this code. :

   function Guardardiv(id_div){

      var mode = 2 // default 1 (save to image), mode 2 = save to canvas
      console.log("Process start");
      var node = document.getElementById(id_div);
      // get the div that will contain the canvas
      var canvas_out = document.getElementById('canvas_out');
      var canvas = document.createElement('canvas');
      canvas.width = node.scrollWidth;
      canvas.height = node.scrollHeight;

      domtoimage.toPng(node).then(function (pngDataUrl) {
          var img = new Image();
          img.onload = function () {
          var context = canvas.getContext('2d');
          context.drawImage(img, 0, 0);
        };

        if (mode == 1){ // save to image
             downloadURI(pngDataUrl, "salida.png");
        }else if (mode == 2){ // save to canvas
          img.src = pngDataUrl;
          canvas_out.appendChild(img);

        }
      console.log("Process finish");
    });

    }

so, if you want to save to image just add this function:

    function downloadURI(uri, name) {
        var link = document.createElement("a");

        link.download = name;
        link.href = uri;
        document.body.appendChild(link);
        link.click();   
    }

Example of use:

 <html>
 <head>
 </script src="/dom-to-image.js"></script>
 </head> 
 <body>
 <div id="container">
   All content that want to transform
  </div>
  <button onclick="Guardardiv('container');">Convert<button> 

 <!-- if use mode 2 -->
 <div id="canvas_out"></div>
 </html>

Comment if that work. Comenten si les sirvio :)

  • I cannot get this working on IE. I tried using a polyfill for ES6 promises, but that didn't work either – J_sdev Nov 30 '20 at 19:25
0

The easiest solution to animate the DOM elements is using CSS transitions/animations but I think you already know that and you try to use canvas to do stuff CSS doesn't let you to do. What about CSS custom filters? you can transform your elements in any imaginable way if you know how to write shaders. Some other link and don't forget to check the CSS filter lab.
Note: As you can probably imagine browser support is bad.

olanod
  • 30,306
  • 7
  • 46
  • 75
-1
function convert() {
                    dom = document.getElementById('divname');
                    var script,
                    $this = this,
                    options = this.options,
                    runH2c = function(){
                        try {
                            var canvas =     window.html2canvas([ document.getElementById('divname') ], {
                                onrendered: function( canvas ) {

                                window.open(canvas.toDataURL());

                                }
                            });
                        } catch( e ) {
                            $this.h2cDone = true;
                            log("Error in html2canvas: " + e.message);
                        }
                    };

                    if ( window.html2canvas === undefined && script === undefined ) {
                    } else {.
                        // html2canvas already loaded, just run it then
                        runH2c();
                    }
                }