Vue3自定義打印實現(xiàn)原理詳解
前言
最近接觸到了一個 Vue3 的打印需求,我發(fā)現(xiàn)自己雖然從事前端開發(fā)已有多年,但對如何實現(xiàn)自定義打印還沒有深入研究,一般都是找現(xiàn)成的庫來解決問題,借這次的機會研究了一下如何實現(xiàn)自定義打印。
在現(xiàn)在的前端開發(fā)中,打印是一個比較常見的功能,無論是在生成報表、下載發(fā)票還是其他用途上都會用到。 最基本的打印方式是直接打印整個頁面內(nèi)容,但在實際項目中,我們常常需要更靈活的打印功能,例如選擇性打印某個特定部分、提供打印預(yù)覽功能等。這就產(chǎn)生了許多 JavaScript 打印庫,如 print-js
、vue-print-nb
等,這些庫不僅簡化了打印流程,還提供了豐富的自定義打印配置,滿足各種不同的打印需求。
由于當(dāng)前的項目基于 Vue3 進行開發(fā),我簡單調(diào)研后發(fā)現(xiàn) vue-print-nb
是 Vue 中常用的打印庫,同時其提供了適用于 Vue3 的 vue3-print-nb
庫。然而,在實際使用過程中,我發(fā)現(xiàn)該庫由于發(fā)布較早,且后期未繼續(xù)維護,因此在支持 Vue3 的一些特性方面有所欠缺,所以我在此基礎(chǔ)上進行了優(yōu)化和改造,來更好地滿足需求。
本文將詳細探討如何使用原生 JavaScript 實現(xiàn)自定義打印功能,同時講解對 vue3-print-nb
的改造和優(yōu)化。
實現(xiàn)自定義打印
實現(xiàn)自定義打印的核心思想是通過將要打印的內(nèi)容放入一個 iframe
或者新窗口中,然后調(diào)用 window.print()
方法進行打印。然而,需要考慮的問題遠不止這些,例如樣式還原、表單展示、以及canvas 的打印等。
實現(xiàn)思路
1. 確定打印區(qū)域
首先,需要確定用戶希望打印的頁面區(qū)域, 這通常通過用戶傳遞的一個選擇器或直接傳入的 DOM 元素來實現(xiàn):
- 選擇器:通過傳遞一個 CSS 選擇器來標(biāo)識需要打印的元素。
- DOM 元素:直接傳遞所需打印的 DOM 對象。
2. 克隆打印內(nèi)容
為了保證打印內(nèi)容與頁面其他部分隔離開來,通常會將需要打印的內(nèi)容深度克隆。這確保了打印時不會受到動態(tài)內(nèi)容變化的干擾,并且可以對其進行獨立處理:
/** * 拷貝需要打印的節(jié)點 */ function cloneContent(selector) { const originalContent = document .querySelector(selector) .cloneNode(true); // 在克隆內(nèi)容中處理樣式和事件,比如刪除不需要的節(jié)點 originalContent .querySelectorAll('.no-print') .forEach((el) => el.remove()); return originalContent.outerHTML; }
3. 創(chuàng)建新的上下文
為了確保打印內(nèi)容不受當(dāng)前頁面的影響,通常會創(chuàng)建一個新的窗口或 iframe 并復(fù)制打印內(nèi)容到新的上下文中。兩種常見的實現(xiàn)方式是:
- 新窗口:打開一個新的瀏覽器窗口或標(biāo)簽頁,將克隆內(nèi)容放置其內(nèi),然后觸發(fā)打印。
- Iframe:在當(dāng)前頁面中創(chuàng)建一個隱藏的
iframe
,將打印內(nèi)容放入iframe
后,再從iframe
中觸發(fā)打印。
/** * 創(chuàng)建一個新的上下文(打印窗口) */ function createPrintIframe() { const iframe = document.createElement('iframe'); iframe.id = this.iframeId; iframe.src = new Date().getTime().toString(); iframe.style.display = 'none'; document.body.appendChild(iframe); return iframe; } /** * 將需要打印的內(nèi)容寫入新的上下文 */ function write(printWindow, writeContent) { const iframeDocument = printWindow.contentDocument; iframeDocument.open(); iframeDocument.write(`<!DOCTYPE html><html lang='zh'><head>${getHead()}</head><body>${writeContent}</body></html>`); iframeDocument.close(); }
4. 處理打印樣式
設(shè)計打印庫時,首先需要解決的問題就是如何在新的上下文中還原樣式,包括頁面原有樣式和用戶自定義樣式,以滿足復(fù)雜業(yè)務(wù)需求。
- 獨立樣式:插入到新的打印文檔或上下文中,可以是內(nèi)聯(lián)樣式或者外部樣式表。
- 打印媒體查詢:使用
@media print
媒體查詢,以定義僅在打印時生效的特殊樣式,使得打印和屏幕顯示效果獨立。
/** * 獲取 head */ function getHead() { // 獲取網(wǎng)頁原有的 css 鏈接 const links = Array.from(document.querySelectorAll('link')) .filter((item) => item.href.includes('.css')) .map( (item) => `<link type="text/css" rel="stylesheet" href='${item.href}'>` ) .join(''); // 獲取頁面中的 style 標(biāo)簽 const style = Array.from(document.styleSheets).reduce( (acc, styleSheet) => { const rules = styleSheet.cssRules || styleSheet.rules; if (!rules) return acc; acc += Array.from(rules).reduce( (innerAcc, rule) => innerAcc + rule.cssText, '' ); return acc; }, '' ); // ...省略處理用戶自定義引入的 css 樣式 // 返回 head return `<title>自定義打印示例</title>${links}<style type="text/css">${style}</style>`; }
5. 觸發(fā)打印
準備好打印內(nèi)容和樣式后,打印庫通過調(diào)用 window.print
方法來觸發(fā)打印。打印操作通常包含以下步驟:
- 打印對話框:調(diào)用
window.print
來觸發(fā)系統(tǒng)打印對話框。 - 清除上下文:打印完成后,關(guān)閉或銷毀用于打印的臨時窗口或
iframe
,以避免內(nèi)存泄漏和資源占用。
/** * 觸發(fā)打印操作 */ function triggerPrint(printWindow) { const iframeWin = printWindow?.contentWindow; iframeWin.addEventListener('load', () => { iframeWin.focus(); iframeWin.print(); iframe.remove(); }) }
通過上述步驟,我們就可以靈活地控制打印內(nèi)容和樣式,并能提供良好的打印體驗和可定制化的功能。
綜合示例
通過以上步驟,我們可以實現(xiàn)一個簡易的自定義打印功能。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>自定義打印示例</title> <style> html, body { margin: 0; } body { padding: 20px } .printable { border: 1px solid #000; padding: 20px; } .gc { color: #0aff; font-size: 14px; } </style> </head> <body> <div class="printable" id="printableArea"> <h1>葫蘆娃</h1> <p class="gc">葫蘆娃 葫蘆娃</p> <p class="gc">一根藤上七朵花</p> <p class="gc">風(fēng)吹雨打都不怕 </p> <p class="gc">啦啦啦啦</p> <p class="gc">叮當(dāng)當(dāng)咚咚當(dāng)當(dāng) 葫蘆娃</p> <p class="gc">叮當(dāng)當(dāng)咚咚當(dāng)當(dāng) 本領(lǐng)大</p> <p class="gc">啦啦啦啦</p> <p class="no-print">我是不需要打印的內(nèi)容,啦啦啦啦啦</p> <p class="no-print">我是不需要打印的內(nèi)容,啦啦啦啦啦</p> <p class="no-print">我是不需要打印的內(nèi)容,啦啦啦啦啦</p> <p class="no-print">我是不需要打印的內(nèi)容,啦啦啦啦啦</p> </div> <div> <button id="printBtn" style="margin-top: 10px">打印內(nèi)容</button> </div> <script> /** * 獲取 head */ function getHead() { // 獲取網(wǎng)頁原有的 css 鏈接 const links = Array.from(document.querySelectorAll('link')) .filter((item) => item.href.includes('.css')) .map( (item) => `<link type="text/css" rel="stylesheet" href='${item.href}'>` ) .join(''); // 獲取頁面中的 style 標(biāo)簽 const style = Array.from(document.styleSheets).reduce( (acc, styleSheet) => { const rules = styleSheet.cssRules || styleSheet.rules; if (!rules) return acc; acc += Array.from(rules).reduce( (innerAcc, rule) => innerAcc + rule.cssText, '' ); return acc; }, '' ); // 返回 head return `<title>自定義打印示例</title>${links}<style type="text/css">${style}</style>`; } /** * 創(chuàng)建一個新的上下文(打印窗口) */ function createPrintIframe() { const iframe = document.createElement('iframe'); iframe.src = new Date().getTime().toString(); iframe.style.display = 'none'; document.body.appendChild(iframe); return iframe; } /** * 拷貝需要打印的節(jié)點 */ function cloneContent(selector) { const originalContent = document .querySelector(selector) .cloneNode(true); // 在克隆內(nèi)容中處理樣式和事件,比如刪除不需要的節(jié)點 originalContent .querySelectorAll('.no-print') .forEach((el) => el.remove()); return originalContent.outerHTML; } /** * 將需要打印的內(nèi)容寫入新的上下文 */ function write(printWindow, writeContent) { const iframeDocument = printWindow.contentDocument; iframeDocument.open(); iframeDocument.write(`<!DOCTYPE html><html lang='zh'><head>${getHead()}</head><body>${writeContent}</body></html>`); iframeDocument.close(); } /** * 觸發(fā)打印操作 */ function triggerPrint(printWindow) { const iframeWin = printWindow?.contentWindow; iframeWin.addEventListener('load', () => { iframeWin.focus(); iframeWin.print(); iframe.remove(); }) } /** * 打印元素 */ function printElement(selector) { const clonedContent = cloneContent(selector); const printWindow = createPrintIframe(); write(printWindow, clonedContent); triggerPrint(printWindow); } document.getElementById('printBtn').addEventListener('click', () => { printElement('#printableArea'); }); </script> </body> </html>
實現(xiàn)效果如下:
通過上述示例,可以實現(xiàn)一個簡易的自定義打印功能。在實際業(yè)務(wù)中,我們可能需要支持更多復(fù)雜的需求,如外部 CSS 的引入,打印 canvas、表單等。
vue3-print-nb
vue3-print-nb
是一個用于 Vue3 的打印插件,提供了相對豐富的打印功能。但在實際使用過程中,我發(fā)現(xiàn)了一些不足之處,例如對 Vue3 setup 函數(shù)的支持不夠友好,以及不支持手動調(diào)用等。
優(yōu)化和改造
為了更好地支持 Vue3 setup 函數(shù),我對 vue3-print-nb
進行了以下改造:
- 使用 TypeScript 重寫了整個庫,使得調(diào)用傳參時體驗更佳。
- 增加了對
VuePrintNext
類的導(dǎo)出,允許手動調(diào)用打印方法,不再局限于指令調(diào)用。 - 增強 Vue3 setup 支持,允許通過直接導(dǎo)入
vPrint
指令進行局部導(dǎo)入使用。 - 支持設(shè)置指定的 CSS 選擇器來忽略打印部分內(nèi)容。
- 支持通過 CSS 選擇器或手動傳入 DOM 節(jié)點進行局部打印。
此外,還修復(fù)了一些已知的 bug:
- 背景色打印失效問題。
- 通過 URL 打印時,有時無法觸發(fā)打印行為的問題。
- 其他一些小問題。
新打印插件
我已將上述優(yōu)化和改造封裝成一個新的插件:vue-print-next
,并已上傳至 GitHub:https://github.com/Alessandro-Pang/vue-print-next。你可以通過 npm 進行安裝和使用。
npm install vue-print-next
結(jié)語
通過本文,我們對自定義打印的實現(xiàn)原理有了比較全面的了解。盡管實現(xiàn)打印功能仍然依賴于 window.print
API,但通過 window.open
或者 iframe
巧妙的將所需打印內(nèi)容進行隔離處理,可以實現(xiàn)靈活的打印功能。
以上就是Vue3自定義打印實現(xiàn)原理詳解的詳細內(nèi)容,更多關(guān)于Vue3自定義打印的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
vue實現(xiàn)的網(wǎng)易云音樂在線播放和下載功能案例
這篇文章主要介紹了vue實現(xiàn)的網(wǎng)易云音樂在線播放和下載功能,結(jié)合具體實例形式分析了網(wǎng)易云音樂相關(guān)接口調(diào)用與操作技巧,需要的朋友可以參考下2019-02-02使用element-ui設(shè)置table組件寬度(width)為百分比
這篇文章主要介紹了使用element-ui設(shè)置table組件寬度(width)為百分比方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-04-04Vue3通過hooks方式封裝節(jié)流和防抖的代碼詳解
vue3 中的 hooks 就是函數(shù)的一種寫法,就是將文件的一些單獨功能的js代碼進行抽離出來,放到單獨的js文件中,或者說是一些可以復(fù)用的公共方法/功能,本文給大家介紹了Vue3通過hooks方式封裝節(jié)流和防抖,需要的朋友可以參考下2024-10-10vscode 使用Prettier插件格式化配置使用代碼詳解
這篇文章主要介紹了vscode 使用Prettier插件格式化配置使用,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-08-08Vue開發(fā)配置tsconfig.json文件的實現(xiàn)
tsconfig.json文件中指定了用來編譯這個項目的根文件和編譯選項,本文就來介紹一下Vue開發(fā)配置tsconfig.json文件的實現(xiàn),感興趣的可以了解一下2023-08-08