Original link: https://www.zhangxinxu.com/wordpress/2023/06/js-canvas-jspdf-export-pdf/
by zhangxinxu from https://www.zhangxinxu.com/wordpress/?p=10854 Xin space – Xin life
This article is welcome to share and aggregate, there is no need to reprint the full text, respect copyright, the circle is so big, if you need it urgently, you can contact for authorization.
1. Introduction to jsPDF project
Recently, I encountered a need to convert specific content in a webpage to PDF, so I had the opportunity to try jsPDF, and encountered some problems, so I will share it with you here.
First, the project address: https://github.com/parallax/jsPDF
At present, the number of stars is 26,000, which can be said to be the Top-level project on Github, and it is also the preferred solution for Web to PDF import.
The official usage instructions are also very simple, structure, content and maintenance.
import { jsPDF } from "jspdf"; // The default is a4 paper size, vertical, and the unit is mm const doc = new jsPDF(); doc. text("Hello world!", 10, 10); doc.save("a4.pdf");
Well, the next step is to use the DOM content in the page as PDF content. This is the case in theory, but the actual use is thought-provoking.
2. The built-in html() method is amazing
jsPDF has a built-in method called html(), which can directly make HTML elements the content of PDF.
var doc = new jsPDF(); // element is the DOM element that needs to be converted to pdf doc.html(element, { callback: function (doc) { doc. save(); }, x: 10, y: 10 });
I finally tried it, and the effect was amazing.
After some research, I found that it is necessary to import complete Chinese fonts, and a Chinese font, at least 5-6M, is not suitable in the scene of the Web.
So I changed the strategy and decided to use html2canvas to convert the content into pictures, and then insert them into PDF page by page in the form of pictures. The implementation cost will be much lower, but the disadvantage is that the text cannot be selected.
3. With the help of html2canvas
The html2canvas project has also been mentioned before, with nearly 30,000 Stars, and it is also the top open source project on Github.
Project address: https://github.com/niklasvh/html2canvas
Minimalist usage instructions
Here is a quick demonstration of how to use html2canvas and jsPDF to generate PDF files.
Suppose there is a layout element on the page, and the id attribute value is element, the effect is shown in the following figure:
Then the following piece of code can make this layout content into a PDF file and download it.
<script src="./html2canvas.min.js"></script> <script src="./jspdf.umd.min.js"></script> <script> var pdf = new jspdf.jsPDF(); html2canvas(target). then(function(canvas) { pdf.addImage(canvas.toDataURL('image/jpeg'), 10, 10); pdf.save('mybook.pdf'); }); </script>
The PDF opening effect at this time is like this:
Seeing is believing, you can click here hard: html2canvs and jspdf generate PDF simple demo
However, real project development is much more troublesome than the demo page.
The problem that almost most people will encounter is how to break pages in PDF if the image generated by html2canvas is too long.
And, if the picture is cross-domain, how to deal with it?
Fourth, the problem of cross-domain and pagination of pictures
The problem of cross-domain between canvas and pictures, I have written a special article to explain it before, the portal address: ” Solve the cross-domain problem of getImageData and toDataURL of canvas pictures “
Although there are many methods in it, according to my many years of practice, it is best to use the server to set Access-Control-Allow-Origin
to run access, and the front-end fetch or XMLHttpRequest to obtain image data.
Take fetch as an example, if you want to get an image data whose image address is imgUrl, you can do this:
fetch(imgUrl).then(res => res.blob()).then(blob => { var reader = new FileReader() ; reader.onload = function () { // this.result is the base64 address of the image}; reader.readAsDataURL(blob); })
Why do you need to convert to base64? Because according to my practice, the image address in the html2canvas content needs to be converted into a base64 address to be truly usable.
pagination problem
The problem of pagination can be solved with code, set the height of each PDF page, and then cut off the height of the canvas page by page and add them separately.
Here are some things you need to know in advance about size.
When we construct a pdf instance, if no parameters are set, its size is the size of A4 paper, 210mm×297mm.
According to my practice, this size is a bit small, the content of the generated PDF is blurred, and the reading experience is extremely poor.
Therefore, it is necessary to customize the PDF size, that is, to set it larger. Although the large size makes the PDF file take up a lot of space, but now is the era of large-screen high-definition, and the traffic is not worth the money. This problem is not worth mentioning.
Specifically, here’s how I handle it.
First determine the width of the container element on the page. Assuming it is 700px, the size of the PDF can be set to double, which is 1400px, and the aspect ratio of the vertical version of the PDF is root number two, which is 1.414, so the height of the PDF is 1400*1.414=1979.6 pixels.
At this point, combined with simple mathematical calculations, we can separate the canvas image into pages and stuff them in the PDF.
For the specific code, please refer to the code diagram of the package in the next section.
Five, the method of packaging
In order to facilitate students who encounter similar needs, they can quickly complete the corresponding development.
I encapsulated the above image processing and paging into a reusable method, the code is as follows (support jspdf and html2canvas src direct connection and npm install installation two forms):
// Export pdf packaging method // by zhangxinxu(.com) // Visit https://www.zhangxinxu.com/wordpress/?p=10854 for updated information export async function exportPdf (element, filename = 'unnamed', callback = () => {}) { if (!element) { callback(); return; } // Size determination const originWidth = element.offsetWidth || 700; // Create a container for cloning elements const container = document.createElement('div'); // 16px is for safe margins in the generated PDF container.style.cssText = `position:fixed;left: ${-2 * originWidth}px; top:0;padding:16px;width:${originWidth}px; box-sizing: content-box;`; // Insert into body document.body.appendChild(container); // clone element container.appendChild(element.cloneNode(true)); // Dependent library var jsPDF; if (typeof html2canvas == 'undefined') { html2canvas = await import('html2canvas').then(module => module.default); } if (typeof jspdf == 'undefined') { jsPDF = await import('jspdf').then(module => module.jsPDF); } else { jsPDF = jspdf.jsPDF; } // In order to ensure the display quality, 2 times the PDF size const scale = 2; const width = originWidth + 32; const PDF_WIDTH = width * scale; const PDF_HEIGHT = width * 1.414 * scale; // rendering method const render = function () { // Render as image and download html2canvas(container, { scale: scale }).then(function(canvas) { const contentWidth = canvas. width; const contentHeight = canvas. height; // A page of pdf displays the canvas height generated by the html page const pageHeight = contentWidth / PDF_WIDTH * PDF_HEIGHT; // The size of the canvas image on the canvas const imgWidth = PDF_WIDTH; const imgHeight = PDF_WIDTH / contentWidth * contentHeight; let leftHeight = contentHeight; let position = 0; const doc = new jsPDF('p', 'px', [PDF_WIDTH, PDF_HEIGHT]); // Less than one page if (leftHeight < pageHeight) { doc.addImage(canvas, 'PNG', 0, 0, imgWidth, imgHeight); } else { // multiple pages while (leftHeight > 0) { doc.addImage(canvas, 'PNG', 0, position, imgWidth, imgHeight) leftHeight -= pageHeight; position -= PDF_HEIGHT; // Avoid adding blank pages if (leftHeight > 0) { doc. addPage(); } } } doc.save(filename + '.pdf'); // Remove the created element container.remove(); // Hide the global loading prompt callback(); }); } // Replace image address with base64 address const eleImgs = container.querySelectorAll('img'); const length = eleImgs. length; let start = 0; container.querySelectorAll('img').forEach(ele => { let src = ele.src; if (!src) { return; } // Event handling, must succeed or fail ele.onload = function () { if (!/^http/.test(ele.src)) { start++; if (start == length) { render(); } } }; // Request image and convert to base64 address fetch(src).then(res => res.blob()).then(blob => { var reader = new FileReader() ; reader.onload = function () { ele.src = this.result; }; reader.readAsDataURL(blob); }).catch(() => { // request exception handling start++; if (start == length) { render(); } }); }); }
It can automatically base64 the image in the container element, and at the same time distribute the content on each page of the PDF and download it.
Real knowledge comes from practice
In order to verify the effect of the encapsulation method, I specially made a demo page.
//zxx: The demo page uses direct calls
You can click here hard: exportPdf encapsulation method and cross-domain image PDF export demo
You can click the “PDF Generation” button as shown in the figure below. After a few turns of the chrysanthemum, you can see the PDF download prompt (depending on your browser settings, it may also be saved directly to the local).
The picture below is a thumbnail of the generated PDF file. You can see that the picture and layout are completely in line with expectations.
JS code reference for calling this block:
<script src="./html2canvas.min.js"></script> <script src="./jspdf.umd.min.js"></script> <script type="module"> import { exportPdf } from './exportPdf.js'; // Click the button to execute PDF export button.addEventListener('click', () => { const article = document. querySelector('article'); // display loading button.loading = true; // Since the export of PDF is asynchronous, it is necessary to hide the loading after the export is complete exportPdf(article, 'The final chapter of the Great Northern Labyrinth', () => { button.loading = false; }); }); </script>
A lot of processing details are missing, so it is much simpler to implement.
6. Rendering problem of cross-line inline background color
In actual use, more difficult problems have been encountered.
It is an inline element. If there is a background color and the background color is wrapped, this part of the color block in the generated PDF will cover part of the content, resulting in an exception.
For example the page rendering looks like this:
The PDF effect is like this:
I checked the issues of html2canvas, and there were many similar feedbacks, but none of them were processed.
According to my understanding of the underlying implementation of html2canvas, this problem is really not easy to deal with.
However, that doesn’t mean there is no way.
The whole block of inline elements can be separated into independent inline elements one by one, so that they can be rendered normally.
That is, this structure:
<span class="bgcolor">CSS New World</span>
It is converted into this (actual development cannot wrap, here is deliberately processed for the convenience of everyone):
<span> <span class="bgcolor">C</span> <span class="bgcolor">S</span> <span class="bgcolor">S</span> <span class="bgcolor">New</span> <span class="bgcolor">World</span> <span class="bgcolor">Session</span> </span>
Let’s see the final effect.
You can click here hard: Solve the color block problem of html2canvas span inline background demo
Click the button below, and JS will process the original DOM structure (actual development can clone the element before processing to avoid potential risks caused by changes in the DOM structure):
At this point, the generated PDF effect is consistent with the original layout style effect:
Seven, other points
This article indicates that the JS files used in the page are all directly connected by script.
The actual development is mostly through the front-end framework.
Since these two JS are JS with relatively large volume, they can be realized by dynamic loading .
import('html2canvas').then(module => module.default).then(...) import('jspdf').then(module => module.jsPDF).then(...)
Looking forward to the stars and looking forward to the moon
The signed version of “CSS Selector World 2nd Edition” is also available for purchase. Open Taobao on your mobile phone and scan the code in the lower left corner of the picture below.
//zxx: Free shipping. In addition, I have three Geek Time 14-day free study cards here. Those who buy first will be given priority.
OK, there is nothing else to say. I hope the content of this article can help friends who encounter similar needs.
This article is an original article, welcome to share, do not reprint the full text, if you really like it, you can bookmark it, it will never expire, and will update knowledge points and correct errors in time, and the reading experience will be better.
Address of this article: https://www.zhangxinxu.com/wordpress/?p=10854
(End of this article)
This article is transferred from: https://www.zhangxinxu.com/wordpress/2023/06/js-canvas-jspdf-export-pdf/
This site is only for collection, and the copyright belongs to the original author.