本篇要解決的問題
幾年前公司有一個案子,是要讓使用者可以做一個簡單的卡片,然後把這卡片存成圖檔分享給朋友,那時就有寫過一次把指定的 <div>
存成圖檔的 JS,結果,案子太久遠了,竟然沒把當時寫存圖檔的 function 給存下來 XD,直到前陣子前同事在問才想起這件事。
把網頁的區塊存成圖檔,總覺得以後有機會再用到,就先整理這篇筆記文,以後真的要用的時候就可以直接 複製貼上 複習一下怎麼寫。
要把頁面存成圖檔有二個步驟,各自用了一個套件。
- 把指定的 HTML 區塊轉成 Canvas:html2canvas
- 把 Canvas 轉存成圖檔:Canvas2image
本篇有做一個 Demo 出來,在繼續往下閱讀前大家可以先玩玩看:
https://letswritetw.github.io/letswrite-web-to-image/
另外,August 也有寫了另一篇是使用 DOM to Image 這套來進行截圖的筆記文,文章連結如下:
將指定區塊轉成 Canvas
html2canvas 這個套件很方便,基本上官方文件上就明明白白的寫了使用方法:
const el = document.querySelector("#something"); html2canvas(el, [option]) .then(canvas => { document.body.appendChild(canvas) });
option
是選填,可寫可不寫,項目請參考官方文件的 Configuration,如果去爬一些教學文,會看見最常寫到的就是 useCORS: true
。
useCORS: true
是在我們要抓圖的 div 裡如果存在其它網域的圖片,就必須告訴 html2canvas 說我們有跨網域的圖片。
啊不過,經 August 人體實驗證明,當有圖片是跨網域的來源,很常會抓圖失敗,失敗機率是 3 張失敗 1 ~ 2 張,如果真要用這套把 div 轉成 Canvas,div 裡的圖要用同網域的會比較保險。
在本篇的範例裡,除了 useCORS
,也有用到 backgroundColor
,直接把空白的地方填入網頁裡的背景色,看上去才會無違和。
如果要好玩一點,也可以背後再接一個 Color Thief 抓出圖檔的主要色系當背景色,就不用寫死背景色的值。
把 Canvas 轉存成圖檔
Canvas2image 這套很妙,一開始 August 在寫 Demo 時,JS 是引用 CDN 上的絕對路徑,然後一直無法自訂圖檔的檔名,可是看官方的原始碼裡又確實有參數是讓人放檔名的。
如此如此,這般這般,就決定直接從官方 GitHub 上下載 JS 檔來用,然後果不其然就直接成功設定圖檔檔名勒。
只是官方給的 JS 要用 import
才能使用,如果對 import
還不熟的朋友,可以考慮下載 August 前陣子寫的 開發初始檔,就能直接編譯 ES6 的 JS 檔。
Canvas2image 的使用方式很簡單:
Canvas2Image.saveAsImage(canvasObj, width, height, type, filename) Canvas2Image.saveAsPNG(canvasObj, width, height, filename) Canvas2Image.saveAsJPEG(canvasObj, width, height, filename) Canvas2Image.saveAsGIF(canvasObj, width, height, filename) Canvas2Image.saveAsBMP(canvasObj, width, height, filename)
官方的說明文件沒有寫上 filename
這個參數,應該是漏了寫,因為 August 去看了 JS 原始碼是有這個參數的。
canvasObj
就直接寫我們從 html2canvas 取得的 canvas
就行。
width
、height
這二個的值 canvas 本身就會有,如果沒有其它需求可以直接寫 canvas.width
、canvas.height
。
第一個的 saveAsImage
需要多帶 type
參數,寫上要什麼檔案類型就行,比方 png
、jpeg
。不過建議就直接用後面四個 function 吧,不讓使用者選直接存成我們希望使用者擁有的類型省事多了。
總合上面二個步驟,程式碼簡單整理如下。
// 將指定區塊轉成 Canvas function htmlToCanvas(id) { const el = document.getElementById(id); html2canvas(el).then(canvas => { canvasToImage(canvas); }); } // 把 Canvas 轉存成圖檔 function canvasToImage(canvas) { const filename = 'xxxxx'; Canvas2Image.saveAsPNG( canvas, canvas.width, canvas.height, filename ); }
截多個 div 存成圖
假設今天我們要截的圖是頁面上幾個 div 併在一起的,方法很簡單,在本篇的 Demo 裡就有寫出來,那個「截圖 Section 1 + 2」的按鈕就是在示範這件事。
Demo 中有二個 sectoin,分別是 <section id="section1">
、<section id="section2">
。
我們的目標是一個按鈕,存出來的圖片包含這二個 section。
首先,建立第三個 section,隨意取叫 <section id="section3">
,然後用 JS 把 1、2 二個 section 的 HTML 塞進去。
塞進 3 後,就可以用 html2canvas 來指定 3 轉成 Canvas。
這樣會遇到一個小狀況,就是 html2canvas 要的是真的要存在頁面上的區塊才行,而當我們把 1、2 的內容都塞到 3 後,3 就會有內容,就會被使用者看到。
把 3 寫上 display: none
是不行的,因為在網頁上就不會有寬高,會被認定是圖片不存在,就無法透過 html2canvas 轉成 Canvas。
山不轉路轉,藏起來不行,那我們就把它移到畫面之外,讓使用者在螢幕上看不到就行。
August 在 Demo 裡的作法如下:
html, body { width: 100; overflow-x: hidden; } #section3 { position: absolute; left: -100%; }
這樣,就可以順利截圖,而使用者是看不到那個被我們偷偷塞了內容的 section3 的。
範例及原始碼
原始碼存在 GitHub 上,也用了 GitHub Pages 生成 Demo 頁。
要取用前麻煩分享本篇,或是 GitHub 上點個星星,你的小小動作對本站都是大大的鼓勵。
原始碼:https://github.com/letswritetw/letswrite-web-to-image
Demo:https://letswritetw.github.io/letswrite-web-to-image/
最後一種做法很妙!感謝分享,目前(2023/12/12)引用 CDN 是可以自訂檔案名稱
實作此截圖功能時,發現若是圖片跨域會無法正確轉存圖檔
現在比較常用的是 dom-to-image,可以改用這套試試:https://www.letswrite.tw/dom-to-image/