1. 程式人生 > >html2canvas實現瀏覽器截圖的原理(包含原始碼分析的通用方法)

html2canvas實現瀏覽器截圖的原理(包含原始碼分析的通用方法)

DevUI是一支兼具設計視角和工程視角的團隊,服務於華為雲DevCloud平臺和華為內部數箇中後臺系統,服務於設計師和前端工程師。

官方網站:devui.design

Ng元件庫:ng-devui(歡迎Star)

官方交流:新增DevUI小助手(devui-official)

DevUIHelper外掛:DevUIHelper-LSP(歡迎Star)

 

引言

有時使用者希望將我們的報表頁面分享到其他的渠道,比如郵件、PPT等,每次都需要自己截圖,一是很麻煩,二是截出來的圖大小不一。

有沒有辦法在頁面提供一個下載報表頁面的功能,使用者只需要點選按鈕,就自動將當前的報表頁面以圖片形式下載下來呢?

html2canvas庫就能幫我們做到,無需後臺支援,純瀏覽器實現截圖,即使頁面有滾動條也是沒問題的,截出來的圖非常清晰。

這個庫的維護時間非常長,早在2013年9月8日它就釋出了第一個版本,比Vue的第一個版本(2013年12月8日)還要早。

截止到今天2020年12月18日,html2canvas庫在github已經有22.3k star,在npm的周下載量也有506k,非常了不起!

上一次提交是在2020年8月9日,可見作者依然在很熱情地維護著這個庫,而且用TypeScript重構過,不過這個庫的作者非常保守,哪怕已經持續不斷地維護了7年,他在README裡依然提到這個庫目前還在實驗階段,不建議在生產環境使用。

事實上我很早就將這個庫用在了生產環境,這篇文章就來分析下這個神奇和了不起的JavaScript庫,看看它是怎麼實現瀏覽器端截圖的。

1 如何使用

在介紹html2canvas的原理之前,先來看看怎麼使用它,使用起來真的非常簡單,幾乎是1分鐘上手。

使用html2canvas只要以下3步:

  1. 安裝
  2. 引入
  3. 呼叫

Step 1: 安裝

npm i html2canvas

Step 2: 引入

隨便在一個現代框架的工程專案中引入html2canvas

import html2canvas from 'html2canvas';

Step 3: 截圖並下載

html2canvas就是一個函式,在頁面渲染完成之後直接呼叫即可。

檢視渲染完成的事件:
1. Angular的ngAfterViewInit方法
2. React的componentDidMount方法
3. Vue的mounted方法

可以只傳一個引數,就是你要截圖的DOM元素,該函式返回一個Promise物件,在它的then方法中可以獲取到繪製好的canvas物件,通過呼叫canvas物件的toDataURL方法就可以將其轉換成圖片。

拿到圖片的URL之後,我們可以

  1. 將其放到<img>標籤的src屬性中,讓其顯示在網頁中;
  2. 也可以將其放到<a>標籤的href屬性中,將該圖片下載到本地磁碟中。

我們選擇後者。

html2canvas(document.querySelector('.main')).then(canvas => {
  const link = document.createElement('a'); // 建立一個超連結物件例項
  const event = new MouseEvent('click'); // 建立一個滑鼠事件的例項
  link.download = 'Button.png'; // 設定要下載的圖片的名稱
  link.href = canvas.toDataURL(); // 將圖片的URL設定到超連結的href中
  link.dispatchEvent(event); // 觸發超連結的點選事件
});

是不是非常簡單?

引數

我們再來大致看一眼它的API,該函式的簽名如下:

html2canvas(element: HTMLElement, options: object): Promise<HTMLCanvasElement>

options物件可選的值如下:

NameDefaultDescription
allowTaint false 是否允許跨域影象汙染畫布
backgroundColor #ffffff 畫布背景顏色,如果在DOM中沒有指定,設定“null”(透明)
canvas null 使用現有的“畫布”元素,用來作為繪圖的基礎
foreignObjectRendering false 是否使用ForeignObject渲染(如果瀏覽器支援的話)
imageTimeout 15000 載入影象的超時時間(毫秒),設定為“0”以禁用超時
ignoreElements (element) => false 從呈現中移除匹配元素
logging true 為除錯目的啟用日誌記錄
onclone null 回撥函式,當文件被克隆以呈現時呼叫,可以用來修改將要呈現的內容,而不影響原始源文件。
proxy null 用來載入跨域圖片的代理URL,如果設定為空(預設),跨域圖片將不會被載入
removeContainer true 是否清除html2canvas臨時建立的克隆DOM元素
scale window.devicePixelRatio 用於渲染的縮放比例,預設為瀏覽器裝置畫素比
useCORS false 是否嘗試使用CORS從伺服器載入影象
width Element width canvas的寬度
height Element height canvas的高度
x Element x-offset canvas的x軸位置
y Element y-offset canvas的y軸位置
scrollX Element scrollX 渲染元素時使用的x軸位置(例如,如果元素使用position: fixed)
scrollY Element scrollY 渲染元素時使用的y軸位置(例如,如果元素使用position: fixed)
windowWidth Window.innerWidth 渲染元素時使用的視窗寬度,這可能會影響諸如媒體查詢之類的事情
windowHeight Window.innerHeight 渲染元素時使用的視窗高度,這可能會影響諸如媒體查詢之類的事情

忽略元素

options有一個ignoreElements引數可以用來忽略某些元素,從渲染過程中移除,除了設定該引數外,還有一種忽略元素的方法,就是在需要忽略的元素上增加data-html2canvas-ignore屬性。

<div data-html2canvas-ignore>Ignore element</div>

2 基本原理

介紹完html2canvas的使用,我們先來了解下它的基本原理,然後再分析細節實現。

它的基本原理其實很簡單,就是去讀取已經渲染好的DOM元素的結構和樣式資訊,然後基於這些資訊去構建截圖,呈現在canvas畫布中。

它無法繞過瀏覽器的內容策略限制,如果要呈現跨域圖片,需要設定一個代理。

3 主流程 html2canvas方法

基本原理很簡單,但原始碼裡面其實東西很多,我們一步一步來,先找到入口,然後慢慢除錯,走一遍大致的流程。

尋找入口檔案

拉取到原始碼,有很多方法可以找到入口檔案:

  • 方法一:最簡單的方法是直接全域性搜尋html2canvas,這種方法效率很低,而且要碰運氣,不推薦
  • 方法二:在專案中引入這個庫,呼叫它,跑起來,並在該方法前面打斷點進行除錯,一般能精確地找到入口檔案,推薦
  • 方法三:觀察下是否有webpack.config.js或者rollup.config.js的構建工具的配置檔案,然後在配置檔案中找到精確的入口檔案(一般是entryinput之類的屬性),推薦
  • 方法四:直接掃一眼目錄結構,一般入口檔案在src/core/packages之類的目錄下,檔名是index或者main,或者是模組的名字,有經驗的話可以用這個方法,找起來很快,強烈推薦

方法一:全域性搜尋

最簡單最容易想到的的方法,就是全域性搜尋關鍵字html2canvas,因為我們在不瞭解html2canvas的實現之前,我們接觸到的關鍵字就只有這一個。

但是全域性搜尋運氣不好的話,很可能搜出來很多結果,在裡面找入口檔案費時費力,比如:

42個檔案285個結果,找起來很麻煩,不推薦。

方法二:打斷點

在呼叫html2canvas的地方打一個斷點。

然後在執行到斷點處時,點擊向下的小箭頭,進入該方法。

因為在開發環境,很快我們就能發現入口檔案和入口方法在哪兒,這裡顯示的是html2canvas檔案,實際上這個檔案是構建之後的檔案,但是這個檔案的上下文給我們提供了找入口方法的資訊,這裡我們發現了renderElement方法。

這時我們可以嘗試全域性搜尋這個方法,很幸運直接找到了