768

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

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

4

15 回答 15

796

原始答案是针对类似问题的。这已被修改:

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

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

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

或者

document.write('<img src="'+img+'"/>');
于 2010-08-18T16:37:57.370 回答
119

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

所以你不需要额外的库。

例如

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

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

于 2009-05-29T02:48:41.320 回答
48

我想我会稍微扩大一下这个问题的范围,在这个问题上提供一些有用的花絮。

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

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 necessary 
var 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 类型,请务必更改 image/png 的两个实例,而不是 image/octet-stream。还值得一提的是,如果在渲染画布时使用任何跨域资源,在尝试使用 toDataUrl 方法时会遇到安全错误。

于 2013-07-01T14:52:10.553 回答
42
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);
}
于 2015-05-22T09:40:58.450 回答
20

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

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

而已!

试试看

于 2011-02-02T02:33:21.773 回答
15

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

- 使用发布数据toDataURL

- 设置标题

$filename = "test.jpg"; //or png
header('Content-Description: File Transfer');
if($msie = !strstr($_SERVER["HTTP_USER_AGENT"],"MSIE")==false)      
  header("Content-type: application/force-download");else       
  header("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();

- 或作为透明 PNG

imagesavealpha($img, true);
imagepng($img);
die($img);
于 2013-07-22T17:33:59.317 回答
10

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

    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

于 2019-09-23T06:38:35.353 回答
8

另一个有趣的解决方案是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
于 2013-05-21T11:28:05.740 回答
4

如果您使用的是 jQuery(很多人都这样做),那么您可以像这样实现公认的答案:

var canvas = $("#mycanvas")[0];
var img = canvas.toDataURL("image/png");

$("#elememt-to-write-to").html('<img src="'+img+'"/>');
于 2013-09-07T11:01:59.330 回答
1

关键是

canvas.toDataURL(类型,质量)


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

例子

100% javascript,没有其​​他 3-rd 库。

<script>
  (() => {
    window.onload = () => {
      // Test 1: SVG from Online
      const 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.data
      const 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 SVG
      ImportFontAwesome()
      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 = src
      this.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/fillText
          const context = this.context
          const originAlpha = context.globalAlpha
          context.globalAlpha = alpha
          context.font = `${size} ${font}`

          switch (mode) {
            case "fill":
              context.fillStyle = color
              context.fillText(text, x, y)
              break
            case "stroke":
              context.strokeStyle = color
              context.strokeText(text, x, y)
              break
            default:
              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.src
      img.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 download
        const a = document.createElement('a')
        document.querySelector('body').append(a)
        a.innerText = filename
        a.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.asp
        this.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.width
      const 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 line
      ctx.strokeStyle = 'rgba(255,0,0, 0.5)'
      ctx.lineWidth = 1
      ctx.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 = 5
      ctx.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 上运行并将鼠标移动到图片上可能会出错

DOMException:剪贴板 API 已被阻止,因为当前文档应用了权限策略

您可以将代码复制到本地计算机上并再次运行,就可以了。

于 2021-07-15T17:02:56.323 回答
0

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)
于 2021-09-01T04:12:44.797 回答
0

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

<!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>
于 2021-01-30T15:46:46.583 回答
0

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

var ok = document.createElement("canvas")
ok.width = 400
ok.height = 140
var 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>

或者,如果您想使用低级字节数据,您可以获取画布的原始字节,然后根据文件规范,将原始图像数据写入数据的必要字节。您只需要调用ctx.getImageData(0, 0, ctx.canvas.widht, ctx.canvas.height)以获取原始图像数据,然后根据文件规范将其写入

于 2020-06-21T21:00:56.587 回答
0

您可以使用 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

于 2017-12-13T21:27:32.717 回答
0

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

  1. 使用画图功能ctx.drawImage(image1, 0, 0, w, h);
  2. 在画布上单击鼠标右键
于 2015-11-16T22:13:30.113 回答