使用 html2canvas 和 jspdf 在 React 中从 Html 生成 pdf

IT技术 reactjs jspdf html2canvas
2021-04-30 17:21:16

我已经在 React 中从 html2canvas 和 jspdf 成功生成了 pdf。但问题是 pdf 中渲染的图像被拉伸。请让我知道做这种工作的最佳选择是什么。是否有可能在不使用 html2canvas 的情况下将 HTML 直接呈现为 pdf?下面是我的截图 输出截图pdf的截图

下面是我的代码

import React, { Component } from 'react';
import * as jsPDF from 'jspdf';
import * as html2canvas from 'html2canvas';


class Invoice extends Component {

handlePdf = () => {
    const input = document.getElementById('page');

    html2canvas(input)
        .then((canvas) => {
            const imgData = canvas.toDataURL('image/png');
            const pdf = new jsPDF('p', 'px', 'a4');
            var width = pdf.internal.pageSize.getWidth();
            var height = pdf.internal.pageSize.getHeight();

            pdf.addImage(imgData, 'JPEG', 0, 0, width, height);
            pdf.save("test.pdf");
        });
};

render() {

    const hrStyle = {
        border: '5px solid rgb(23, 162, 184)'
    };

    const subtotal = [0, ...this.props.inputs.totals];
    const add = (a, b) => a + b;
    const sum = subtotal.reduce(add);

    const tax = sum * 0.1;
    const total = sum + tax;

    return (
        <React.Fragment>
            <div className="col-12 col-lg-6" id="page">
                <div className="container-fluid bg-info text-white">
                    <div className="row">
                        <div className="col text-left m-2">
                            <p>Your Company Name</p>
                            <h2>Invoice</h2>
                        </div>
                        <div className="col text-right">
                            <p>22 Yusen St</p><br />
                            <p>borburn</p><br />
                            <p>WSN</p>
                        </div>
                    </div>
                </div>
                <div className="container-fluid">
                    <div className="row">
                        <div className="col-4">
                            <h5>Billed To</h5>
                            <p>{this.props.inputs.company}</p>
                            <p>{this.props.inputs.address}</p>
                            <p>{this.props.inputs.zip}</p>
                        </div>
                        <div className="col-4">
                            <div>
                                <h5>Invoive number</h5>
                                <p>Za{Math.floor((Math.random() * 100) + 1)}</p>
                            </div>
                            <div>
                                <h5>Date</h5>
                                <p>{this.props.inputs.date}</p>
                            </div>
                        </div>
                        <div className="col-4">
                            <div>
                                <h5>Invoice Totals</h5>
                                <p>${total}</p>
                            </div>
                        </div>
                    </div>
                </div>
                <hr style={hrStyle} />
                <div className="Invoices">
                    <table className="table">
                        <thead>
                            <tr>
                                <th>Description</th>
                                <th>Unit Price</th>
                                <th>Quantity</th>
                                <th>Total</th>
                            </tr>
                        </thead>
                        <tbody>
                            {this.props.inputs.invoices.map((invoice, index) => {
                                return (
                                    <tr key={index}>
                                        <td>{invoice.description}</td>
                                        <td>{invoice.unit}</td>
                                        <td>{invoice.quantity}</td>
                                        <td>{invoice.quantity * invoice.unit}</td>
                                    </tr>
                                )
                            })
                            }
                        </tbody>
                    </table>
                </div>
                <hr />
                <div className="col-12 text-right" >
                    <h5 className="m-2">
                        Subtotal<span className="m-2">${sum}</span>
                    </h5 >
                    <p>Tax(10%)<span className="m-2">${tax.toFixed(2)}</span></p>
                    <h2>Total<span className="m-2">${total}</span></h2>
                </div>
            </div>
            <button onClick={this.handlePdf} className="btn btn-primary btn-lg mx-auto">Generate PDF</button>

        </React.Fragment >
    );
}
}

export default Invoice;
2个回答

你可以使用优秀的React-pdf库。它具有良好的性能,并具有额外的好处,即如果您打印文本,它将是可搜索的。

如果您需要在 DOM 和 PDF 中呈现页面,我建议您自己创建原语并使用它们而不是 dom 标签(div、p 等)。通过使用这些原语,您将能够在 dom 和 pdf 中渲染相同的组件。

下面是一个 View 原语的例子:

import React from 'react';
import { View as ViewPDF } from '@react-pdf/renderer';
import { RENDERING_CONTEXT, withRenderingContext } from '../RenderingContext';

class View extends React.Component {
  render() {
    const { renderingContext, children, className, style } = this.props;
    const isPdf = renderingContext === RENDERING_CONTEXT.PDF;
    return isPdf
      ? <ViewPDF className={className} style={style}>
          {children}
        </ViewPDF>
      : <div className={className} style {style}>
          {children}
        </div>;
  }
}
export default withRenderingContext(View);

RenderingContext 是一个上下文,您可以在其中存储当前渲染上下文。然后,您只需要使用设置正确上下文的提供程序包装您的 pdf 页面。将根据渲染上下文使用正确的组件(来自 react-pdf 或 dom)。

在您的示例中,您可以简单地用这些原语替换 Invoice 中的 dom 元素。然后,您将能够在 pdf 文件或 dom 中呈现 Invoice 组件。

您仍然可以使用具有 100% 宽度和自动高度的 html2canvas 呈现 jspdf 以避免拉伸。我发现这个解决方案有效。

https://stackoverflow.com/a/55497749/5347308