接上篇富文本編輯器 html 內(nèi)容轉(zhuǎn) word:html-docs-js 避坑指南举畸,我們已經(jīng)完成了 html 內(nèi)容轉(zhuǎn) word 文檔的需求炸站,接著咱們看下圖片和 pdf 的處理亡电。
介紹下用到的庫(kù)
html2canvas
圖片和 pdf 的轉(zhuǎn)換都會(huì)用到html2canvas來(lái)完成,通過(guò)官網(wǎng)上的介紹壮池,我們可以總結(jié)一下它的特點(diǎn):
- 不需要后臺(tái)支持偏瓤,通過(guò)純?yōu)g覽器端”截圖“;
- 可對(duì)部分或整個(gè)網(wǎng)頁(yè)進(jìn)行“截圖”椰憋;
- 基于 DOM(遍歷頁(yè)面的 DOM)厅克,利用可用的信息構(gòu)建屏幕截圖;
- 有些 css 屬性未被支持橙依,可查看支持的 css 屬性列表证舟;
- 受同源策略影響;
- 無(wú)法渲染 iframe窗骑,flash 等內(nèi)容女责。
jsPDF
pdf 的轉(zhuǎn)換用到jsPDF,可以看看這個(gè)demo创译,對(duì) jsPDF 的介紹比較詳細(xì)鲤竹。
圖片的轉(zhuǎn)換
相比于 html 轉(zhuǎn) word 來(lái)說(shuō),圖片和 pdf 的轉(zhuǎn)換相對(duì)來(lái)說(shuō)簡(jiǎn)單了許多昔榴,咱們來(lái)看下圖片的轉(zhuǎn)換過(guò)程辛藻,主要有以下幾個(gè)步驟:
-
克隆需要截圖的 DOM 元素
通過(guò)cloneNode將需要克隆的節(jié)點(diǎn)生成一份副本,這一步的目的是:我們不能直接對(duì)原始 DOM 進(jìn)行操作互订,因?yàn)闀?huì)影響頁(yè)面布局吱肌。所以可以修改克隆后的 DOM 節(jié)點(diǎn),通過(guò)修改節(jié)點(diǎn)的樣式(border仰禽、box-shadow 等)或修改節(jié)點(diǎn)寬高氮墨,達(dá)到我們想要的截圖效果。
const cloneEle = ele.cloneNode(true) // 對(duì)克隆的節(jié)點(diǎn)進(jìn)行操作 cloneEle.style.xxx = ''
-
通過(guò) 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,通過(guò)調(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)換后的圖片后蹄梢,就可以通過(guò)
a
標(biāo)簽的方式來(lái)下載圖片疙筹,我們可以通過(guò) dispatchEvent 來(lái)模擬點(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é)合在一起翘盖,通過(guò)和 html2canvas 將 html 內(nèi)容轉(zhuǎn)為 canvas,再通過(guò) jsPDF 將 canvas 轉(zhuǎn)為 pdf凹蜂。說(shuō)幾個(gè)我遇到的問題(可能是我用的不對(duì)):
- 在 JSPDF 中我設(shè)置了
format: 'a4'
馍驯,意思是使用 A4 紙的大小來(lái)導(dǎo)出,頁(yè)面同樣設(shè)置為 A4玛痊,但導(dǎo)出的 pdf 文件寬度顯示不全汰瘫; - 我們頁(yè)面可以設(shè)置成 A3、A4擂煞、A5 幾種特定紙張混弥,并且支持設(shè)置寬高自定義紙張,但當(dāng)我傳入寬高后,發(fā)現(xiàn)得到的 pdf 文件不是我設(shè)置好的寬高蝗拿;
- 沒有了晾捏,直接換庫(kù)跑路 ??
遇到問題解決不了怎么辦?找 leader哀托,找 leader惦辛,還是找 leader
通過(guò)我們一陣商量,最終確定了一個(gè)方案:先用 html2canvas 將 html 轉(zhuǎn)換為圖片仓手,再利用 jsPDF 提供的addImage方法將圖片貼到 pdf 中胖齐,因?yàn)閳D片導(dǎo)出目前是沒有什么問題,而且展示效果也挺好嗽冒,所以導(dǎo)出的 pdf 應(yīng)該也不會(huì)有什么問題呀伙。
接下來(lái)就是和產(chǎn)品掰頭環(huán)節(jié),巴拉巴拉的...添坊,成功讓他們改了需求剿另。
最后看下實(shí)現(xiàn)過(guò)程:
html2canvas 的使用與前面生成圖片一樣,接著通過(guò)generatePDF
生成 pdf帅腌。
...
Html2canvas(warp, { useCORS: true })
.then((canvas) => generatePDF(canvas, filename))
...
我們看下generatePDF
的實(shí)現(xiàn)步驟:
- 計(jì)算一頁(yè) A4 紙能顯示當(dāng)前 html 生成的 canvas 高度驰弄;
- 如果 canvas 的高度未超過(guò)一頁(yè) A4 紙的顯示高度,無(wú)需分頁(yè)速客,直接貼圖導(dǎo)出戚篙;
- 否則需要分頁(yè)打印,分頁(yè)打印思路如下:
- 設(shè)置變量
leftHeight
記錄剩余高度溺职,打印完一頁(yè)后 leftHeight 減去已經(jīng)打印的 canvas 的高度 pageHeight岔擂,如果剩余高度大于 0,說(shuō)明沒打印完浪耘,通過(guò)addPage()增加分頁(yè)繼續(xù)打勇伊椤; - 設(shè)置變量
position
記錄打印開始的距離頭部的位置七冲,打印完一頁(yè)后 position 增加一頁(yè) 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
// 一頁(yè)pdf顯示html頁(yè)面生成的canvas高度
const pageHeight =
(contentWidth / A4_PAPER_SIZE_ENUM.width) * A4_PAPER_SIZE_ENUM.height
// 未生成pdf的html頁(yè)面高度
let leftHeight = contentHeight
// 頁(yè)面偏移
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)容未超過(guò)pdf一頁(yè)顯示的范圍,無(wú)需分頁(yè)
if (leftHeight < pageHeight) {
// addImage(pageData, 'JPEG', 左澜躺,上蝉稳,寬度,高度)設(shè)置
PDF.addImage(pageData, 'JPEG', 0, 0, imgWidth, imgHeight)
} else {
// 超過(guò)一頁(yè)時(shí)掘鄙,分頁(yè)打釉牌荨(每頁(yè)高度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)小問題:分頁(yè)的地方處理不太好,不會(huì)自動(dòng)識(shí)別隔頁(yè)處理操漠,而只是比較粗暴的從中間被拆分收津,類似下面這張圖。
總結(jié)
不管是工作還是學(xué)習(xí)中,都要有良好的“小記”習(xí)慣撞秋,將遇到的問題长捧、解決的過(guò)程記錄下來(lái),最后整理成文部服,積累沉淀唆姐,不僅鍛煉自己的文筆,同時(shí)拓寬知識(shí)面廓八、幫助他人,在以后工作中遇到時(shí)也能更快的解決問題赵抢,實(shí)現(xiàn)業(yè)務(wù)需求剧蹂;而不是做完就停滯了,下次遇到同樣的問題還是處理不了烦却。
以上就是本文的全部?jī)?nèi)容宠叼,希望這篇文章對(duì)你有所幫助,歡迎點(diǎn)贊和收藏??其爵,如果發(fā)現(xiàn)有什么錯(cuò)誤或者更好的解決方案及建議冒冬,歡迎隨時(shí)聯(lián)系。