捕获超文本标记语言画布作为gif/jpg/png/pdf?

是否可以将html画布中显示的内容捕获或打印为图像或pdf?

我想通过画布生成一个图像,并能够从该图像生成一个png。

751129 次浏览

HTML5提供了Canvas.toDataURL(mimetype),它在Opera、Firefox和Safari4 beta中实现。然而,有许多安全限制(主要与将来自另一个来源的内容绘制到画布上有关)。

所以你不需要额外的图书馆。

e. g.

 <canvas id=canvas width=200 height=200></canvas><script>window.onload = function() {var canvas = document.getElementById("canvas");var context = canvas.getContext("2d");context.fillStyle = "green";context.fillRect(50, 50, 100, 100);// no argument defaults to image/png; image/jpeg, etc also work on some// implementations -- image/png is the only one that must be supported per spec.window.location = canvas.toDataURL("image/png");}</script>

从理论上讲,这应该创建然后导航到中间有绿色正方形的图像,但我还没有测试。

最初的答复是针对一个类似的问题。这已经修改:

const canvas = document.getElementById('mycanvas')const img    = canvas.toDataURL('image/png')

使用IMG中的值,您可以将其写成新图像,如下所示:

document.getElementById('existing-image-id').src = img

document.write('<img src="'+img+'"/>');

我会使用“wkhtmltopdf”。它工作得很好。它使用webkit引擎(用于Chrome,Safari等),并且非常易于使用:

wkhtmltopdf stackoverflow.com/questions/923885/ this_question.pdf

就是它了!

试试看

另一个有趣的解决方案是PhantomJS。这是一个带有JavaScript或CoffeeScript的无头WebKit脚本表。

其中一个用例是屏幕捕获:您可以以编程方式捕获Web内容,包括SVG和Canvas和/或使用缩略图预览创建网站屏幕截图。

最好的切入点是屏幕截图 wiki页面。

这是一个很好的极地时钟示例(来自RaphaelJS):

>phantomjs rasterize.js http://raphaeljs.com/polar-clock.html clock.png

您想将页面渲染为PDF吗?

> phantomjs rasterize.js 'http://en.wikipedia.org/w/index.php?title=Jakarta&printable=yes' jakarta.pdf

我想我应该把这个问题的范围扩大一点,在这个问题上有一些有用的花絮。

为了将画布作为图像获取,您应该执行以下操作:

var canvas = document.getElementById("mycanvas");var image = canvas.toDataURL("image/png");

您可以使用它将图像写入页面:

document.write('<img src="'+image+'"/>');

其中“image/png”是mime类型(png是唯一必须支持的类型)。如果您想要支持的类型数组,您可以按照以下方式执行操作:

var imageMimes = ['image/png', 'image/bmp', 'image/gif', 'image/jpeg', 'image/tiff']; //Extend as necessaryvar acceptedMimes = new Array();for(i = 0; i < imageMimes.length; i++) {if(canvas.toDataURL(imageMimes[i]).search(imageMimes[i])>=0) {acceptedMimes[acceptedMimes.length] = imageMimes[i];}}

您只需要在每个页面上运行一次-它不应该在页面的生命周期中发生变化。

如果您希望让用户在保存文件时下载文件,您可以执行以下操作:

var canvas = document.getElementById("mycanvas");var image = canvas.toDataURL("image/png").replace("image/png", "image/octet-stream"); //Convert image to 'octet-stream' (Just a download, really)window.location.href = image;

如果您将其用于不同的mime类型,请务必更改图像/png的两个实例,但不要更改图像/八位字节流。还值得一提的是,如果您在渲染画布时使用任何跨域资源,当您尝试使用toDataUrl方法时,您将遇到安全错误。

如果您通过服务器进行下载,这里有一些帮助(这样您就可以命名/转换/后处理/等您的文件):

使用toDataURL发布数据

-设置标题

$filename = "test.jpg"; //or pngheader('Content-Description: File Transfer');if($msie = !strstr($_SERVER["HTTP_USER_AGENT"],"MSIE")==false)header("Content-type: application/force-download");elseheader("Content-type: application/octet-stream");header("Content-Disposition: attachment; filename=\"$filename\"");header("Content-Transfer-Encoding: binary");header("Expires: 0"); header("Cache-Control: must-revalidate");header("Pragma: public");

-创建图像

$data = $_POST['data'];$img = imagecreatefromstring(base64_decode(substr($data,strpos($data,',')+1)));

-导出图像如jpeg

$width = imagesx($img);$height = imagesy($img);$output = imagecreatetruecolor($width, $height);$white = imagecolorallocate($output,  255, 255, 255);imagefilledrectangle($output, 0, 0, $width, $height, $white);imagecopy($output, $img, 0, 0, 0, 0, $width, $height);imagejpeg($output);exit();

-or如透明PNG

imagesavealpha($img, true);imagepng($img);die($img);

如果你使用的是jQuery,很多人都这样做,那么你会像这样实现公认的答案:

var canvas = $("#mycanvas")[0];var img = canvas.toDataURL("image/png");
$("#elememt-to-write-to").html('<img src="'+img+'"/>');
function exportCanvasAsPNG(id, fileName) {
var canvasElement = document.getElementById(id);
var MIME_TYPE = "image/png";
var imgURL = canvasElement.toDataURL(MIME_TYPE);
var dlLink = document.createElement('a');dlLink.download = fileName;dlLink.href = imgURL;dlLink.dataset.downloadurl = [MIME_TYPE, dlLink.download, dlLink.href].join(':');
document.body.appendChild(dlLink);dlLink.click();document.body.removeChild(dlLink);}

在某些版本的Chrome中,您可以:

  1. 使用绘制图像函数ctx.drawImage(image1, 0, 0, w, h);
  2. 右键单击画布

您可以使用jspdf将画布捕获到图像或pdf中,如下所示:

var imgData = canvas.toDataURL('image/png');var doc = new jsPDF('p', 'mm');doc.addImage(imgData, 'PNG', 10, 10);doc.save('sample-file.pdf');

更多信息:https://github.com/MrRio/jsPDF

这是另一种方式,没有字符串,尽管我不知道它是否更快。而不是toDataURL(正如这里所有的问题所提出的)。在我的情况下,我想阻止dataUrl/base 64,因为我需要一个数组缓冲区或视图。所以HTMLCanvasElement中的另一种方法是toBlob。(TypeScript函数):

    export function canvasToArrayBuffer(canvas: HTMLCanvasElement, mime: string): Promise<ArrayBuffer> {return new Promise((resolve, reject) => canvas.toBlob(async (d) => {if (d) {const r = new FileReader();r.addEventListener('loadend', e => {const ab = r.result;if (ab) {resolve(ab as ArrayBuffer);}else {reject(new Error('Expected FileReader result'));}}); r.addEventListener('error', e => {reject(e)});r.readAsArrayBuffer(d);}else {reject(new Error('Expected toBlob() to be defined'));}}, mime));}

blob的另一个优点是您可以创建ObjectUrls来将数据表示为文件,类似于HTMLInputFile的“文件”成员。更多信息:

https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/toBlob

简单的答案就是获取它的blob并将img src设置为该blob的新对象URL,然后使用一些库将该图像添加到PDF中,例如

var ok = document.createElement("canvas")ok.width = 400ok.height = 140var ctx = ok.getContext("2d");for(let k = 0; k < ok.height; k++)(k%Math.floor((Math.random()) *10)==0) && (y => {for(var i = 0; i < ok.width; i++) {if(i % 25 == 0) {ctx.globalAlpha = Math.random()ctx.fillStyle = ("rgb(" +Math.random() * 255 + "," +Math.random() * 255 + "," +Math.random() * 255 + ")");
(wdth =>ctx.fillRect(Math.sin(i * Math.PI / 180) *Math.random() *ok.width,Math.cos(i * Math.PI / 180,) * wdth + y,wdth,wdth))(15)}}})(k)
ok.toBlob(blob => {k.src = URL.createObjectURL(blob)})
<img id=k>

Alternatively, if you wanted to work with low-level byte data, you can get the raw bytes of the canvas, then, depending on the file spec, write the raw image data into the necessary bytes of the data. you just need to call ctx.getImageData(0, 0, ctx.canvas.widht, ctx.canvas.height) to get the raw image data, then based on the file specification, write it to that

如果你想嵌入画布,你可以使用这个片段

<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title></head><body><canvas id=canvas width=200 height=200></canvas><iframe id='img' width=200 height=200></iframe><script>window.onload = function() {var canvas = document.getElementById("canvas");var context = canvas.getContext("2d");context.fillStyle = "green";context.fillRect(50, 50, 100, 100);document.getElementById('img').src = canvas.toDataURL("image/jpeg");console.log(canvas.toDataURL("image/jpeg"));}</script></body></html>

关键是

canvas.toDataURL(类型,质量)


我想为像我这样想要将SVG保存到PNG的人提供一个例子(如果你愿意,也可以添加一些文本),这可能来自在线资源或字体很棒的图标等。

示例

100%javascript,没有其他第三库。

<script>(() => {window.onload = () => {// Test 1: SVG from Onlineconst canvas = new Canvas(650, 500)// canvas.DrawGrid() // If you want to show grid, you can use it.const svg2img = new SVG2IMG(canvas.canvas, "https://upload.wikimedia.org/wikipedia/commons/b/bd/Test.svg")svg2img.AddText("Hello", 100, 250, {mode: "fill", color: "yellow", alpha: 0.8})svg2img.AddText("world", 200, 250, {mode: "stroke", color: "red"})svg2img.AddText("!", 280, 250, {color: "#f700ff", size: "72px"})svg2img.Build("Test.png")
// Test 2: URI.dataconst canvas2 = new Canvas(180, 180)const uriData = "data:image/svg+xml;base64,PHN2ZyBjbGFzcz0ic3ZnLWlubGluZS0tZmEgZmEtc21pbGUtd2luayBmYS13LTE2IiBhcmlhLWhpZGRlbj0idHJ1ZSIgZm9jdXNhYmxlPSJmYWxzZSIgZGF0YS1wcmVmaXg9ImZhciIgZGF0YS1pY29uPSJzbWlsZS13aW5rIiByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDQ5NiA1MTIiIGRhdGEtZmEtaTJzdmc9IiI+PHBhdGggZmlsbD0iY3VycmVudENvbG9yIiBkPSJNMjQ4IDhDMTExIDggMCAxMTkgMCAyNTZzMTExIDI0OCAyNDggMjQ4IDI0OC0xMTEgMjQ4LTI0OFMzODUgOCAyNDggOHptMCA0NDhjLTExMC4zIDAtMjAwLTg5LjctMjAwLTIwMFMxMzcuNyA1NiAyNDggNTZzMjAwIDg5LjcgMjAwIDIwMC04OS43IDIwMC0yMDAgMjAwem0xMTcuOC0xNDYuNGMtMTAuMi04LjUtMjUuMy03LjEtMzMuOCAzLjEtMjAuOCAyNS01MS41IDM5LjQtODQgMzkuNHMtNjMuMi0xNC4zLTg0LTM5LjRjLTguNS0xMC4yLTIzLjctMTEuNS0zMy44LTMuMS0xMC4yIDguNS0xMS41IDIzLjYtMy4xIDMzLjggMzAgMzYgNzQuMSA1Ni42IDEyMC45IDU2LjZzOTAuOS0yMC42IDEyMC45LTU2LjZjOC41LTEwLjIgNy4xLTI1LjMtMy4xLTMzLjh6TTE2OCAyNDBjMTcuNyAwIDMyLTE0LjMgMzItMzJzLTE0LjMtMzItMzItMzItMzIgMTQuMy0zMiAzMiAxNC4zIDMyIDMyIDMyem0xNjAtNjBjLTI1LjcgMC01NS45IDE2LjktNTkuOSA0Mi4xLTEuNyAxMS4yIDExLjUgMTguMiAxOS44IDEwLjhsOS41LTguNWMxNC44LTEzLjIgNDYuMi0xMy4yIDYxIDBsOS41IDguNWM4LjUgNy40IDIxLjYuMyAxOS44LTEwLjgtMy44LTI1LjItMzQtNDIuMS01OS43LTQyLjF6Ij48L3BhdGg+PC9zdmc+"const svg2img2 = new SVG2IMG(canvas2.canvas, uriData)svg2img2.Build("SmileWink.png")
// Test 3: Exists SVGImportFontAwesome()const range = document.createRange()const fragSmile = range.createContextualFragment(`<i class="far fa-smile" style="background-color:black;color:yellow"></i>`)document.querySelector(`body`).append(fragSmile)
// use MutationObserver wait the fontawesome convert ``<i class="far fa-smile"></i>`` to SVG. If you write the element in the HTML, then you can skip this hassle way.const observer = new MutationObserver((mutationRecordList, observer) => {for (const mutation of mutationRecordList) {switch (mutation.type) {case "childList":const targetSVG = mutation.target.querySelector(`svg`)if (targetSVG !== null) {const canvas3 = new Canvas(64, 64) // 👈 Focus here. The part of the observer is not important.const svg2img3 = new SVG2IMG(canvas3.canvas, SVG2IMG.Convert2URIData(targetSVG))svg2img3.Build("Smile.png")targetSVG.remove() // This SVG is created by font-awesome, and it's an extra element. I don't want to see it.observer.disconnect()return}}}})observer.observe(document.querySelector(`body`), {childList: true})}})()
class SVG2IMG {/*** @param {HTMLCanvasElement} canvas* @param {string} src "http://.../xxx.svg"  or "data:image/svg+xml;base64,${base64}"* */constructor(canvas, src) {this.canvas = canvas;this.context = this.canvas.getContext("2d")this.src = srcthis.addTextList = []}
/*** @param {HTMLElement} node* @param {string} mediaType: https://en.wikipedia.org/wiki/Media_type#Common_examples_%5B10%5D* @see https://en.wikipedia.org/wiki/List_of_URI_schemes* */static Convert2URIData(node, mediaType = 'data:image/svg+xml') {const base64 = btoa(node.outerHTML)return `${mediaType};base64,${base64}`}
/*** @param {string} text* @param {int} x* @param {int} y* @param {"stroke"|"fill"} mode* @param {string} size, "30px"* @param {string} font, example: "Arial"* @param {string} color, example: "#3ae016" or "yellow"* @param {int} alpha, 0.0 (fully transparent) to 1.0 (fully opaque) // https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Applying_styles_and_colors#transparency* */AddText(text, x, y, {mode = "fill", size = "32px", font = "Arial", color = "black", alpha = 1.0}) {const drawFunc = (text, x, y, mode, font) => {return () => {// https://www.w3schools.com/graphics/canvas_text.asp// https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/fillTextconst context = this.contextconst originAlpha = context.globalAlphacontext.globalAlpha = alphacontext.font = `${size} ${font}`
switch (mode) {case "fill":context.fillStyle = colorcontext.fillText(text, x, y)breakcase "stroke":context.strokeStyle = colorcontext.strokeText(text, x, y)breakdefault:throw Error(`Unknown mode:${mode}`)}context.globalAlpha = originAlpha}}this.addTextList.push(drawFunc(text, x, y, mode, font))}
/*** @description When the build is finished, you can click the filename to download the PNG or mouse enters to copy PNG to the clipboard.* */Build(filename = "download.png") {const img = new Image()img.src = this.srcimg.crossOrigin = "anonymous" // Fixes: Tainted canvases may not be exported
img.onload = (event) => {this.context.drawImage(event.target, 0, 0)for (const drawTextFunc of this.addTextList) {drawTextFunc()}
// create a "a" node for downloadconst a = document.createElement('a')document.querySelector('body').append(a)a.innerText = filenamea.download = filename
const quality = 1.0// a.target = "_blank"a.href = this.canvas.toDataURL("image/png", quality)a.append(this.canvas)}
this.canvas.onmouseenter = (event) => {// set background to white. Otherwise, background-color is black.this.context.globalCompositeOperation = "destination-over" // https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation // https://www.w3schools.com/tags/canvas_globalcompositeoperation.aspthis.context.fillStyle = "rgb(255,255,255)"this.context.fillRect(0, 0, this.canvas.width, this.canvas.height)this.canvas.toBlob(blob => navigator.clipboard.write([new ClipboardItem({'image/png': blob})])) // copy to clipboard}}}
class Canvas {
/*** @description for do something like that: ``<canvas width="" height=""></>canvas>``**/constructor(w, h) {const canvas = document.createElement("canvas")document.querySelector(`body`).append(canvas)this.canvas = canvas;[this.canvas.width, this.canvas.height] = [w, h]}
/*** @description If your SVG is large, you may want to know which part is what you wanted.* */DrawGrid(step = 100) {const ctx = this.canvas.getContext('2d')const w = this.canvas.widthconst h = this.canvas.height
// Draw the vertical line.ctx.beginPath();for (let x = 0; x <= w; x += step) {ctx.moveTo(x, 0);ctx.lineTo(x, h);}// set the color of the linectx.strokeStyle = 'rgba(255,0,0, 0.5)'ctx.lineWidth = 1ctx.stroke();
// Draw the horizontal line.ctx.beginPath();for (let y = 0; y <= h; y += step) {ctx.moveTo(0, y)ctx.lineTo(w, y)}ctx.strokeStyle = 'rgba(128, 128, 128, 0.5)'ctx.lineWidth = 5ctx.stroke()}}
function ImportFontAwesome() {const range = document.createRange()const frag = range.createContextualFragment(`<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.2/css/all.min.css" integrity="sha512-HK5fgLBL+xu6dm/Ii3z4xhlSUyZgTT9tuc/hSrtw6uzJOvgRr2a9jyxxT1ely+B+xFAmJKVSTbpM/CuL7qxO8w==" crossorigin="anonymous" /><script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.2/js/all.min.js" integrity="sha512-UwcC/iaz5ziHX7V6LjSKaXgCuRRqbTp1QHpbOJ4l1nw2/boCfZ2KlFIqBUA/uRVF0onbREnY9do8rM/uT/ilqw==" crossorigin="anonymous"/>`)document.querySelector("head").append(frag)}</script>

如果你想在stackoverflow上运行并在图片上移动鼠标可能会出错

由于应用于当前文档的权限策略,剪贴板API已被阻止

您可以在本地机器上复制代码并再次运行,就可以了。

upload image<canvas />

async function canvasToBlob(canvas) {if (canvas.toBlob) {return new Promise(function (resolve) {canvas.toBlob(resolve)})} else {throw new Error('canvas.toBlob Invalid')}}
await canvasToBlob(yourCanvasEl)