接上篇富文本編輯器 html 內(nèi)容轉(zhuǎn) word:html-docs-js 避坑指南,我們已經(jīng)完成了 html 內(nèi)容轉(zhuǎn) word 文檔的需求,接著咱們看下圖片和 pdf 的處理。
介紹下用到的庫
html2canvas
圖片和 pdf 的轉(zhuǎn)換都會(huì)用到html2canvas來完成,通過官網(wǎng)上的介紹,我們可以總結(jié)一下它的特點(diǎn):
- 不需要后臺(tái)支持,通過純?yōu)g覽器端”截圖“;
- 可對(duì)部分或整個(gè)網(wǎng)頁進(jìn)行“截圖”;
- 基于 DOM(遍歷頁面的 DOM),利用可用的信息構(gòu)建屏幕截圖;
- 有些 css 屬性未被支持,可查看支持的 css 屬性列表;
- 受同源策略影響;
- 無法渲染 iframe,flash 等內(nèi)容。
jsPDF
pdf 的轉(zhuǎn)換用到jsPDF,可以看看這個(gè)demo,對(duì) jsPDF 的介紹比較詳細(xì)。
圖片的轉(zhuǎn)換
相比于 html 轉(zhuǎn) word 來說,圖片和 pdf 的轉(zhuǎn)換相對(duì)來說簡單了許多,咱們來看下圖片的轉(zhuǎn)換過程,主要有以下幾個(gè)步驟:
-
克隆需要截圖的 DOM 元素
通過cloneNode將需要克隆的節(jié)點(diǎn)生成一份副本,這一步的目的是:我們不能直接對(duì)原始 DOM 進(jìn)行操作,因?yàn)闀?huì)影響頁面布局。所以可以修改克隆后的 DOM 節(jié)點(diǎn),通過修改節(jié)點(diǎn)的樣式(border、box-shadow 等)或修改節(jié)點(diǎn)寬高,達(dá)到我們想要的截圖效果。
const cloneEle = ele.cloneNode(true) // 對(duì)克隆的節(jié)點(diǎn)進(jìn)行操作 cloneEle.style.xxx = '' -
通過 html2canvas 截圖
我們將第一步克隆到的 DOM 進(jìn)行一個(gè)清理的動(dòng)作,清理的作用是:移除不需要截圖的 DOM 節(jié)點(diǎn);將克隆的節(jié)點(diǎn)添加到 DOM 上,并返回新節(jié)點(diǎn)和刪除節(jié)點(diǎn)的方法。刪除節(jié)點(diǎn)
cleanHtmlRecover方法用于在截圖完成后移除 DOM 元素。接著使用 html2canvas 方法將 DOM 繪制為 canvas,通過調(diào)用 canvas 對(duì)象的 toDataURL 方法將 canvas 轉(zhuǎn)換成圖片。
這里需要為 html2canvas 提供第二個(gè)參數(shù)
useCORS: true,開啟使用 CORS 從服務(wù)器加載圖像,不然如果圖片不同源時(shí)就會(huì)導(dǎo)致一片白。更多參數(shù)配置請(qǐng)參考configuration。const cleanHtml = (ele: HTMLElement) => { // 移除不需要截圖的DOM節(jié)點(diǎn) const selectElements = ele.querySelectorAll('select') selectElements.forEach((sel) => (sel.style.display = 'none')) const title = document.createElement('div') const warp = document.createElement('div') // 圖片、pdf導(dǎo)出背景色不是白色 warp.style.position = 'absolute' warp.style.zIndex = '-1' warp.append(ele) document.body.append(warp) return { warp, cleanHtmlRecover: () => { warp.remove() } } } const { warp, cleanHtmlRecover } = cleanHtml(cloneEle) return new Promise<void>((resolve) => { Html2canvas(warp, { useCORS: true }) .then((canvas) => { // 生成截圖 const image = canvas.toDataURL('image/jpg') // 下載圖片 }) .finally(() => { cleanHtmlRecover() resolve() }) }) -
下載圖片
上一步獲取到轉(zhuǎn)換后的圖片后,就可以通過
a標(biāo)簽的方式來下載圖片,我們可以通過 dispatchEvent 來模擬點(diǎn)擊事件完成下載。對(duì) dispatchEvent 的其他使用可以看這篇文章。// 下載圖片 const a = document.createElement('a') a.download = filename a.href = canvas.toDataURL('image/jpg') const event = new MouseEvent('click') a.dispatchEvent(event)
pdf 的轉(zhuǎn)換
圖片的導(dǎo)出已經(jīng)完成,那么 pdf 的導(dǎo)出應(yīng)該如何做呢?
一開始我們是用的html2pdf-jspdf2,它就是使用 html2canvas 和 jsPDF 結(jié)合在一起,通過和 html2canvas 將 html 內(nèi)容轉(zhuǎn)為 canvas,再通過 jsPDF 將 canvas 轉(zhuǎn)為 pdf。說幾個(gè)我遇到的問題(可能是我用的不對(duì)):
- 在 JSPDF 中我設(shè)置了
format: 'a4',意思是使用 A4 紙的大小來導(dǎo)出,頁面同樣設(shè)置為 A4,但導(dǎo)出的 pdf 文件寬度顯示不全; - 我們頁面可以設(shè)置成 A3、A4、A5 幾種特定紙張,并且支持設(shè)置寬高自定義紙張,但當(dāng)我傳入寬高后,發(fā)現(xiàn)得到的 pdf 文件不是我設(shè)置好的寬高;
- 沒有了,直接換庫跑路 ??
遇到問題解決不了怎么辦?找 leader,找 leader,還是找 leader
通過我們一陣商量,最終確定了一個(gè)方案:先用 html2canvas 將 html 轉(zhuǎn)換為圖片,再利用 jsPDF 提供的addImage方法將圖片貼到 pdf 中,因?yàn)閳D片導(dǎo)出目前是沒有什么問題,而且展示效果也挺好,所以導(dǎo)出的 pdf 應(yīng)該也不會(huì)有什么問題。
接下來就是和產(chǎn)品掰頭環(huán)節(jié),巴拉巴拉的...,成功讓他們改了需求。
最后看下實(shí)現(xiàn)過程:
html2canvas 的使用與前面生成圖片一樣,接著通過generatePDF生成 pdf。
...
Html2canvas(warp, { useCORS: true })
.then((canvas) => generatePDF(canvas, filename))
...
我們看下generatePDF的實(shí)現(xiàn)步驟:
- 計(jì)算一頁 A4 紙能顯示當(dāng)前 html 生成的 canvas 高度;
- 如果 canvas 的高度未超過一頁 A4 紙的顯示高度,無需分頁,直接貼圖導(dǎo)出;
- 否則需要分頁打印,分頁打印思路如下:
- 設(shè)置變量
leftHeight記錄剩余高度,打印完一頁后 leftHeight 減去已經(jīng)打印的 canvas 的高度 pageHeight,如果剩余高度大于 0,說明沒打印完,通過addPage()增加分頁繼續(xù)打?。?/li> - 設(shè)置變量
position記錄打印開始的距離頭部的位置,打印完一頁后 position 增加一頁 A4 紙的高度繼續(xù)打印。
- 設(shè)置變量
最后貼上完整代碼:
/** a4紙的尺寸 */
enum A4_PAPER_SIZE_ENUM {
'width' = 595.28,
'height' = 841.89,
}
const generatePDF = (canvas: HTMLCanvasElement, filename: string) => {
const contentWidth = canvas.width
const contentHeight = canvas.height
// 一頁pdf顯示html頁面生成的canvas高度
const pageHeight =
(contentWidth / A4_PAPER_SIZE_ENUM.width) * A4_PAPER_SIZE_ENUM.height
// 未生成pdf的html頁面高度
let leftHeight = contentHeight
// 頁面偏移
let position = 0
const imgWidth = A4_PAPER_SIZE_ENUM.width
const imgHeight = (A4_PAPER_SIZE_ENUM.width / contentWidth) * contentHeight
const pageData = canvas.toDataURL('image/jpeg', 1.0)
const PDF = new JsPDF('p', 'pt', 'a4')
// 當(dāng)內(nèi)容未超過pdf一頁顯示的范圍,無需分頁
if (leftHeight < pageHeight) {
// addImage(pageData, 'JPEG', 左,上,寬度,高度)設(shè)置
PDF.addImage(pageData, 'JPEG', 0, 0, imgWidth, imgHeight)
} else {
// 超過一頁時(shí),分頁打印(每頁高度841.89)
while (leftHeight > 0) {
PDF.addImage(pageData, 'JPEG', 0, position, imgWidth, imgHeight)
leftHeight -= pageHeight
position -= A4_PAPER_SIZE_ENUM.height
if (leftHeight > 0) {
PDF.addPage()
}
}
}
PDF.save(filename + '.pdf')
}
小瑕疵
這種方法有一點(diǎn)點(diǎn)小問題:分頁的地方處理不太好,不會(huì)自動(dòng)識(shí)別隔頁處理,而只是比較粗暴的從中間被拆分,類似下面這張圖。

總結(jié)
不管是工作還是學(xué)習(xí)中,都要有良好的“小記”習(xí)慣,將遇到的問題、解決的過程記錄下來,最后整理成文,積累沉淀,不僅鍛煉自己的文筆,同時(shí)拓寬知識(shí)面、幫助他人,在以后工作中遇到時(shí)也能更快的解決問題,實(shí)現(xiàn)業(yè)務(wù)需求;而不是做完就停滯了,下次遇到同樣的問題還是處理不了。
以上就是本文的全部內(nèi)容,希望這篇文章對(duì)你有所幫助,歡迎點(diǎn)贊和收藏??,如果發(fā)現(xiàn)有什么錯(cuò)誤或者更好的解決方案及建議,歡迎隨時(shí)聯(lián)系。