使用Javascript从div中的HTML生成pdf

Joh*_*ord 207 javascript jspdf

我有以下HTML代码:

<!DOCTYPE html>
<html>
    <body>
        <p>don't print this to pdf</p>
        <div id="pdf">
            <p><font size="3" color="red">print this to pdf</font></p>
        </div>
    </body>
</html>
Run Code Online (Sandbox Code Playgroud)

我想做的就是打印到pdf中找到的任何ID为"pdf"的div.这必须使用JavaScript完成.然后应自动下载"pdf"文档,文件名为"foobar.pdf"

我一直在使用jspdf来做这件事,但它唯一的功能是"text",它只接受字符串值.我想将HTML提交给jspdf,而不是文本.

snr*_*rlx 203

jsPDF能够使用插件.为了使其能够打印HTML,您必须包含某些插件,因此必须执行以下操作:

  1. 转到https://github.com/MrRio/jsPDF并下载最新版本.
  2. 在项目中包含以下脚本:
    • jspdf.js
    • jspdf.plugin.from_html.js
    • jspdf.plugin.split_text_to_size.js
    • jspdf.plugin.standard_fonts_metrics.js

如果要忽略某些元素,则必须使用ID标记它们,然后可以在jsPDF的特殊元素处理程序中忽略该ID.因此,您的HTML应如下所示:

<!DOCTYPE html>
<html>
  <body>
    <p id="ignorePDF">don't print this to pdf</p>
    <div>
      <p><font size="3" color="red">print this to pdf</font></p>
    </div>
  </body>
</html>
Run Code Online (Sandbox Code Playgroud)

然后使用以下JavaScript代码在PopUp中打开创建的PDF:

var doc = new jsPDF();          
var elementHandler = {
  '#ignorePDF': function (element, renderer) {
    return true;
  }
};
var source = window.document.getElementsByTagName("body")[0];
doc.fromHTML(
    source,
    15,
    15,
    {
      'width': 180,'elementHandlers': elementHandler
    });

doc.output("dataurlnewwindow");
Run Code Online (Sandbox Code Playgroud)

对我来说,这创建了一个漂亮而整洁的PDF,其中只包括"打印到pdf"这一行.

请注意,特殊元素处理程序仅处理当前版本中的ID,这也在GitHub问题中说明.它指出:

因为匹配是针对节点树中的每个元素完成的,所以我希望尽可能快地完成匹配.在这种情况下,它意味着"只有元素ID匹配"元素ID仍然以jQuery样式"#id"完成,但并不意味着所有jQuery选择器都受支持.

因此,用'.ignorePDF'等类选择器替换'#ignorePDF'对我来说不起作用.相反,您将不得不为每个要忽略的元素添加相同的处理程序,如:

var elementHandler = {
  '#ignoreElement': function (element, renderer) {
    return true;
  },
  '#anotherIdToBeIgnored': function (element, renderer) {
    return true;
  }
};
Run Code Online (Sandbox Code Playgroud)

示例中还可以说,可以选择诸如"a"或"li"的标签.对于大多数用例来说,这可能有点无限制:

我们支持特殊元素处理程序.使用jQuery样式ID选择器为ID或节点名称注册它们.("#iAmID","div","span"等)此时不支持任何其他类型的选择器(复合类).

一个非常重要的事情是你丢失了所有的样式信息(CSS).幸运的是,jsPDF可以很好地格式化h1,h2,h3等,这对我来说已经足够了.另外,它只会在文本节点中打印文本,这意味着它不会打印textareas等的值.例:

<body>
  <ul>
    <!-- This is printed as the element contains a textnode -->        
    <li>Print me!</li>
  </ul>
  <div>
    <!-- This is not printed because jsPDF doesn't deal with the value attribute -->
    <input type="textarea" value="Please print me, too!">
  </div>
</body>
Run Code Online (Sandbox Code Playgroud)

  • 如果我没记错的话 ````doc.fromHTML```` 不再受支持 (17认同)
  • `doc.fromHTML()` 已被弃用并替换为 `doc.html()` (6认同)
  • "如果你想忽略某些元素,你必须用一个ID来标记它们" - 一个很棒的图书馆,被那个倒置的重新评估所破坏.OP想要打印一个`<div>`,这可能是数百个之一 - 所以他必须标记所有*不需要的*DOM元素? (4认同)
  • 我猜猜元素处理程序可以是一个类吗?这在语义上更符合HTML5标准.ID不仅在CSS中具有太多的重要性,它还必须是唯一的. (3认同)
  • @snrlx我得到空白的pdf和这个错误:renderer.pdf.sHashCode不是一个函数 (2认同)
  • 这个插件可能很好,但它的文档很少。我在 stackoverflow 中看到的大多数示例甚至都不起作用。 (2认同)

小智 46

这是一个简单的解决方案.这适用于我.您可以使用javascript打印概念,并将其简单保存为pdf.

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>
    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
    <script type="text/javascript">
        $("#btnPrint").live("click", function () {
            var divContents = $("#dvContainer").html();
            var printWindow = window.open('', '', 'height=400,width=800');
            printWindow.document.write('<html><head><title>DIV Contents</title>');
            printWindow.document.write('</head><body >');
            printWindow.document.write(divContents);
            printWindow.document.write('</body></html>');
            printWindow.document.close();
            printWindow.print();
        });
    </script>
</head>
<body>
    <form id="form1">
    <div id="dvContainer">
        This content needs to be printed.
    </div>
    <input type="button" value="Print Div Contents" id="btnPrint" />
    </form>
</body>
</html>
Run Code Online (Sandbox Code Playgroud)

  • "简单地将其保存为pdf" - 我错过了那一部分.你怎么做呢? (22认同)
  • 这对我有用,为了解决CSS样式的问题,我创建了另一个名为printPDF.css的css文件,并在上面的示例中使用链接标记添加:printWindow.document.write('<html> <head> <title> DIV内容</ title>'); printWindow.document.write('<link rel ="stylesheet"href ="../ css/printPDF.css"/>'); printWindow.document.write('</ head> <body>'); (7认同)
  • 多么完美的答案,就像馅饼一样简单.奇迹般有效.必须选择作为已接受的答案并作为正确的解决方案提供. (4认同)
  • 一些评论: 1) 您不需要用于打印的特定样式表。在您当前的样式表中,执行以下操作:@media print { .pageBreak { page-break-before: always; } .labelPdf { 字体粗细:粗体;字体大小:20px;} .noPrintPdf { 显示:无;并根据需要使用这些类。2) .live("click", ...) 对我不起作用,所以我使用 .on("click", ...) 代替。 (3认同)
  • @DavidsonLima 此代码创建新窗口,该窗口不会看到旧窗口 css。这就是为什么它“忽略”css,它不是忽略,只是在新窗口中它没有加载。只需在头部加载它,它就会被渲染。 (2认同)
  • 以防万一有人试图用 Angular 实现这一点,请将您的 CSS 文件放在 assets 文件夹中。 (2认同)

Luk*_*kas 17

  • 无依赖,纯JS
  • 要添加 CSS 或图像 - 不要使用相对 URL,使用完整 URL 等http://...domain.../path.css。它创建单独的 HTML 文档,并且没有主要内容的上下文。
  • 您还可以将图像嵌入为 base64

这为我服务了多年:

export default function printDiv({divId, title}) {
  let mywindow = window.open('', 'PRINT', 'height=650,width=900,top=100,left=150');

  mywindow.document.write(`<html><head><title>${title}</title>`);
  mywindow.document.write('</head><body >');
  mywindow.document.write(document.getElementById(divId).innerHTML);
  mywindow.document.write('</body></html>');

  mywindow.document.close(); // necessary for IE >= 10
  mywindow.focus(); // necessary for IE >= 10*/

  mywindow.print();
  mywindow.close();

  return true;
}
Run Code Online (Sandbox Code Playgroud)

当然,这将打开打印对话框,用户必须知道她/他可以选择打印到 pdf 选项,以获取 pdf。可以预先选择打印机,如果用户确认可以实际打印该文件。为了避免这种情况并提供没有任何额外内容的PDF,您需要制作PDF文件。可能在服务器端。您可以使用仅带有发票的小 html 页面,并将其转换为带有无头 chrome 的 PDF 文件。使用 puppeteer 非常容易。无需安装/配置 chrome,只需安装 npm 包 puppeteer(由 chrome 团队管理)并运行它。请记住,这实际上会在没有 GUI 的情况下启动真正的 chrome,因此您需要为此配备一些 RAM 和 CPU。大多数服务器在流量足够低的情况下都可以。这是代码示例,但这必须在 BACKEND 上运行。节点。它也是缓慢的调用,它是资源密集型调用。

const puppeteer = require('puppeteer');
(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto('https://your-domain.com/path-to-invoice', {
    waitUntil: 'networkidle2',
  });
  await page.pdf({ path: 'invoice-file-path.pdf', format: 'a4' });

  await browser.close();
})();
Run Code Online (Sandbox Code Playgroud)

在此处了解更多信息:https : //pptr.dev/

  • 问题是 pdf 中不会有任何 css 效果。 (3认同)
  • @ShadabFaiz 它将但它可能与主窗口不同。您仍然可以添加自定义 css 至此 html。 (2认同)
  • 但是不渲染图像。 (2认同)
  • 我喜欢这个!在这里和那里进行一些调整,看起来不错。还有一件小事不会删除 `&lt;body &gt;` 处的额外间距,它需要这个:P (2认同)

Mar*_*rco 16

您可以使用autoPrint()并将输出设置为'dataurlnewwindow',如下所示:

function printPDF() {
    var printDoc = new jsPDF();
    printDoc.fromHTML($('#pdf').get(0), 10, 10, {'width': 180});
    printDoc.autoPrint();
    printDoc.output("dataurlnewwindow"); // this opens a new popup,  after this the PDF opens the print window view but there are browser inconsistencies with how this is handled
}
Run Code Online (Sandbox Code Playgroud)

  • 从我观察到的,非常具有讽刺意味的是,仅当作为参数发送的元素不包含任何HTML时,HTML才起作用:仅支持纯文本.有点杀死了整个事物的目的. (12认同)
  • `.fromHTML()` 已被弃用并替换为 `.html()` (2认同)

Roh*_*rte 10

一种方法是使用 window.print() 函数。哪个不需要任何库

优点

1.不需要外部库。

2.我们也可以只打印身体的选定部分。

3.没有css冲突和js问题。

4.核心html/js功能

---只需添加以下代码

CSS

@media print {
        body * {
            visibility: hidden; // part to hide at the time of print
            -webkit-print-color-adjust: exact !important; // not necessary use         
               if colors not visible
        }

        #printBtn {
            visibility: hidden !important; // To hide 
        }

        #page-wrapper * {
            visibility: visible; // Print only required part
            text-align: left;
            -webkit-print-color-adjust: exact !important;
        }
    }
Run Code Online (Sandbox Code Playgroud)

JS 代码 -在 btn 单击上调用 bewlow 函数

$scope.printWindow = function () {
  window.print()
}
Run Code Online (Sandbox Code Playgroud)

注意:在每个 css 对象中使用 !important

例子 -

.legend  {
  background: #9DD2E2 !important;
}
Run Code Online (Sandbox Code Playgroud)

  • 浏览器的打印功能存在问题。用户通常为打印视图选择默认选项(边距、页面大小等)。因此,在没有培训用户的情况下,很难生成具有所需样式的所需 PDF,这是更加困难且几乎不可能的...... (2认同)

Noa*_*Noa 10

2022年答案:

要从 HTML 元素生成 PDF 并提示保存文件:

import { jsPDF } from "jsPDF"

function generatePDF() {
  const doc = new jsPDF({ unit: 'pt' }) // create jsPDF object
  const pdfElement = document.getElementById('pdf') // HTML element to be converted to PDF

  doc.html(pdfElement, {
    callback: (pdf) => {
      pdf.save('MyPdfFile.pdf')
    },
    margin: 32, // optional: page margin
    // optional: other HTMLOptions
  })
}
Run Code Online (Sandbox Code Playgroud)
<button onclick="generatePDF()">Save PDF</button>
Run Code Online (Sandbox Code Playgroud)

...

要预览 PDF 而不打印:

doc.html(pdfElement, {
  callback: (pdf) => {
    const myPdfData = pdf.output('datauristring')
  }
})
Run Code Online (Sandbox Code Playgroud)
<embed type="application/pdf" src={myPdfData} />
Run Code Online (Sandbox Code Playgroud)

...
更多 HTMLOptions:
https://github.com/parallax/jsPDF/blob/master/types/index.d.ts

  • jsPDF 无法处理复杂的布局和样式。仅适用于简单页面。 (2认同)

Bap*_*els 9

如上所述,您应该使用jsPDFhtml2canvas.我还在jsPDF的问题中找到了一个函数,它将你的pdf自动拆分成多个页面(源代码)

function makePDF() {

    var quotes = document.getElementById('container-fluid');

    html2canvas(quotes, {
        onrendered: function(canvas) {

        //! MAKE YOUR PDF
        var pdf = new jsPDF('p', 'pt', 'letter');

        for (var i = 0; i <= quotes.clientHeight/980; i++) {
            //! This is all just html2canvas stuff
            var srcImg  = canvas;
            var sX      = 0;
            var sY      = 980*i; // start 980 pixels down for every new page
            var sWidth  = 900;
            var sHeight = 980;
            var dX      = 0;
            var dY      = 0;
            var dWidth  = 900;
            var dHeight = 980;

            window.onePageCanvas = document.createElement("canvas");
            onePageCanvas.setAttribute('width', 900);
            onePageCanvas.setAttribute('height', 980);
            var ctx = onePageCanvas.getContext('2d');
            // details on this usage of this function: 
            // https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Using_images#Slicing
            ctx.drawImage(srcImg,sX,sY,sWidth,sHeight,dX,dY,dWidth,dHeight);

            // document.body.appendChild(canvas);
            var canvasDataURL = onePageCanvas.toDataURL("image/png", 1.0);

            var width         = onePageCanvas.width;
            var height        = onePageCanvas.clientHeight;

            //! If we're on anything other than the first page,
            // add another page
            if (i > 0) {
                pdf.addPage(612, 791); //8.5" x 11" in pts (in*72)
            }
            //! now we declare that we're working on that page
            pdf.setPage(i+1);
            //! now we add content to that page!
            pdf.addImage(canvasDataURL, 'PNG', 20, 40, (width*.62), (height*.62));

        }
        //! after the for loop is finished running, we save the pdf.
        pdf.save('test.pdf');
    }
  });
}
Run Code Online (Sandbox Code Playgroud)

  • 这并不能真正制作出好的矢量pdf,它使用画布制作了很多位图,并将它们堆叠为图像。结果有很多缺点 - 大,质量低,无法从 PDF 复制和粘贴等... (4认同)
  • 它不会转换图像 (3认同)
  • 感谢您的回答,您能提供有关如何将其转换为 A4 页面格式的任何提示吗? (2认同)

gha*_*eri 9

我使用jspdfhtml2canvas进行 css 渲染,并导出特定 div 的内容,因为这是我的代码

$(document).ready(function () {
    let btn=$('#c-oreder-preview');
    btn.text('download');
    btn.on('click',()=> {

        $('#c-invoice').modal('show');
        setTimeout(function () {
            html2canvas(document.querySelector("#c-print")).then(canvas => {
                //$("#previewBeforeDownload").html(canvas);
                var imgData = canvas.toDataURL("image/jpeg",1);
                var pdf = new jsPDF("p", "mm", "a4");
                var pageWidth = pdf.internal.pageSize.getWidth();
                var pageHeight = pdf.internal.pageSize.getHeight();
                var imageWidth = canvas.width;
                var imageHeight = canvas.height;

                var ratio = imageWidth/imageHeight >= pageWidth/pageHeight ? pageWidth/imageWidth : pageHeight/imageHeight;
                //pdf = new jsPDF(this.state.orientation, undefined, format);
                pdf.addImage(imgData, 'JPEG', 0, 0, imageWidth * ratio, imageHeight * ratio);
                pdf.save("invoice.pdf");
                //$("#previewBeforeDownload").hide();
                $('#c-invoice').modal('hide');
            });
        },500);

        });
});
Run Code Online (Sandbox Code Playgroud)

  • 这有效,但它将内容转换为图像 (5认同)
  • 另外,如何设置分页符,以便内容/图像在不适合当前页面时打印在新页面上? (2认同)

小智 7

如果您需要特定页面的可下载pdf文件,只需添加这样的按钮

<h4 onclick="window.print();"> Print </h4>
Run Code Online (Sandbox Code Playgroud)

使用window.print()打印所有页面而不仅仅是div

  • 只是一个简单的补充,如果你想创建一个 iframe 的可下载 pdf,那么使用开发者控制台:`document.querySelector("#myIframe").contentWindow.print()` (3认同)

ilm*_*acs 5

使用pdfMake.js这个 Gist

(我在这里找到了 Gist以及指向html-to-pdfmake包的链接,我现在最终没有使用它。)

npm install pdfmake保存 Gist 之后,htmlToPdf.js我像这样使用它:

const pdfMakeX = require('pdfmake/build/pdfmake.js');
const pdfFontsX = require('pdfmake-unicode/dist/pdfmake-unicode.js');
pdfMakeX.vfs = pdfFontsX.pdfMake.vfs;
import * as pdfMake from 'pdfmake/build/pdfmake';
import htmlToPdf from './htmlToPdf.js';

var docDef = htmlToPdf(`<b>Sample</b>`);
pdfMake.createPdf({content:docDef}).download('sample.pdf');
Run Code Online (Sandbox Code Playgroud)

评论:

  • 我的用例是从 markdown 文档(使用markdown-it)创建相关的 html ,然后生成 pdf,并上传其二进制内容(我可以使用pdfMakegetBuffer()函数获取),所有这些都来自浏览器。与我尝试过的其他解决方案相比,生成的 pdf 对这种 html 来说更好。
  • 我对jsPDF.fromHTML()接受的答案中建议的结果不满意,因为该解决方案很容易被我的 HTML 中的特殊字符混淆,这些特殊字符显然被解释为一种标记并完全弄乱了生成的 PDF。
  • 使用基于画布的解决方案(如已弃用的jsPDF.from_html()功能,不要与接受的答案混淆)对我来说不是一种选择,因为我希望生成的 PDF 中的文本是可粘贴的,而基于画布的解决方案生成基于位图的 PDF。
  • 直接降价到 pdf 转换器(如md-to-pdf)仅是服务器端,对我不起作用。
  • 使用浏览器的打印功能对我不起作用,因为我不想显示生成的 PDF 而是上传其二进制内容。