前端處理網頁列印的 CSS 樣式

前端處理網頁列印的 CSS 樣式

開發筆記

前言

在測試網頁的列印效果時,我發現兩個問題:

  1. 列印時顯示的畫面跟切版長得完全不一樣。
  2. 美觀的排版會讓列印出來的內容變得相當長,相當浪費紙。

Google 後發現,居然有針對列印用的 CSS…

使用列印相關的 CSS

控制列印換頁

元素太長的情況下,通常會直接被會切成兩頁。

break 可以指定列印時要在哪個地方換頁,其中有分為 break-beforebreak-insidebreak-after,主要差在判斷換頁的位置。

page-break-* 這個 CSS 已經被 break-* 取代,但仍然可以用。

.my-element {
  break-before: always;
  break-after: always;
  break-inside: avoid;
}

調整列印版面配置

@page 可以調整頁面配置,例如列印的頁面大小、頁面邊距等等。

@page {
  /* 列印時的頁面大小 */
  size: landscape;
  /* 列印時的頁面邊距 */
  margin: 3cm;
}

列印時的畫面樣式

@media print 可以調整列印時的畫面配置,裡面的 CSS 都是列印時才會生效:

@media print {
  /* 列印時的字體 */
  font-family: sans-serif, Arial, Helvetica;
  /* 列印時的字體大小 */
  font-size: 12pt;
}

另外,如果列印時背景顏色沒有被套用,可以嘗試使用 print-color-adjust 這個 CSS,可以強制列印背景顏色或圖片。

.my-element {
  print-color-adjust: exact;
  /* Chrome 需要添加 -webkit- */
  -webkit-print-color-adjust: exact;
}

不過我向來認為列印功能應該會期望所見即所印,讓我不禁好奇怎麼會需要特別設定 CSS 才能列印背景呢?

根據 MDN 描述,print-color-adjust 預設情況下可能會選擇忽略所有背景並調整文字顏色,除了能最佳化對比度以適合在白紙上閱讀,也能節省墨水,難怪預設值叫做 economy(經濟)。

監聽列印狀態做額外判斷

上面使用 CSS 處理是比較理想的解法,但人生總是會充滿意外…

有時候不知道是套件衝突或是單純手殘,即使寫了 CSS 卻依然不 work,又或是列印需要呈現的畫面差異已經不是單純用 CSS 能解決的範疇。

這時候就只能找其他方法了…例如透過 addEventListener 來監聽狀態列印:

onMounted(() => {
  window.addEventListener("beforeprint", (event) => {
    console.log("列印準備開始");
  });
 
  window.addEventListener("afterprint", (event) => {
    console.log("列印結束或列印視窗關閉");
  });
});

但這會遇到一個問題,不論是透過 window.print(); 或是瀏覽器觸發列印,瀏覽器的列印視窗總會先比 addEventListener 的觸發還早出現。

所以我手動調整了一下觸發列印的方式:

// Vue 中綁定 v-show 來控制列印時顯示指定的 DOM
const isPrinting = ref(false);
 
// 觸發列印
function printPageAsPDF() {
  isPrinting.value = true;
  setTimeout(() => {
    window.print();
  }, 0);
}
 
onMounted(() => {
  // 偵測到結束列印就會改變 isPrinting 狀態
  window.addEventListener("afterprint", (event) => {
    isPrinting.value = false;
  });
});

但這個方式有個致命缺點。因為原理其實等同於先替換畫面後再觸發列印,所以若不是透過網頁中的列印按鈕來觸發列印,例如像是瀏覽器中按右鍵、快捷鍵 Command + P 觸發列印,就不會先被替換成指定的列印頁面。

結語

當今遇到網頁有需要列印的需求的實在不多,以上方法幾乎都能解決手邊專案的問題,這篇稍微筆記一下。

更多要觸發列印可能都是為了要存 PDF 的需求,如果後端能處理最好,又或是得考慮 jsPDF 之類的套件,雖然要處理也蠻麻煩的。

未來如果有更好的方式再回來補充,也歡迎留言分享給我不同的作法。

參考資料