将HTML元素渲染到<canvas>上

74

是否有一种方法可以在画布中呈现任意HTML元素(然后访问其缓冲区)...。


7
你应该查看html2canvas - Linus Thiel
6个回答

45

目前来看,你无法直接将HTML元素渲染到<canvas>中,因为canvas上下文并没有用于渲染HTML元素的功能。

但有一些模拟方法:

html2canvas项目http://html2canvas.hertzen.com/index.html(基本上是一个基于Javascript + canvas的HTML渲染器尝试)

根据您的使用情况,可能可以通过将HTML转换为SVG再绘制到<canvas>上来实现:

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

此外,如果您正在使用Firefox浏览器,可以通过获取扩展权限,然后将DOM窗口渲染到<canvas>中。

https://developer.mozilla.org/zh-CN/docs/Web/API/Canvas_API/Drawing_Graphics_with_Canvas?redirectlocale=en-US&redirectslug=Drawing_Graphics_with_Canvas#Rendering_Web_Content_Into_A_Canvas


目前(截至2016年5月25日),html2canvas存在缺陷(例如,为我生成了一个0x0的画布;当我将画布修复为某个大小时,在画布上没有可见的HTML文本)。我尝试过修复它,但这并不容易,所以我放弃了。 - Eugene Gr. Philippov
1
@EvgeniyPhilippov:这听起来像是一个bug,应该报告给相关的问题跟踪器。这个项目相当受欢迎,适用于多个用户。如果有什么原因导致你无法使用,那么应该正确地报告。 - Mikko Ohtamaa
2
html2canvas似乎对于可以在大约20行代码中完成的事情来说有些过度设计了... - CpnCrunch

43

请查看MDN关于将DOM对象绘制到画布中的文章

给定加载到JS string值中的HTML,可以将其呈现为<canvas><img>元素。这是通过SVG的<foreignObject>元素来完成的。

以下是在Javascript中将HTML string呈现为<canvas>元素或<img />元素的工作示例:

将HTML呈现到<canvas>元素:

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>


将HTML渲染为<img />图像元素:

您还可以通过一些调整将HTML渲染到文档中的<img>元素中:

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://dev59.com/06Tja4cB1Zd3GeqPGbtj#75848053
        URL.revokeObjectURL( oldSrc );
    }

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


1
虽然这在您的示例中可以工作,但对于任意的HTML来说并不适用,因为您需要将HTML转换为XML。 - CpnCrunch
如果我们想要创建一个包含图片的子元素的 div,该怎么办?当我这样做时,图片没有显示出来。我错过了什么吗? - Jess the Mess
@Jess 你需要将代码放在 <div xmlns=... </div> 元素内。 - Suresh Mahawar
2
对于其他寻求启示的人:foreignObject在IE中不受支持。 - icfantv
@CpnCrunch 在foreignObject中,任意HTML与XML有何不同? - Nick
显示剩余4条评论

29

这是将任意HTML代码呈现为画布的代码:

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

例子:

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>


2
你能在JSFiddle上提供一个演示吗(div + image)?谢谢! - aloisdg
这绝对不是一种可靠的方式,因为它甚至不能渲染图像,更不用说视频元素或其他动态数据了。 - Footniko
很好,它可以工作!你有浏览器支持的任何想法吗?谢谢。 - cancerbero
@cancerbero 在Safari、Firefox和Chrome上似乎可以可靠地工作。我们正在生产代码中使用它。我刚刚更新了代码以修复散列字符的问题。 - CpnCrunch
找不到名称“txt”。你是不是想说“Text”? - Anton Duzenko
显示剩余3条评论

4
CSS的element()函数可能最终会对某些人有所帮助,即使它不是直接回答问题的答案。它允许您将元素(以及所有子元素,包括视频,跨域iframe等)用作背景图像(以及在 CSS 代码中通常使用 url(...) 的任何其他地方)。这篇博客文章展示了您可以通过它实现什么。
自2011年以来,Firefox已经实现了它,并且在Chromium/Chrome中正在考虑使用它(如果您关心此功能,请不要忘记为该问题打星)。

很遗憾,截至2022年7月,element()在除Firefox以外的所有浏览器中仍然得到了非常差的支持:https://caniuse.com/css-element-function - flyingace

3
RasterizeHTML 是一个非常好的项目,但是如果你需要访问 canvas,它在 Chrome 上不起作用,因为使用了 <foreignObject>
如果你需要访问 canvas,则可以使用 html2canvas。
我正在尝试寻找另一个项目,因为 html2canvas 的性能非常慢。

你好,你的回答似乎有些混乱(“由于使用”什么?)。也许有一些信息丢失了? - jochen

1
根据HTML规范,您无法访问Canvas的元素。您可以获取其上下文,并在其中绘制和操作,但仅限于此。
但是,您可以将Canvas和html元素放置在同一个带有position: relative的div中,然后将canvas和其他元素设置为position: absolute。这样它们将彼此重叠。然后,您可以使用leftright CSS属性来定位html元素。
如果元素未显示出来,则可能是canvas在其前面,因此请使用z-index CSS属性将其置于canvas之前。

1
抱歉,可能我没有表达清楚...我的意思是:是否可以在画布帧缓冲区上呈现HTML元素。 - gotch4

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