Generating a PDF file from React Components
Asked Answered
M

13

111

I have been building a polling application. People are able to create their polls and get data regarding the question(s) they ask. I would like to add the functionality to let the users download the results in the form of a PDF.

For example I have two components which are responsible for grabbing the question and data.

<QuestionBox />
<ViewCharts />

I'm attempting to output both components into a PDF file. The user can then download this PFD file. I have found a few packages that permit the rendering of a PDF inside a component. However I failed to find one that can generate PDF from an input stream consisting of a virtual DOM. If I want to achieve this from scratch what approach should I follow ?

Mixologist answered 8/7, 2017 at 17:48 Comment(3)
Based on this question and the answers, I wrote two brief tutorials about this topic: React Component to PDF & React Component to ImageWhilst
@RobinWieruch Great article, but what if the component is big enough it doesn't fit on one page? I have just followed your tutorial , yet if there is a moderate amount of content it doesn't show all of it . Do you maybe know how to make a 'break' and start a new page ? Thanks !Desolate
@PeterMalik maybe you could try landscape mode. An alternative could be triggering a CSS media query before generating the image, so that the media query brings the content into a more suitable shape for the PDF.Whilst
P
161

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.

Padraig answered 10/7, 2017 at 16:27 Comment(12)
I have successfully created a PDF file from an html page with the help of your answer. But if we want to create a PDF with multiple pages then what to do? with a4 size page with appropriate margins at all sides and in each new page that margin should be applied. please help me its argent. Thank you in advance. I have created a question here questionOxonian
Same thing. Position your content in a list of divs. Each div represents an a4 sheet (add css to the div to replicate margins). Then run the print function on each div.Padraig
@ShivekKhurana my div changes acording to the size of the screen, but I'd like the PDF file to be always the same, not depending on window size. Is there anyway I can do that? Also, how do i import {...css} ?Unknowing
By this method can the text on created pdf be copied ?Ogpu
@Ogpu the answer is no: the content of the PDF is a bitmap imageCorina
This method is not working if included an image in the div. any solution text along with image?Gallinule
This works, but the PDF that is generated appears massively zoomed in to the top-left corner. Any ideas why this might be the case?Ideograph
@Matt maybe try setting the dimensions of the parent div in millimeters.Padraig
@ShivekKhurana I'm not sure what you mean. Could this be because I'm on a Mac with a retina screen? Screenshots are always really big because of the resolution...Ideograph
You can try this codesandbox that I have created to generate a PDF file from React component: codesandbox.io/s/20qpkznx3pParang
super easy and clean solution! thanx @ShivekKhurana :) (btw i think addImage accepts either 1/8 arguments.)Caulis
@ShivekKhurana how to use above code in react typescript project?Leatherleaf
M
16

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.

Mikael answered 29/4, 2020 at 21:44 Comment(1)
For the record, react-pdf and @react-pdf/renderer are not the same thingEjective
M
12

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

Midday answered 7/12, 2020 at 5:4 Comment(4)
Is the link and link.download necessary?Dorcas
not needed thoughJoesphjoete
this worked for me very easilyKennith
It is not accommodating full page, crops from the bottom.Faucet
P
11

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>
Paige answered 1/4, 2019 at 23:24 Comment(3)
Why did you add a copy of the answer?Interactive
The image quality isn't good, and with a4 side part of the generated Pdf is cut, this result is the same with both react-to-pdf and jdpdf libraryGriskin
The only problem with this solution is that it will capture the whole image of the div but will generate pdf with part of that - not the complete. specially when the div is too wide. easy fix is to replace two line. one is the jspdf constructor line const pdf = new jsPDF('p', 'pt', 'a4', false); and second will be pasting image on pdf line pdf.addImage(imgData, 'PNG', 0, 0, 600, 0, undefined, false);. if you want to adjust the width or height of captured image in pdf then play with 5th parameter of addImage function. Go NUTS :PShing
D
7
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
Dominations answered 23/3, 2021 at 3:0 Comment(1)
This is only capture one page if the component has more than one page!!Utopia
U
7

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>
  );
};
Unjaundiced answered 21/7, 2022 at 16:16 Comment(1)
This does not download the component as PDF. It displays a PDF preview for it.Duteous
C
5

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.

Clockwork answered 23/8, 2018 at 19:35 Comment(1)
Looked interesting, but renderToStaticMarkup doesnt return any styling, so as far as I can tell jsPDF simply renders some default layout of the raw html unless there is some way to inject css classes?Brimful
L
3

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();
});
Lewendal answered 21/6, 2018 at 14:43 Comment(0)
G
3

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.

Gilroy answered 18/10, 2022 at 20:33 Comment(2)
can you give a small example on how to use it?Candleberry
sorry for the late reply, example is linked to github repo gh repo gh pagesCalamine
G
2
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")
Goon answered 2/11, 2018 at 7:19 Comment(1)
@Felix the HTML string allows for <style> tags so you'd have to write CSS inline.Westcott
R
1

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

Rambert answered 10/11, 2020 at 4:37 Comment(0)
M
0

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.

Mclemore answered 7/10, 2021 at 1:39 Comment(1)
Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.Bushcraft
B
-2

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.

Brookins answered 4/4, 2019 at 13:23 Comment(3)
This only allows displaying of PDFs not converting DOM to PDFs? Correct me if i'm wrong pleaseFrangos
this doesn't convert it in pdf for download. Its just a viewer.Grigsby
ReactPDF is a viewer, it allows displaying PDF on DOM.Rambert

© 2022 - 2024 — McMap. All rights reserved.