摘要:筆者最近做了一個在構建打印模板的需求,從中學習到一些有價值的東西,特地記錄一篇文章分享。此時,瀏覽器會彈出打印預覽的窗口,通過頁面生成了用于打印預覽。最后,聯想到也可用于實現打印模板需求,筆者以和模板為例進行了一個簡單的實踐。
筆者最近做了一個在 Web 構建打印模板的需求,從中學習到一些有價值的東西,特地記錄一篇文章分享。概述
本文首先描述筆者所處的項目組的 Web 打印項目的需求背景,然后描述筆者在實踐 Web 打印項目的過程中遇到了諸多問題,闡述 Web 打印的問題解決思路,最后給出了另外一種 Web 打印的需求解決方案,即使用Headless browser生成圖片并打印的方案。預計閱讀時間5 ~ 10分鐘。
本文主要分下面幾個方面:
Web 構建打印模板需求
基本概念
打印設備接口
頁面模型
引入打印樣式
處理 Web打印 分頁問題
去除瀏覽器默認的頁眉頁底
構建自定義的頁眉頁底
使用 Headless browser 生成圖片的解決方案
Web 構建打印模板需求產品經理小姐姐近期給筆者寫了這樣一個需求:
實現一個打印報告的模板頁面,瀏覽器或客戶端調用打印設備的接口打印出對應的報告。
對應報告支持報告模板配置,模板分幾種,例如免費玩家用極簡版、低保玩家用基本版、充值玩家用高級版、土豪玩家用頂配版。沒錯,充值才能變得更強。
需要實現分頁功能,支持把對應的內容展示到對應的頁。例如:內容A為基本信息,需要展示到第一頁,低保玩家享受內容B,展示到第二頁...土豪玩家享受所有的功能,展示到第n頁。
展示產品配置的對應的頁眉、頁底。
于是,為了解決上述需求,筆者大概寫了這樣的一個模板,如下所示:
基本概念 打印設備接口我是第一頁1我是第一頁2我是第一頁3我是第二頁1我是第二頁2我是第二頁3我是第三頁1我是第三頁2我是第三頁3我是第四頁1我是第四頁2我是第四頁3
瀏覽器打印是一個很成熟的應用,最簡單的打印直接調用window.print()或者是調用document.execCommand("print")。此時,瀏覽器會彈出打印預覽的窗口,通過頁面生成了pdf用于打印預覽。如圖所示,展示了谷歌首頁的打印預覽:
頁面模型和 CSS 盒子模型一樣,頁面盒子模型由外邊距 (margin)、邊框 (border)、內邊距 (padding) 和 內容區域 (content area) 構成:
有以下兩點可以注意:
打印頁面時,只打印出頁面的內容區域
頁面默認有頁眉頁腳信息,展現到頁面外邊距范圍
默認情況下,頁面是從左到右、從上到下展示,如果需要更改打印設備的方向,可以通過設置根元素的 direction 和 writing-mode 屬性來改變頁面方向。
引入打印樣式可以通過三種方式引入打印樣式:
使用 @media print:
@media print { body { background-color:#FFFFFF; margin: 0mm; /* this affects the margin on the content before sending to printer */ } // ... }
內聯樣式使用media屬性:
在 CSS 中使用 @import:
@import url("print.css") print;
HTML 中使用的link標簽添加media屬性:
處理 Web打印 分頁問題項目需求中首先遇到的問題是需要處理 Web打印 分頁問題。即使該部分未占滿一頁紙的高度,也需要進行手動的分頁。起初,我通過計算頁面每個部分的高度,在對應頁面部分的節點的高度下方預留一部分的外邊距來實現,如下代碼所示,通過查資料得知 A4紙的寬高比為 297 : 210 ,除去頁面外邊距(左右各 20mm )來算得每一部分需要預留的高度:
const A4_HEIGHT_WIDTH_RATE = 297 / (210 - 2 * 13); // 打印區域長寬比:(A4紙高)比(A4紙寬減去左右側20mm的邊距) const PAGE_WIDTH = 680; // 頁面寬度(像素值) const PAGE_HEIGHT = PAGE_WIDTH * A4_HEIGHT_WIDTH_RATE; // 頁面高度 const $page1El = document.querySelector(".page1"); const page1Height = parseInt($page1El.clientHeight); // page1的高度是多少像素 const pageNum = Math.ceil(page1Height / PAGE_HEIGHT); // page1需要占多少頁,超過1頁的高度,就需要占2頁,因此向上取整 const marginBottom = pageNum * PAGE_HEIGHT - page1Height; // 需要預留多少外邊距 $page1El.style.marginBottom = `${marginBottom}px`;
但是,其實 CSS 早就支持了打印設備里的分頁問題了,可以通過設置break-after: page; 或 page-break-after: always;實現在打印設備的分頁:
.page1 { break-after: page; page-break-after: always; } // ...去除瀏覽器默認的頁眉頁底
實現分頁的效果后,發現頁面打印會在頁底出現當前頁面的 url :
頁面默認有頁眉頁腳信息,展現到頁面外邊距范圍,通過去除 頁面模型 的外邊距,使得內容不會延伸到頁面的邊緣,再通過設置 body 元素的 margin 來保證 A4 紙打印出來的頁面帶有外邊距:
@media print { @page { margin: 0; } body { margin: 2cm; } }
現在打印出來的頁面不再具有默認的頁底:
構建自定義的頁眉頁底通過將對應的頁眉、頁底元素的 position 設置為 fixed 可以固定對應節點到頁面的任意一部分,它們也將在每個打印頁面上重復。
.header { position: fixed; top: 0; } .footer { position: fixed; bottom: 0; }使用 Headless browser 生成圖片的解決方案
上面說了那么多,都是在前端實現的 Web 打印的解決方案,但實際上,如果可以在后臺直接通過 Web 頁面,預先保存好的頁面模板,通過拉取后臺數據,并運行Headless browser生成一張截圖,通過打印截圖就可以解決這樣的問題了,下面以 phantomjs 配合 pug為例,展示筆者使用Headless browser生成圖片的簡單解決方案:
// 針對鏈接的截圖服務 // 返回phantom實例的promise對象,為了獲取對應的base64編碼 function captureByUrl(url, data) { let instance; let page; const destroyInstance = () => { // 關閉頁面 page.close(); // 退出實例 instance.exit(); }; return phantom.create() // 首先,創建phantom實例 .then((_instance) => { instance = _instance; return instance.createPage(); }) .then((_page) => { page = _page; if (data.width && data.height) { // 設置phantom截圖頁面的寬高值 page.property("viewportSize", { width: data.width, height: data.height }); } page.setting("userAgent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.120 Safari/537.36"); return page.open(url); }) .then(() => { return page.renderBase64("PNG"); // 渲染對應圖片,拿到base64字符串 }) .then((image) => { destroyInstance(); // 銷毀phantom實例 return image; }, (error) => { destroyInstance(); // 銷毀phantom實例 throw error; }); }
如上代碼所示,使用 Headless browser 打開一個鏈接,通過renderBase64將對應頁面的預覽圖截圖生成base64字符串。
對應的,在服務端,可以通過讀取預先寫好的pug模板,傳入對應數據生成對應頁面預覽圖,再通過 Headless browser 生成截圖保存到本地,即可實現 Web 打印在服務端的解決方案,如下代碼所示,為服務端讀取模板,并保存圖片的部分代碼:
// 針對模板和數據的截圖服務 function captureByTemplate(template, data) { const content = pug.compile(template)(Object.assign({ URL_PREFIX, }, data)); const contentInBase64 = new Buffer(content).toString("base64"); const url = `data:text/html;charset=utf8;base64,${contentInBase64}`; return captureByUrl(url, data); } captureByTemplate(fs.readFileSync("./print.pug", "utf-8"), data) .then(base64Data => { fs.writeFile("out.png", base64Data, "base64", function(err) { console.error(err); }); }) .catch(err => { console.error(err); });小結
本文為筆者在實踐 Web 打印相關項目的項目總結,首先描述了 Web 打印項目一般需求,然后在打印設備下,頁面模型的展現形式;然后描述了筆者在實踐過程中遇到的一些常見問題,給出一些通用性的解決方案。最后,聯想到 Headless browser 也可用于實現打印模板需求,筆者以 phantomjs 和 pug 模板為例進行了一個簡單的實踐。
最后,筆者近期建立了一個技術交流群,歡迎大家在群里討論技術,另外,幫團隊打個廣告,醫療健康事業部還在招聘前端、后臺、數據工程師,有想加入騰訊醫療健康的朋友可以加群或者直接發送簡歷到 counterxing@tencent.com 。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/102699.html
摘要:對于通常的特別是那些具備并行計算多線程背景知識的來講,的異步處理著實稱得上詭異。而這個詭異從結果上講,是由的單線程這個特性所導致的。的特性之一是單線程,也即是從頭到尾,都在同一根線程下運行。而這兩者的不同,便在于單線程和多線程上。 對于通常的developer(特別是那些具備并行計算/多線程背景知識的developer)來講,js的異步處理著實稱得上詭異。而這個詭異從結果上講,是由js...
摘要:通過方法提交一個任務,并且通過對象來獲得結果。對象可以取消運行任務,設置等待時間,獲取任務狀態,最終獲得任務結果。類似于,但是并不會有返回結果和異常信息。由兩個階段所觸發的,沒有保證的結果用于依賴階段的計算。 本系列關于concurrent的代碼示例,是被我分割成了小部分,在系列文章結束以后,我會將較為完整的代碼上傳,在寫的過程中我會參考官方API以及其他牛人的見解,大家有不同的看法可...
閱讀 2801·2023-04-25 22:51
閱讀 2025·2021-10-11 10:58
閱讀 3308·2019-08-30 10:49
閱讀 1870·2019-08-29 17:09
閱讀 3135·2019-08-29 10:55
閱讀 838·2019-08-26 10:34
閱讀 3465·2019-08-23 17:54
閱讀 980·2019-08-23 16:06