+2

Chuyển đổi HTML sang PDF bằng JavaScript kèm ví dụ chi tiết

Trong bài viết này, chúng ta sẽ tìm hiểu cách chuyển đổi một trang HTML sang PDF bằng JavaScript. Điều này là vô cùng quan trọng và hữu ích nếu như bạn đang làm một dự án. Hãy cùng khám phá nhé!

Đối với một dự án, tôi cần chuyển đổi một số HTML sang PDF bằng JavaScript.

Đó là một trang web cơ bản. Bên trong sẽ có một biểu mẫu (form) nằm trong một thẻ div, và tất cả những gì tôi cần làm là tạo một tệp PDF từ div đó và hiển thị nó trong một tab mới. Tất cả đều thực hiện phía client, không cần đến server backend.

Các nhiệm vụ chính ở đây bao gồm:

  • Tạo PDF từ HTML.
  • Hiển thị tệp PDF đã tạo trong một tab mới.

Chuyển đổi HTML sang PDF bằng JavaScript

Phần đầu tiên khá đơn giản – chuyển đổi HTML sang PDF.

Sau một lượt tìm kiếm nhanh trên Google, tôi đã tìm thấy thư viện html2pdf.

Theo tài liệu của nó: "html2pdf.js chuyển đổi bất kỳ trang web hoặc phần tử HTML nào thành một tệp PDF có thể in được, hoàn toàn trên phía client bằng cách sử dụng html2canvas và jsPDF."

Đó chính xác là những gì tôi cần. Có rất nhiều hướng dẫn khác về thư viện này.

Mọi thứ đều hoạt động và tệp PDF được tải xuống thành công. Nhưng tệp PDF lại trống.

html2pdf tạo và trả về PDF trống hoặc rỗng

Điều này khá kỳ lạ. Sau một vài lần tìm kiếm, tôi phát hiện ra rằng có một số vấn đề liên quan đến phiên bản của thư viện.

Tôi đã sử dụng phiên bản 0.9.3 và vấn đề đã được giải quyết.

CDN Link: https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.9.3/html2pdf.bundle.min.js

Phiên bản này đã in toàn bộ nội dung của div.

JavaScript Code:

let element = document.getElementById('div-to-print')

html2pdf().from(element).save();

Đoạn mã này giúp lưu / tải xuống tệp PDF.

Tuy nhiên, tôi không muốn tải xuống, mà tôi cần hiển thị nó trong một tab trình duyệt mới.

Mở PDF trong tab mới thay vì tải xuống – JavaScript – sử dụng Blob

Chúng ta cần tạo một Blob mới từ tệp PDF và tạo một URL mới để hiển thị tệp đó. Khi nghiên cứu vấn đề này, tôi phát hiện rằng bằng cách sử dụng Promise API của html2pdf, chúng ta có thể lấy tệp thay vì tải xuống. Sau đó, chúng ta có thể sử dụng nó để tạo Blob.

Cách thực hiện với một tệp thông thường

const filed = document.querySelector('input[type=file]').files[0];
let file = new Blob([filed], { type: 'application/pdf' });
let fileURL = URL.createObjectURL(file);
window.open(fileURL);

Sau đó, tôi đã thử áp dụng cách này với tệp PDF được tạo từ HTML bằng html2pdf.


async function printHTML() {

let worker = await html2pdf().from(element).toPdf().output('blob').then((data) => {
    console.log(data)
    let fileURL = URL.createObjectURL(data);
    window.open(fileURL);
    })
}

Kết quả là nó đã hoạt động! Tệp PDF không còn bị tải xuống nữa mà được mở trực tiếp trong một tab mới của trình duyệt.

Tiếp theo, tôi đã chỉnh sửa phần div bằng CSS để tùy chỉnh giao diện. Nhưng lại có một vấn đề nảy sinh...

html2pdf Không hoạt động với CSS

CSS mà tôi đã viết cho div không được tải lên. Nó không hoạt động khi sử dụng html2pdf.

Sau khi tìm kiếm, tôi phát hiện rằng html2pdf không tải được CSS bên ngoài. Vì vậy, chỉ có HTML được hiển thị mà không có CSS.

Giải pháp:

  • Viết CSS trực tiếp trong HTML bằng thẻ <style>.
  • Sử dụng inline CSS (CSS nội tuyến).
  • Một số cách giải quyết khác cũng được đề xuất trong các diễn đàn.

Cuối cùng, tệp PDF đã được tạo như mong muốn. Nhưng vẫn còn một vấn đề nữa.

Văn bản trong PDF không thể chọn được

Các đoạn văn bản trong PDF không thể chọn (select). Điều này có thể không quan trọng với hầu hết các dự án, nhưng tôi cần tính năng này.

Lý do là html2pdf tạo PDF bằng cách chuyển đổi HTML thành ảnh canvas. Nó sử dụng html2canvas, do đó, tất cả nội dung chỉ là hình ảnh, không phải văn bản thực.

Tôi đã phải tìm một thư viện khác. Nhưng tại sao không dùng chính công cụ đã chạy ngầm phía dưới suốt thời gian qua?

jsPDF được sử dụng bởi html2pdf, vì vậy tôi đã thử dùng trực tiếp jsPDF.

doc.fromHTML(document.getElementById("div-to-print"),
     22, // Margins
     17,
     {'width': 400},
     function (a) {
          // doc.save("HTML2PDF.pdf"); // To Save
          let blobPDF = new Blob([doc.output()], { type: 'application/pdf' });
          let blobUrl = URL.createObjectURL(blobPDF);
          window.open(blobUrl);
});

Và mọi thứ đã tốt trở lại.

Mở PDF trong tab mới thay vì tải xuống từ jsPDF

Để mở PDF trong một tab mới thay vì tải xuống khi sử dụng jsPDF, tương tự như với html2pdf, chúng ta có thể truyền doc.output() vào một Blob trong hàm callback.

Điểm khác biệt là PDF được tạo chứa văn bản thực, không phải hình ảnh.

Mọi thứ đã hoạt động tốt, và tôi đã thêm CSS. Nhưng lại gặp một vấn đề khác.

jsPDF Không hoạt động với CSS

Hóa ra jsPDF không hỗ trợ CSS. Để làm việc với CSS, cần sử dụng html2canvas. Và đây chính là cách mà html2pdf đã xử lý từ đầu.

Tuy nhiên, jsPDF hỗ trợ thiết lập lề (margin) dễ dàng. Nó cũng hỗ trợ các thuộc tính HTML cơ bản, như các thẻ <p>.

Nhưng lý do tôi cần CSS là vì div mà tôi muốn in ra có hai div con bên trong. Một trong số đó cần được căn giữa theo cả chiều dọc và chiều ngang.

Sau đó, tôi đã tìm kiếm: "Làm thế nào để căn giữa một div con chỉ bằng HTML, không dùng CSS?"

Hóa ra, jsPDF có một API văn bản (text API) hỗ trợ nhiều tham số khác nhau, giúp thực hiện việc căn giữa dễ dàng mà không cần CSS.

API.text = function(text, x, y, flags, angle, align);

Nhiều đoạn văn bản như vậy và công việc sẽ hoàn thành

Nhưng thay vì viết nhiều đoạn văn bản riêng lẻ, nếu tôi có thể làm điều đó với nhiều phần tử HTML bằng cách sử dụng hàm withHTML(), thì sẽ rất tuyệt.

Hóa ra điều đó có thể thực hiện được bằng cách thêm một khối phần tử HTML khác trong callback của hàm, kết hợp với khối trước đó, tôi có thể đạt được kết quả mong muốn.

Thêm một vài phép tính nữa, rồi sử dụng các giá trị tính toán làm lề (margin), nội dung có thể được căn giữa hoàn hảo.

let pageHeight = doc.internal.pageSize.height || doc.internal.pageSize.getHeight()
let pageWidth = doc.internal.pageSize.width || doc.internal.pageSize.getWidth()
let recipientBlock = document.querySelector(".div2-block")
let rHeight = recipientBlock.clientHeight
let rWidth = recipientBlock.clientWidth

doc.fromHTML(document.querySelector(".div1-block"),
   22, 17, { 'width': 200, 'height': 200 },
   function (a) {
      doc.fromHTML(document.querySelector(".div2-block"),
          pageWidth / 2 - rWidth / 4,
          pageHeight / 2 - rHeight / 4,
          { 'width': 200, 'height': 200 },
          function (a) {
              let blobPDF = new Blob([doc.output()], { type: 'application/pdf' });
              let blobUrl = URL.createObjectURL(blobPDF);
              window.open(blobUrl);
                });
        });

Cuối cùng, gần hoàn thành dự án

Chi còn một việc nữa cần làm: thiết lập chiều rộng và chiều cao cho tệp PDF cuối cùng.

Điều này đã được đề cập trong tài liệu và khá dễ thực hiện.

Chỉ cần truyền chiều cao và chiều rộng dưới dạng một mảng và chỉ định đơn vị đo.

Vì một số lý do, khi sử dụng đơn vị "px" (pixel), nó gặp vấn đề. Vì vậy, tôi đã sử dụng "pt" (point) và nó hoạt động hoàn hảo.

let doc =new jsPDF({orientation: 'l', unit: 'pt', format: [widthForJsPDF, heightForJsPDF]})

Vậy là dự án đã hoàn thành – Chuyển đổi HTML sang PDF bằng JavaScript

Và tất cả những gì tôi đã làm chỉ là tìm kiếm trên Google. Thật thú vị phải không nào!


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí