淺析js實(shí)現(xiàn)網(wǎng)頁截圖的兩種方式
Web端的截圖(生成圖片)并不算是個(gè)高頻的需求,資料自然也不算多,查來查去,也不過Canvas 和 SVG兩種實(shí)現(xiàn)方案,原理大概相似,都非真正義上的截圖而是把DOM轉(zhuǎn)為圖片,然而實(shí)現(xiàn)方式卻截然不同。
Canvas 實(shí)現(xiàn)
如何將dom轉(zhuǎn)換成canvas圖片?自然是要一點(diǎn)點(diǎn)畫到canvas里,想想都是件麻煩事。通過分析github的知名截圖庫 niklasvh/html2canvas (7k+ star)的源碼,梳理了其大致的思路:
- 遞歸取出目標(biāo)模版的所有DOM節(jié)點(diǎn),填充到一個(gè)rederList,并附加是否為頂層元素/包含內(nèi)容的容器 等信息
- 通過z-index postion float等css屬性和元素的層級(jí)信息將rederList排序,計(jì)算出一個(gè)canvas的renderQueue
- 遍歷renderQueue,將css樣式轉(zhuǎn)為setFillStyle可識(shí)別的參數(shù),依據(jù)nodeType調(diào)用相對(duì)應(yīng)canvas方法,如文本則調(diào)用fillText,圖片drawImage,設(shè)置背景色的div調(diào)用fillRect等
- 將畫好的canvas填充進(jìn)頁面
無論是排序優(yōu)先級(jí)的計(jì)算還是從css到canvas的轉(zhuǎn)換,毫無疑問都是些巨麻煩的事,尤其是放在真實(shí)的業(yè)務(wù)場(chǎng)景里,DOM模版中往往會(huì)包含復(fù)雜的樣式與排版,html2canvas 足足用了20多個(gè)js來實(shí)現(xiàn)這層轉(zhuǎn)換,復(fù)雜成度可見一斑。索性,我們不需要再重新造一遍輪子。
使用canvas轉(zhuǎn)化的話靈活性較高,環(huán)境依賴上也只需要確保瀏覽器支持canvas就可以了,但它有個(gè)顯著的缺點(diǎn):慢。原因自然是因?yàn)榇罅康挠?jì)算與遞歸調(diào)用,這是無可避免的。不過html2canvas代碼中大量使用了Promise,所以html2canvas 支持異步操作。
限制:
- 無法跨域跨域資源
- 無法渲染iframe,flash等內(nèi)容,但目前支持svg
值得一提的是,盡管html2canvas主頁表示它還處于實(shí)驗(yàn)室環(huán)境,但自14年起便已經(jīng)被Twitter 等用在了生產(chǎn)環(huán)境,所以雖然有諸多限制,穩(wěn)定性應(yīng)該還是保障的。
canvas如此復(fù)雜,那么有沒有一種更簡單的方法呢?
自然是有的,那便是SVG
SVG實(shí)現(xiàn)
首先,svg本來就是矢量圖形;其次,svg是可以用xml描述的;再其次,用來描述svg的標(biāo)簽里有個(gè) foreignObject標(biāo)簽,這個(gè)標(biāo)簽可以加載其它命名空間的xml(xhtml)文檔。也就是說,如果使用svg的話,我們不再需要一點(diǎn)點(diǎn)的遍歷,轉(zhuǎn)換節(jié)點(diǎn);不用再計(jì)算復(fù)雜的元素優(yōu)先級(jí),只需要一股腦的將要渲染的DOM扔進(jìn)<foreignObject></foreignObject>就好了,剩下的就交給瀏覽器去渲染。
讓我們理一理思路:
- 首先,我們要聲明一個(gè)基礎(chǔ)的svg模版,這個(gè)模版需要一些基礎(chǔ)的描述信息,最重要的,它要有<foreignObject></foreignObject>這對(duì)標(biāo)簽
- 將要渲染的DOM模版模版嵌入foreignObject
- 利用Blob構(gòu)建svg圖像
- 取出URL,賦值給
<div id='text'> <h1 style="background-color: #ccc;width: 200px;height: 200px;" >Hello World</h1> </div>
//此代碼僅在chrome測(cè)試下通過 function html2Svg (domStr) { //創(chuàng)建模版字符串 var svgXML= `<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200"> <foreignObject width="100%" height="100%">${generateXML(html)}</foreignObject> </svg>` //利用Blob創(chuàng)建svg var svg = new Blob([svgXML], {type: 'image/svg+xml'}) //利用DOMURL.createObjectURL取出對(duì)象 var url = window.URL.createObjectURL(svg); var img = new Image() img.src = url return img } // 由于`foreignObject`只能引用XML文檔, // 所以我們需要對(duì)DOM進(jìn)行格式化 function generateXML (domStr) { var doc = document.implementation.createHTMLDocument(''); doc.write(html); doc.documentElement.setAttribute('xmlns', doc.documentElement.namespaceURI); doc = parseStyle(doc) console.log(doc) html = (new XMLSerializer).serializeToString(doc).replace('<!DOCTYPE html>',''); return html }
可以看到按這個(gè)思路來實(shí)現(xiàn)非常簡單,并且沒有了復(fù)雜的計(jì)算和遞歸,渲染速度自然要優(yōu)于前者。然而使用svg,需要考慮諸多的限制問題。一個(gè)最為嚴(yán)肅的問題在于:SVG無法加載外部資源,也就是說,在svg里面,無論是還是 或者css中的背景圖,這些資源都是無法加載的。在使用canvas實(shí)現(xiàn)時(shí),因?yàn)槲覀兪且粋€(gè)node一個(gè)node去畫,所以不存在資源引用的問題。但使用svg實(shí)現(xiàn),相當(dāng)于我們把文檔交給SVG再來來渲染一遍,這對(duì)于我們來說是其實(shí)是無法控制的黑盒操作,是受SVG限制的
萬幸,一個(gè)昵稱為Christoph Burgmer的小哥寫了一個(gè)名為rasterizeHTML.js 的庫,通過一系列的hack技巧替我們繞過了許多限制。我知道你很好奇他是怎么做到的。 簡單來講,rasterizeHTML.js在我們的基礎(chǔ)實(shí)現(xiàn)上做了這些hack:
- 將<img/>的url 轉(zhuǎn)為 dataURI
- 將background-color從style中取出,修改url后重新插入樣式表
- 將link的的樣式通過ajax down下來然后注入<style></sytle>
- 詳見源碼...
當(dāng)然, rasterizeHTML.js能幫我們做的也不過是處理資源引用問題和瀏覽器兼容問題,更多的SVG的限制是無法繞過的,該庫的文檔正式列出了足足一整頁的限制,讓人讀完后心中一涼。比如:
- 跨域資源無法加載
- 如lazyload等通過js加載的資源無法加載
- 內(nèi)聯(lián)或js操作background-image無法加載
- 詳見文檔
思考下rasterizeHTML.js的原理便可理解這些限制無法避免的原因: rasterizeHTML.js只能對(duì)已經(jīng)存在的靜態(tài)資源進(jìn)行處理,而對(duì)js動(dòng)態(tài)生成并不能實(shí)時(shí)處理。
目前rasterizeHTML.js已經(jīng)被用于知乎-意見反饋功能。
參考
源碼https://developer.mozilla.org/en-US/docs/Web/API/CanvasAPI/DrawingDOMobjectsintoacanvas
以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
JS實(shí)現(xiàn)簡單可拖動(dòng)的模態(tài)框
這篇文章主要為大家詳細(xì)介紹了JS實(shí)現(xiàn)簡單可拖動(dòng)的模態(tài)框,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-07-07JavaScript實(shí)現(xiàn)異步任務(wù)循環(huán)順序執(zhí)行詳解
這篇文章主要為大家詳細(xì)介紹了JavaScript中實(shí)現(xiàn)異步任務(wù)循環(huán)順序執(zhí)行的方法,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下2023-03-03JS實(shí)現(xiàn)動(dòng)態(tài)無縫輪播
這篇文章主要為大家詳細(xì)介紹了JS實(shí)現(xiàn)動(dòng)態(tài)無縫輪播,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-01-01