如何将HTML元素转换为画布元素?

63

能够将普通元素临时转换为canvas会非常有用。例如,假设我有一个经过样式化的div元素,我想要翻转它。我需要动态创建一个画布,将HTMLElement渲染到画布中,隐藏原始元素并动画显示画布。

这个能做到吗?

10个回答

57

4
太棒了!!考虑到所有因素,真的非常准确。 - Alex

24

看一下这个在MDN上的教程:https://developer.mozilla.org/en/HTML/Canvas/Drawing_DOM_objects_into_a_canvas (已存档)

它的关键技巧是:

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;

也就是说,它使用了一个临时的SVG图像HTML内容作为“外部元素”包含进来,然后将该SVG图像渲染到画布元素中。然而,在这种方式下,你可以在SVG图像中包含的内容受到重要的限制。(有关详细信息,请参见“安全性”部分-基本上由于隐私和跨域问题,它比iframe或AJAX更受限制。)


这应该是被接受的答案。帮助我从原始数据编写了我的API到DOM再到画布(然后转换为图像)! - Panini Luncher
感谢您的回答。所有浏览器都支持这个吗? - Crashalot
备用链接已失效。 - darryn.ten
1
@darryn.ten 谢谢,有人添加了一个有些可疑的原始内容镜像链接,现在似乎也已经移动了。虽然有类似的“实时”URL,但我认为最好只引用原始内容在Archive.org上的版本,并且我已经清理了我的答案以达到这个目的。 - natevw

20

抱歉,浏览器无法将HTML渲染到画布中。

如果可以的话,这可能会成为一个潜在的安全风险,因为HTML可以包含来自第三方网站的内容(特别是图像和iframes)。如果canvas可以将HTML内容转换为图像,然后读取图像数据,您可能会从其他网站提取特权内容。

要从HTML获取画布,您基本上必须使用drawImagefillText编写自己的HTML渲染器,这是一个潜在的巨大任务。有一种尝试在此处,但它有点不安全,并且离完整还有很长的路要走。(它甚至尝试从头开始解析HTML/CSS,我认为这太疯狂了!最好从已应用样式的真实DOM节点开始,并使用getComputedStyle读取样式以及使用offsetTop等相对位置来读取其部分。)


4
那真不爽。能够“冻结”一个元素并以某种方式扭曲它,然后再“解冻”它将会很棒。我不同意这会是一个新的安全风险。这与使用JS读取DOM并窃取信息没有任何区别(当然,任何JS沙盒约束都将适用于此)。感谢确认我的猜想的答案,Bobince。 - davemyron
1
是的,对于一般的HTML内容应用同源限制要比跨文档脚本更加棘手。您必须记录屏幕上包含任何第三方内容的每个矩形,并拒绝与它们相交的渲染请求 - 图像、iframe、视频/音频、带有背景图像的元素,所有对象/嵌入式对象/小程序无论是否为第三方(因为它们都有自己的跨站点规则),SVG等等。 - bobince
22
我不同意关于安全问题的观点。如果你有访问他们页面的权限,那么你一定能够获得更多信息,而不仅仅是从一个页面截图所能获取的信息。如果页面是公开的,那么对页面进行截图并不会增加新的安全漏洞! - BentFX
2
您可以将某人的页面包含在框架或其他嵌入方式中,而无需访问任何脚本。我可以使用<iframe src="https://example-online-bank.com/mystatement">将目标页面包含在我的文档中,但我没有通过DOM访问该页面的任何内容,并且不应该能够通过截屏来访问该信息。 - bobince
2
要从HTML中获取画布,您基本上必须使用drawImage和fillText从头开始编写自己的HTML渲染器,这是一个潜在的巨大任务。但是,仍然完成了,可以参考Aristos提供的其他答案。在我看来,这应该是被接受的答案。 - Daniel Baulig
显示剩余5条评论

13

您可以使用dom-to-image库(我是维护者)。

以下是您解决问题的方法:

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;
});

这里是 jsfiddle


1
感谢这个库!!在您的jsfiddle中通过在HTML中添加<h1>...</h1>进行了小改动。现在,输出被截断了。请查看新的jsfiddle链接:[link]http://jsfiddle.net/2zLL7wvn/我做错了什么吗?请帮忙解决。谢谢。 - junkle
@junkle 看起来是由于 h1 标签周围的边距引起的,可以通过设置高度、删除边距或将边距转换为填充来修复。 - xer21

9

基于natevw引用的Mozdev文章,我开始了一个小项目,可以在Firefox、Chrome和Safari中将HTML渲染到画布上。因此,例如,您可以简单地执行以下操作:

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

源代码和更详细的示例在这里


6

抱歉,没有这样的东西。

尽管规范说明:

未来版本的2D上下文API可能会提供一种将使用CSS呈现的文档片段直接渲染到画布上的方法。

这可能是你能得到的最接近的东西了。

很多人想要像 ctx.drawArbitraryHTML/Element 这样的功能,但是没有内置的功能可以实现。

唯一的例外是Mozilla独有的 drawWindow,它将DOM窗口的内容快照绘制到画布上。该功能仅适用于具有Chrome(“本地”)特权的代码。在普通的HTML页面中不允许使用它。因此,您可以将其用于编写类似于此示例中的FireFox扩展,但仅限于此。


4

4
我相信你的用意是好的,但像我这样的许多人是通过具体的谷歌查询来找到这些问题的,有人告诉我要做与我所询问的不同的事情并不是有帮助的。将样式化的HTML渲染到画布上意味着我可以将该HTML上传到WebGL纹理,并对格式化的内容进行样式化演示,包括变换和着色器。 - paniq
嗨@paniq,我只是想向您介绍一种实际有效且易于掌握的替代技术。很抱歉打扰了您。 - Jürgen Messing
1
好的。Stack Overflow说如果原始答案正在被编辑,我可以撤回我的踩。所以,如果你这样做,我会撤回。抱歉,当时有些不高兴。 - paniq
谢谢您的见解,我认为想要将HTML转换为画布的人们可能希望在您发布的示例中已经存在的效果。所以我很高兴您在这个帖子中发表了观点。 - thedrs

3

下面的代码可以用于两种模式,模式1将HTML代码保存为图像,模式2将HTML代码保存为画布。

此代码与库一起使用:https://github.com/tsayen/dom-to-image

*“id_div”是要转换的HTML元素的ID。

**“canvas_out”是包含画布的div的ID,因此请尝试此代码。

   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");
    });

    }

所以,如果你想保存图片,只需添加这个函数:

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

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

使用示例:

 <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>

如果这个有效,请发表评论。

如果对您有帮助,请留言 :)


我无法在IE上使其工作。我尝试使用ES6 promises的polyfill,但那也没有起作用。 - J_sdev

1
最简单的动画DOM元素的解决方案是使用CSS过渡/动画,但我想你已经知道了并且尝试使用canvas来做一些CSS不允许的事情。那么CSS自定义滤镜怎么样?如果你知道如何编写着色器,你可以以任何想象的方式转换你的元素。还有一些链接,别忘了检查CSS过滤实验室
注意:正如你可能想象的那样,浏览器支持很差。

0
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();
                    }
                }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接