【问题标题】:Generating a PDF file from React Components从 React 组件生成 PDF 文件
【发布时间】:2017-12-12 20:01:55
【问题描述】:

我一直在构建一个投票应用程序。人们能够创建他们的民意调查并获取有关他们提出的问题的数据。我想添加让用户以 PDF 格式下载结果的功能。

例如,我有两个组件负责抓取问题和数据。

<QuestionBox />
<ViewCharts />

我正在尝试将这两个组件都输出到 PDF 文件中。然后,用户可以下载此 PFD 文件。我发现了一些允许在组件内呈现 PDF 的包。但是,我找不到可以从包含虚拟 DOM 的输入流中生成 PDF 的工具。如果我想从头开始实现这一目标,我应该遵循什么方法?

【问题讨论】:

  • 基于这个问题和答案,我写了两个关于这个主题的简短教程:React Component to PDF & React Component to Image
  • @RobinWieruch 很棒的文章,但是如果组件足够大,一页都放不下怎么办?我刚刚按照您的教程进行操作,但如果内容数量适中,它不会显示所有内容。您是否知道如何“休息”并开始新的一页?谢谢!
  • @PeterMalik 也许你可以试试横向模式。另一种方法是在生成图像之前触发 CSS 媒体查询,以便媒体查询将内容带入更适合 PDF 的形状。

标签: reactjs pdf meteor


【解决方案1】:

您也可以尝试“React on the fly pdf”,它将从 HTML 生成 pdf。这个插件在后台使用 html2canvas 和 jspdf 来生成 pdf 文档。该插件支持多页、页眉和页脚。我想警告你,它不会生成“真正的”pdf。意思是真正的pdf应该是矢量。我想不出任何可以生成真正 pdf 的 Javascript 插件。您需要使用其他一些工具来做到这一点。

【讨论】:

  • 您的答案可以通过额外的支持信息得到改进。请edit 添加更多详细信息,例如引用或文档,以便其他人可以确认您的答案是正确的。你可以找到更多关于如何写好答案的信息in the help center
【解决方案2】:
my opinion : 

import { useRef } from "react";
import { jsPDF } from "jspdf";
import html2canvas from "html2canvas";

import "./styles.css";

const App = () => {
  const inputRef = useRef(null);
  const printDocument = () => {
    html2canvas(inputRef.current).then((canvas) => {
      const imgData = canvas.toDataURL("image/png");
      const pdf = new jsPDF();
      pdf.addImage(imgData, "JPEG", 0, 0);
      pdf.save("download.pdf");
    });
  };
  return (
    <>
      <div className="App">
        <div className="mb5">
          <button onClick={printDocument}>Print</button>
        </div>
        <div id="divToPrint" ref={inputRef}>
          <div>Note: Here the dimensions of div are same as A4</div>
          <div>You Can add any component here</div>
        </div>
      </div>
    </>
  );
};
export default App;

link demo:

https://codesandbox.io/s/frosty-sea-44b4q?file=/src/App.js:0-907

【讨论】:

    【解决方案3】:

    您可以使用 ReactDOMServer 将您的组件呈现为 HTML,然后在 jsPDF 上使用它。

    首先进行导入:

    import React from "react";
    import ReactDOMServer from "react-dom/server";
    import jsPDF from 'jspdf';
    

    然后:

    var doc = new jsPDF();
    doc.fromHTML(ReactDOMServer.renderToStaticMarkup(this.render()));
    doc.save("myDocument.pdf");
    

    喜欢使用:

    renderToStaticMarkup

    代替:

    renderToString

    因为前者包含了 react 所依赖的 HTML 代码。

    【讨论】:

    • 看起来很有趣,但 renderToStaticMarkup 不返回任何样式,据我所知,jsPDF 只是呈现原始 html 的一些默认布局,除非有某种方法可以注入 css 类?
    【解决方案4】:

    只有几步。我们可以从我们的 HTML 页面下载或生成 PDF,也可以从 HTML 页面生成特定 div 的 PDF。

    步骤:HTML -> 图像(PNG 或 JPEG)-> PDF

    请按照以下步骤,

    第 1 步:-

    npm install --save html-to-image
    npm install jspdf --save
    

    第 2 步:-

    /* ES6 */
    import * as htmlToImage from 'html-to-image';
    import { toPng, toJpeg, toBlob, toPixelData, toSvg } from 'html-to-image';
     
    /* ES5 */
    var htmlToImage = require('html-to-image');
    
    -------------------------
    import { jsPDF } from "jspdf";
    

    第 3 步:-

       ******  With out PDF properties given below  ******
    
     htmlToImage.toPng(document.getElementById('myPage'), { quality: 0.95 })
            .then(function (dataUrl) {
              var link = document.createElement('a');
              link.download = 'my-image-name.jpeg';
              const pdf = new jsPDF();          
              pdf.addImage(dataUrl, 'PNG', 0, 0);
              pdf.save("download.pdf"); 
            });
    
    
        ******  With PDF properties given below ******
    
        htmlToImage.toPng(document.getElementById('myPage'), { quality: 0.95 })
            .then(function (dataUrl) {
              var link = document.createElement('a');
              link.download = 'my-image-name.jpeg';
              const pdf = new jsPDF();
              const imgProps= pdf.getImageProperties(dataUrl);
              const pdfWidth = pdf.internal.pageSize.getWidth();
              const pdfHeight = (imgProps.height * pdfWidth) / imgProps.width;
              pdf.addImage(dataUrl, 'PNG', 0, 0,pdfWidth, pdfHeight);
              pdf.save("download.pdf"); 
            });
    

    我认为这很有帮助。请尝试

    【讨论】:

    • 链接和link.download有必要吗?
    【解决方案5】:

    我使用了 jsPDF 和 html-to-image。

    你可以在下面的 git repo 上查看代码。

    Link

    如果你喜欢,你可以在里面打一颗星✌️

    【讨论】:

      【解决方案6】:

      React-PDF 是一个很好的资源。

      将您的标记和 CSS 转换为 React-PDF 的格式有点费时,但它很容易理解。导出 PDF 并从中导出非常简单。

      要允许用户下载由 react-PDF 生成的 PDF,请使用他们的 on the fly rendering,它提供了可自定义的下载链接。点击后,网站会为用户呈现并下载 PDF。

      这是他们的REPL,它将让您熟悉所需的标记和样式。他们也有 PDF 的下载链接,但这里没有显示代码。

      【讨论】:

        【解决方案7】:

        您可以使用ReactPDF

        让您轻松将 div 转换为 PDF。您需要匹配现有标记才能使用 ReactPDF 标记,但这是值得的。

        【讨论】:

        • 这仅允许显示不将 DOM 转换为 PDF 的 PDF?如果我错了请纠正我
        • 这不会将其转换为 pdf 以供下载。它只是一个查看器。
        • ReactPDF 是一个查看器,它允许在 DOM 上显示 PDF。
        【解决方案8】:

        您可以使用 jsPDF 来使用画布

        import jsPDF from 'jspdf';
        import html2canvas from 'html2canvas';
        
         _exportPdf = () => {
        
             html2canvas(document.querySelector("#capture")).then(canvas => {
                document.body.appendChild(canvas);  // if you want see your screenshot in body.
                const imgData = canvas.toDataURL('image/png');
                const pdf = new jsPDF();
                pdf.addImage(imgData, 'PNG', 0, 0);
                pdf.save("download.pdf"); 
            });
        
         }
        

        而你 div 的 id 捕获是:

        示例

        <div id="capture">
          <p>Hello in my life</p>
          <span>How can hellp you</span>
        </div>
        

        【讨论】:

        • 为什么要添加答案的副本?
        • 画质不好,生成的pdf的a4边部分被剪掉了,react-to-pdf和jdpdf库这个结果都是一样的
        • 这个解决方案的唯一问题是它会捕获 div 的整个图像,但会生成 pdf 的一部分——而不是完整的。特别是当 div 太宽时。简单的解决方法是更换两条线。一个是 jspdf 构造函数行 const pdf = new jsPDF('p', 'pt', 'a4', false);,第二个是在 pdf 行 pdf.addImage(imgData, 'PNG', 0, 0, 600, 0, undefined, false); 上粘贴图像。如果您想调整 pdf 中捕获图像的宽度或高度,请使用 addImage 函数的第 5 个参数。去坚果:P
        【解决方案9】:
        npm install jspdf --save
        

        //反应代码

        import jsPDF from 'jspdf';
        
        
        var doc = new jsPDF()
        
        
         doc.fromHTML("<div>JOmin</div>", 1, 1)
        
        
        onclick //
        
         doc.save("name.pdf")
        

        【讨论】:

        • @Felix HTML 字符串允许使用&lt;style&gt; 标签,因此您必须内联编写 CSS。
        【解决方案10】:

        这可能是也可能不是次优的处理方式,但我发现的多页问题最简单的解决方案是确保在调用 jsPDFObj.save 方法之前完成所有渲染。

        至于渲染隐藏文章,这通过与css图像文本替换类似的修复来解决,我将要渲染的元素绝对定位在页面左侧-9999px, 这不会影响布局并允许元素对 html2pdf 可见,尤其是在使用依赖于{display: none} 的选项卡、手风琴和其他 UI 组件时。

        此方法将先决条件包装在一个 Promise 中,并在 finally() 方法中调用 pdf.save()。我不能确定这是万无一失的还是反模式,但它似乎在我提出的大多数情况下都有效。

        // Get List of paged elements.
        let elems = document.querySelectorAll('.elemClass');
        let pdf = new jsPDF("portrait", "mm", "a4");
        
        // Fix Graphics Output by scaling PDF and html2canvas output to 2
        pdf.scaleFactor = 2;
        
        // Create a new promise with the loop body
        let addPages = new Promise((resolve,reject)=>{
          elems.forEach((elem, idx) => {
            // Scaling fix set scale to 2
            html2canvas(elem, {scale: "2"})
              .then(canvas =>{
                if(idx < elems.length - 1){
                  pdf.addImage(canvas.toDataURL("image/png"), 0, 0, 210, 297);
                  pdf.addPage();
                } else {
                  pdf.addImage(canvas.toDataURL("image/png"), 0, 0, 210, 297);
                  console.log("Reached last page, completing");
                }
          })
          
          setTimeout(resolve, 100, "Timeout adding page #" + idx);
        })
        
        addPages.finally(()=>{
           console.log("Saving PDF");
           pdf.save();
        });

        【讨论】:

          【解决方案11】:

          将 react 渲染为 pdf 通常很痛苦,但有一种方法可以使用画布。

          这个想法是转换: HTML -> 画布 -> PNG(或 JPEG) -> PDF

          要实现上述目标,您需要:

          1. html2canvas &
          2. jsPDF

          import React, {Component, PropTypes} from 'react';
          
          // download html2canvas and jsPDF and save the files in app/ext, or somewhere else
          // the built versions are directly consumable
          // import {html2canvas, jsPDF} from 'app/ext';
          
          
          export default class Export extends Component {
            constructor(props) {
              super(props);
            }
          
            printDocument() {
              const input = document.getElementById('divToPrint');
              html2canvas(input)
                .then((canvas) => {
                  const imgData = canvas.toDataURL('image/png');
                  const pdf = new jsPDF();
                  pdf.addImage(imgData, 'JPEG', 0, 0);
                  // pdf.output('dataurlnewwindow');
                  pdf.save("download.pdf");
                })
              ;
            }
          
            render() {
              return (<div>
                <div className="mb5">
                  <button onClick={this.printDocument}>Print</button>
                </div>
                <div id="divToPrint" className="mt4" {...css({
                  backgroundColor: '#f5f5f5',
                  width: '210mm',
                  minHeight: '297mm',
                  marginLeft: 'auto',
                  marginRight: 'auto'
                })}>
                  <div>Note: Here the dimensions of div are same as A4</div> 
                  <div>You Can add any component here</div>
                </div>
              </div>);
            }
          }

          sn-p 在这里不起作用,因为没有导入所需的文件。

          this answer 中使用了另一种方法,其中中间步骤被删除,您可以简单地从 HTML 转换为 PDF。 jsPDF文档中也有这样做的选项,但是个人观察,我觉得先将dom转成png的时候精度更高。

          更新 0:2018 年 9 月 14 日

          通过这种方法创建的 pdf 上的文本将无法选择。如果这是一项要求,您可能会发现 this article 很有帮助。

          【讨论】:

          • 在您的回答的帮助下,我已经成功地从一个 html 页面创建了一个 PDF 文件。但是,如果我们想创建一个包含多个页面的 PDF,那么该怎么做呢?使用 a4 大小的页面,所有边都有适当的边距,并且在每个新页面中都应该应用边距。请帮我看看它的银色。先感谢您。我在这里创建了一个问题question
          • 同样的事情。将您的内容放置在 div 列表中。每个 div 代表一个 a4 表(将 css 添加到 div 以复制边距)。然后在每个 div 上运行打印功能。
          • @ShivekKhurana 我的 div 会根据屏幕大小而变化,但我希望 PDF 文件始终相同,而不是取决于窗口大小。反正我能做到吗?另外,我如何导入 {...css} ?
          • 通过这种方法可以复制创建的pdf上的文字吗?
          • @Goutham 答案是否定的:PDF 的内容是位图图像
          猜你喜欢
          • 2023-01-26
          • 2014-09-29
          • 2010-12-18
          • 2017-12-12
          • 2015-08-31
          • 1970-01-01
          • 2010-10-10
          • 1970-01-01
          • 2019-05-24
          相关资源
          最近更新 更多