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