从反应部件生成 PDF 文件

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

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

<QuestionBox />
<ViewCharts />

我正在尝试将这两个组件输出到 PDF 文件中。然后用户可以下载这个 PFD 文件。我找到了一些允许在组件内呈现 PDF 的包。然而,我没有找到一个可以从由虚拟 DOM 组成的输入流生成 PDF 的方法。如果我想从零开始实现这个目标,我应该采取什么方法?

226326 次浏览

Rendering react as pdf is generally a pain, but there is a way around it using canvas.

The idea is to convert : HTML -> Canvas -> PNG (or JPEG) -> PDF

To achieve the above, you'll need :

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

The snippet will not work here because the required files are not imported.

An alternate approach is being used in this answer, where the middle steps are dropped and you can simply convert from HTML to PDF. There is an option to do this in the jsPDF documentation as well, but from personal observation, I feel that better accuracy is achieved when dom is converted into png first.

Update 0: September 14, 2018

The text on the pdfs created by this approach will not be selectable. If that's a requirement, you might find this article helpful.

This may or may not be a sub-optimal way of doing things, but the simplest solution to the multi-page problem I found was to ensure all rendering is done before calling the jsPDFObj.save method.

As for rendering hidden articles, this is solved with a similar fix to css image text replacement, I position absolutely the element to be rendered -9999px off the page left, this doesn't affect layout and allows for the elem to be visible to html2pdf, especially when using tabs, accordions and other UI components that depend on {display: none}.

This method wraps the prerequisites in a promise and calls pdf.save() in the finally() method. I cannot be sure that this is foolproof, or an anti-pattern, but it would seem that it works in most cases I have thrown at it.

// 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();
});

You can use ReactDOMServer to render your component to HTML and then use this on jsPDF.

First do the imports:

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

then:

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

Prefer to use:

renderToStaticMarkup

instead of:

renderToString

As the former include HTML code that react relies on.

npm install jspdf --save

//code on react

import jsPDF from 'jspdf';




var doc = new jsPDF()




doc.fromHTML("<div>JOmin</div>", 1, 1)




onclick //


doc.save("name.pdf")

you can user canvans with 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");
});


}

and you div with id capture is:

example

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

You can use ReactPDF

Lets you convert a div into PDF with ease. You will need to match your existing markup to use ReactPDF markup, but it is worth it.

React-PDF is a great resource for this.

It is a bit time consuming converting your markup and CSS to React-PDF's format, but it is easy to understand. Exporting a PDF and from it is fairly straightforward.

To allow a user to download a PDF generated by react-PDF, use their on the fly rendering, which provides a customizable download link. When clicked, the site renders and downloads the PDF for the user.

Here's their REPL which will familiarize you with the markup and styling required. They have a download link for the PDF too, but they don't show the code for that here.

I used jsPDF and html-to-image.

You can check out the code on the below git repo.

Link

If you like, you can drop a star there✌️

Only few steps. We can download or generate PDF from our HTML page or we can generate PDF of specific div from a HTML page.

Steps : HTML -> Image (PNG or JPEG) -> PDF

Please Follow the below steps,

Step 1 :-

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

Step 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";

Step 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");
});

I think this is helpful. Please try

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

You can also try "React on the fly pdf" which will generate pdf from HTML. This plugin uses html2canvas and jspdf under the hood to generate the pdf document. This plugin supports multiple pages, header and footer. I like to warn you that it won't generate "real" pdf. Meaning real pdf should be vector. I can't think of any of Javascript plugin which can generate real pdf. You need to use some other tools to do that.

Besides using the html2canvas and jspdf libraries, I found another simple-to-use library called react-to-print for downloading a React component as a PDF file here https://www.npmjs.com/package/react-to-print. The documentation, examples, and demos are amazing. It even has documentation for class-based component and functional-based component users.

Here is a sample of using functional-based component:

import React, { useRef } from 'react';
import { useReactToPrint } from 'react-to-print';


import { ComponentToPrint } from './ComponentToPrint';


const Example = () => {
const componentRef = useRef();
const handlePrint = useReactToPrint({
content: () => componentRef.current,
});


return (
<div>
<ComponentToPrint ref={componentRef} />
<button onClick={handlePrint}>Print this out!</button>
</div>
);
};

You can use my library react-pdf-printer.

This library will help you have control e.g. customizing size of the page, pointing where your website can be splitted into many pages or setting custom header and footer with pagination.

I hope it helps you are somebody else.