前端如何實現(xiàn)一鍵截圖功能?
前言
網(wǎng)頁截圖功能目前也是非常常見的需求, 尤其是在在線教育領(lǐng)域. 我們朋友圈的微信海報, 活動海報等, 一般都是運營/市場人員通過設(shè)計工具設(shè)計而成, 但是如何更好的映射到自己的服務(wù)體系里面, 比如H5頁面中, 植入更多信息收集, 交互能力. 這一塊的應(yīng)用探索, 頁面截圖是一個非常好的解決方案.
接下來筆者就來復(fù)盤一下如何基于網(wǎng)頁, 一鍵生成頁面海報的功能, 并將此能力, 集成到筆者的開源項目H5-Dooring中為編輯器賦能.
正文
在實現(xiàn)具體功能之前, 我們先看看具體的實現(xiàn)效果:

從演示中我們可以看出, 我們最終目標(biāo)是實現(xiàn)在PC端生成H5頁面的截圖, 所以可能會涉及到以下幾個問題:
- 如何實現(xiàn)將頁面轉(zhuǎn)化為圖片
- 如何實現(xiàn)H5效果模擬并截取實際的H5頁面
我們可以先來想想實現(xiàn)思路, 如何能基于dom轉(zhuǎn)化為圖片? 這塊技術(shù)也是老生常談的課題了, 我們都知道可以用canvas來實現(xiàn), 大致流程如下:

我們?nèi)绻迷膶崿F(xiàn)方案, 大致要經(jīng)歷以上幾個步驟, 其中第二步是關(guān)鍵環(huán)節(jié)也是最復(fù)雜的一步, 我們需要手動實現(xiàn)dom到canvas的映射, 最后轉(zhuǎn)化為標(biāo)準(zhǔn)的canvas繪圖對象. 當(dāng)然現(xiàn)成也有很多庫可以直接幫我們簡化這一步驟, 比如html2canvas, dom-to-image. 接下來我們就來解決第一個問題.
如何實現(xiàn)將頁面轉(zhuǎn)化為圖片
在親自調(diào)研了html2canvas庫并使用的過程中, 筆者發(fā)現(xiàn)了很多問題, 比如如果樣式中出現(xiàn)%單位, 或者有一些圖片背景的問題, 導(dǎo)致html2canvas并沒有很好的work, 而且渲染還原度和清晰度都有問題, 所以筆者暫時沒有深入研究(不過這些問題可以通過修改庫本身解決), 后面筆者直接用了dom-to-image, 發(fā)現(xiàn)使用起來很簡單, 而且?guī)缀醪粫霈F(xiàn)上面說的這些問題, 所以筆者果斷采用了dom-to-image, 后面看了該庫的源碼, 感覺寫的也很優(yōu)雅易懂, 后期做二次開發(fā)應(yīng)該問題不是很大. 我們可以看看其官網(wǎng)的基本使用:
- // 引入
- import domtoimage from 'dom-to-image';
- // 生成圖片
- domtoimage.toPng(node)
- .then(function (dataUrl) {
- var img = new Image();
- img.src = dataUrl;
- document.body.appendChild(img);
- })
- .catch(function (error) {
- console.error('oops, something went wrong!', error);
- });
用法也很簡單, 而且它提供了足夠多的配置項, 我們可以靈活配置.

第一個問題就這么解決了, 不過在使用過程中發(fā)現(xiàn)圖片模糊的問題, 這塊網(wǎng)上也有很多解決方案. 比如先放大dom, 在處理成canvas最后生成圖片的時候在縮小等, 這塊筆者就不一一舉例了.
如何實現(xiàn)H5效果模擬并截取實際的H5頁面
因為我們設(shè)計的H5頁面都在pc端完成的, 所以要想生成H5預(yù)覽圖, 無非是本地模擬尺寸, 進(jìn)行渲染, 具體方案如下:
- 采用iframe作為H5頁面容器去生成截圖
- 直接限制寬度在當(dāng)前頁面生成截圖
- 采用服務(wù)端爬蟲一鍵模擬手機訪問生成截圖
上面說的方案都可以嘗試, 第三種方案筆者之前也開源過爬蟲應(yīng)用來解決這個問題, 感興趣的可以研究了解一下, 我們很明顯會選擇第一種方案來實現(xiàn), 就如演示中的, 我們看到的彈窗中的H5其實是在iframe中渲染的:
實現(xiàn)思路有了, 該問題也就很好實現(xiàn)了, 我們只需要在父頁面和iframe實現(xiàn)消息通信即可, 比如在iframe加載完成之后手動通知iframe截取自身. 基本實現(xiàn)代碼如下:
- // 編輯器頁面, 也就是父頁面
- // 定義截圖子頁面句柄函數(shù)
- window.getFaceUrl = (url) => {
- setFaceUrl(url)
- setShowModalIframe(false)
- }
- // iframe頁面, 也就是預(yù)覽頁面
- const generateImg = (cb:any) => {
- domtoimage.toBlob(refImgDom.current,
- {
- width,
- height,
- }
- )
- .then(function (blob:Blob) {
- const formData = new FormData();
- formData.append('file', blob, 'tpl.jpg');
- req.post('/files/upload/free', formData).then((res:any) => {
- cb && cb(res.url)
- })
- })
- .catch(function (error:any) {
- console.error('oops, something went wrong!', error);
- });
- }
- // 觸發(fā)父頁面的方法,將圖片傳給父頁面
- generateImg((url:string) => {
- parent.window.getFaceUrl(url);
- })