頁(yè)面截圖html2canvas、dom-to-image、html-to-image、modern-screenshot
當(dāng)前位置:點(diǎn)晴教程→知識(shí)管理交流
→『 技術(shù)文檔交流 』
需求背景頁(yè)面上有大量的圖表,用戶的述求是能對(duì)頁(yè)面截屏從而直接分享給別人。 那么就有小伙伴要發(fā)問(wèn)了,為什么不直接把頁(yè)面鏈接分享給別人呢? 首先,頁(yè)面可能有權(quán)限校驗(yàn),被分享的人可能沒(méi)有該頁(yè)面的訪問(wèn)權(quán)限,而圖片不會(huì)有這個(gè)問(wèn)題;其次,實(shí)踐表明,如果分享的是鏈接,用戶的點(diǎn)擊意愿很低,如果不是直接相關(guān)的人往往不會(huì)點(diǎn)開(kāi)鏈接查看,而如果是圖片的話,非常直觀,往往第一眼就傳遞了很多信息給被分享的人。 那么又有小伙伴要發(fā)問(wèn)了,既然如此,為何不讓用戶自己裝一個(gè)截屏軟件自己截算了? 考慮兩個(gè)點(diǎn),第一是不一定所有用戶都有一個(gè)好用的截屏的軟件(特別是在Mac上,大伙應(yīng)該深有體會(huì)),并且頁(yè)面如果需要滾動(dòng)截屏,用戶的操作就會(huì)比較麻煩,因此頁(yè)面上能提供一個(gè)一鍵截屏的按鈕就十分便利了;第二是如果由頁(yè)面提供截圖能力,可以很好地定制最終圖片上所呈現(xiàn)的頁(yè)面,比如可以調(diào)整一下布局,修改某些元素。 不過(guò)需要注意的是,我們要實(shí)現(xiàn)的截屏并不是一個(gè)真正的截屏,而是相當(dāng)于dom的快照,針對(duì)傳入的dom生成圖片。 方案調(diào)研那么咱就來(lái)研究研究,市面上都有哪些截屏的方案。 后端方案一種比較常見(jiàn)的方案,是在服務(wù)端使用puppeteer(或者playwright啥的)起一個(gè)無(wú)頭瀏覽器,渲染完頁(yè)面后截圖返回給前端,比如金山文檔就是這么做的。 但是吧,這種方案的缺陷很明顯。首先毋庸置疑的是,服務(wù)端的壓力會(huì)變大,成本會(huì)變高;其次,最終生成的圖片往往與用戶所看到的頁(yè)面有些出入,比如金山文檔的截屏,如果源文檔是些奇奇怪怪的字體,最終生成的圖片里的字體就會(huì)是默認(rèn)字體,另外布局什么的也可能會(huì)不一致; 源文檔: 生成的圖片: 那么后端方案優(yōu)點(diǎn)也就與缺點(diǎn)一一對(duì)應(yīng),首先是對(duì)用戶設(shè)備的消耗較小,性能較差的設(shè)備也能使用;其次是對(duì)于同一頁(yè)面,后端方案生成圖片能夠完全一致,不會(huì)因?yàn)橛脩舻臋C(jī)型不同導(dǎo)致頁(yè)面布局發(fā)生變化,而且更重要的一點(diǎn)是,生成圖片基本上都依賴于canvas,而canvas這東西有個(gè)坑,它對(duì)寬、高、面積有一定的限制,并且不同瀏覽器、不同設(shè)備的限制還不太一樣,并且同一設(shè)備同一瀏覽器也會(huì)因?yàn)橛脩舻脑O(shè)備可用資源受到影響,在生成canvas之前也不能拿到這個(gè)限制,這個(gè)限制在IOS設(shè)備上最為嚴(yán)重(有意思的是canvas是蘋(píng)果提出的標(biāo)準(zhǔn)),參考javascript - Maximum size of a element - Stack Overflow,因此采用后端方案能夠保證結(jié)果的一致性。 前端方案有的小伙伴會(huì)說(shuō)了,瀏覽器自帶截屏功能的,直接用多好呀。是的,瀏覽器有一個(gè)截屏功能,但是我們?cè)贘S代碼里并沒(méi)法直接調(diào)用,并且瀏覽器自帶的截屏,也無(wú)法實(shí)現(xiàn)上述所說(shuō)的修改頁(yè)面元素的能力。 瀏覽器自帶截屏: 那么比較靠譜的前端截屏方案其實(shí)就兩種,一種自己實(shí)現(xiàn)渲染,將dom一一渲染到canvas上后生成圖片,比如html2canvas;另一種是借助foreignObject,將svg繪制到canvas上再生成圖片,代表作為dom-to-image。 html2canvashtml2canvas可以說(shuō)是最古老的前端截屏實(shí)現(xiàn)方案了,也稱得上是獨(dú)一檔的實(shí)現(xiàn)。它的原理簡(jiǎn)單來(lái)說(shuō)就是克隆傳入的dom,遍歷克隆樹(shù),通過(guò)getBoundingClientRect獲取元素的位置、大小,getComputedStyle獲取元素樣式,然后使用canvas的底層API,一點(diǎn)一點(diǎn)畫(huà)出來(lái)的。 可想而知,這個(gè)過(guò)程是多么復(fù)雜,相當(dāng)于自己實(shí)現(xiàn)了一套渲染引擎,并且css越來(lái)越復(fù)雜,想要完全繪制到canvas,夠嗆,所以html2canvas現(xiàn)在有一個(gè)很大的缺點(diǎn)就是對(duì)css的支持不夠好。 另外,由于它自建了一套渲染,需要處理的情況非常多,所以包體積相當(dāng)大,官網(wǎng)標(biāo)注的gzip壓縮后也有45kB。 除了上述原因外,真正讓我放棄這個(gè)庫(kù)的原因是,它太老了,它真的太老了,作為一個(gè)十幾年前的庫(kù),它現(xiàn)在已經(jīng)年久失修,上次更新都是兩年前,而且看著只是文檔修改。 并且已經(jīng)堆積了800+ issue沒(méi)有處理,基本上是不維護(hù)狀態(tài)了。 更有意思的是,即使這個(gè)庫(kù)已經(jīng)存在了十幾年,并且有大量頁(yè)面將其應(yīng)用到了生產(chǎn)環(huán)境,其中不乏一些大公司產(chǎn)品,比如騰訊文檔(別問(wèn)我怎么知道的,問(wèn)就是我寫(xiě)的),但是它的作者仍在Readme里邊寫(xiě)到: dom-to-imagedom-to-image的基本原理十分簡(jiǎn)單,不需要做什么復(fù)雜的渲染,利用到了svg元素的foreignObject: 只需要把dom丟到foreignObject里邊,就會(huì)在svg里邊渲染出來(lái),因?yàn)槭菫g覽器的標(biāo)準(zhǔn),也不用擔(dān)心對(duì)css的支持不夠友好: 其實(shí),到這一步,你會(huì)發(fā)現(xiàn)已經(jīng)達(dá)到將dom轉(zhuǎn)成圖片的目的了,svg本來(lái)就是圖片。但是你可能會(huì)需要其他格式的圖片,并且這樣生成的svg體積實(shí)在是大了點(diǎn),包含了大量冗余的信息。所以這里還是用到canvas,通過(guò)drawImage把svg畫(huà)到canvas上,再通過(guò)canvas的toDataUrl生成圖片鏈接。 從體積上看,不到10kB,是完全可以接受的: 看看它的代碼倉(cāng)庫(kù),可以看到已經(jīng)七八年不更新了,并且有200+ issue沒(méi)有處理,也基本上處于不維護(hù)狀態(tài)了。: 如果能夠滿足需求,也不是不能用,遺憾的是,不太能滿足我的需求。 首先是資源跨域問(wèn)題,其實(shí)資源本身是支持跨域的,但是原始html中的標(biāo)簽沒(méi)有加上crossorigin屬性,導(dǎo)致生成圖片時(shí)會(huì)報(bào)跨域錯(cuò)誤,像頁(yè)面里的圖片、外鏈css啥的得做點(diǎn)特殊處理才能用。另外還有些奇奇怪怪的問(wèn)題,可以看看issue,反正是不太能用。 dom-to-image-moredom-to-image-more聽(tīng)名字也能聽(tīng)出來(lái)是fork的dom-to-image,解決了dom-to-image的部分bug,增加了一些能力。最重要的能力應(yīng)該是解決了上述提到的跨域問(wèn)題,它把link標(biāo)簽做了一下攔截,使用fetch去請(qǐng)求對(duì)應(yīng)的src,加上了跨域配置,然后再對(duì)返回結(jié)果進(jìn)行處理。另外還有一個(gè)有意思的點(diǎn),在dom-to-image中,獲取元素的樣式是通過(guò)document.getComputedStyle拿到每個(gè)dom節(jié)點(diǎn)的樣式,然后通過(guò)行內(nèi)樣式插入到對(duì)應(yīng)的標(biāo)簽上,會(huì)導(dǎo)致最后生成的圖片上包含了大量的行內(nèi)樣式,體積自然就比較大;而dom-to-image-more做了一個(gè)優(yōu)化,利用沙盒獲取到了元素的默認(rèn)樣式,再和getComputedStyle作比較,只插入不同于默認(rèn)樣式的屬性,從而極大地減小了圖片的體積,自然而然,這個(gè)復(fù)雜度高了點(diǎn),生成圖片的耗時(shí)稍微長(zhǎng)點(diǎn)。 體積很理想,不到6kB: 之前看最新的更新在兩年前,但是近期好像又有更新,說(shuō)明還是有人在維護(hù)的: 但是最終還是沒(méi)有用它,因?yàn)橛袀€(gè)痛點(diǎn),在我的場(chǎng)景下用了很多icon,而這些icon都是svg格式的,它們通過(guò)defs - SVG定義了一次,然后使用時(shí)都是通過(guò) - SVG引用的;但是這個(gè)庫(kù)沒(méi)有處理這種情況,導(dǎo)致生成圖片時(shí)只復(fù)制了use元素,而沒(méi)有將其對(duì)應(yīng)的defs元素復(fù)制過(guò)去,從而導(dǎo)致最終生成的圖片上丟失了這些icon。 html-to-imagehtml-to-image也是fork的dom-to-image,修了部分bug,增加了一些能力。這個(gè)庫(kù)相較于dom-to-image,特點(diǎn)是優(yōu)化了文件結(jié)構(gòu),增加typescript支持,對(duì)比上述的dom-to-image-more,處理好了svg use和svg defs的情況,在有use的情況下會(huì)去找到對(duì)應(yīng)的defs元素并添加進(jìn)來(lái)。但是,它沒(méi)有解決跨域問(wèn)題。 另外還有個(gè)痛點(diǎn),之前提到的icon,它們的樣式吧,上面我們提到了,是通過(guò)getComputedStyle獲取到,然后插入到行內(nèi)樣式實(shí)現(xiàn)的;對(duì)于普通的dom元素而言,這樣做沒(méi)有問(wèn)題,因?yàn)檫@些dom使用的地方就是它們定義的地方;但是對(duì)于svg defs和svg use這樣的元素而言,在定義時(shí)它的樣式就已經(jīng)被行內(nèi)樣式寫(xiě)死了,使用的時(shí)候就沒(méi)辦法覆蓋定義時(shí)的樣式,導(dǎo)致我的彩色icon全變成黑色了: 原圖: 生成的圖片: 看了下源代碼,確實(shí)沒(méi)有針對(duì)這點(diǎn)進(jìn)行處理,所以還是放棄了,另外可想而知的是,像webcomponent這樣定義和使用分離的情況,估計(jì)也存在樣式不能覆蓋的問(wèn)題。 modern-screenshotmodern-screenshot也是基于dom-to-image,但它不是直接fork的dom-to-image,而是上面提到的html-to-image,所以相當(dāng)于是dom-to-image的孫子輩了。 這個(gè)庫(kù)既然是fork的html-to-image,自然也就繼承了html-to-image良好的文件結(jié)構(gòu)以及優(yōu)秀的ts支持;并且這個(gè)庫(kù)有意思的是,它還整合了dom-to-image-more的優(yōu)化,不會(huì)產(chǎn)生跨域的問(wèn)題了;對(duì)于svg use和svg defs,它更進(jìn)一步,復(fù)用已有的defs,減小了生成圖片的體積;另外還有個(gè)點(diǎn),它用到了webworker并行地發(fā)起網(wǎng)絡(luò)請(qǐng)求。 東抄抄西補(bǔ)補(bǔ),modern-screenshot是目前我看到的效果最理想的前端截屏方案,并且這個(gè)庫(kù)的作者仍在維護(hù): 最近的更新發(fā)生在三周前,包體積gzip壓縮后不到10kB,完全可以接受。 美中不足的是,這個(gè)庫(kù)依然沒(méi)有解決上述提到的svg use樣式不能覆蓋問(wèn)題。其實(shí)想想也明白,通過(guò)getComputedStyle再寫(xiě)入行內(nèi)樣式的方式,這個(gè)問(wèn)題是避免不了的。不過(guò),考慮到svg defs元素一般都是icon在使用,而這些icon一般來(lái)說(shuō)不會(huì)被外界樣式所影響,所以針對(duì)svg defs和svg use標(biāo)簽,我們不通過(guò)getComputedStyle獲取其樣式,而是直接使用dom.cloneNode獲取的樣式,這樣就不會(huì)寫(xiě)死行內(nèi)樣式,從而解決了這個(gè)問(wèn)題。于是給該項(xiàng)目提了一個(gè)PR,也順利合入: 當(dāng)然這種解法并不嚴(yán)謹(jǐn),但是絕大部分情況下應(yīng)該夠用,至少在我的場(chǎng)景下已經(jīng)足夠滿足需求,因此最終我也是選擇了使用modern-screenshot來(lái)實(shí)現(xiàn)截屏的需求。 作者:超級(jí)無(wú)敵大怪獸 鏈接:https://juejin.cn/post/7339671825646338057 來(lái)源:稀土掘金 著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。 該文章在 2024/2/27 9:57:06 編輯過(guò) |
關(guān)鍵字查詢
相關(guān)文章
正在查詢... |