Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 julia一区二区三区中文字幕,高清视频一区,国产精品综合网

          整合營銷服務(wù)商

          電腦端+手機端+微信端=數(shù)據(jù)同步管理

          免費咨詢熱線:

          知識就是金錢之:js中關(guān)于Blob對象的介紹與使用



          lob對象介紹

          一個Blob對象表示一個不可變的,原始數(shù)據(jù)的類似文件對象。Blob表示的數(shù)據(jù)不一定是一個JavaScript原生格式blob對象本質(zhì)上是js中的一個對象,里面可以儲存大量的二進制編碼格式的數(shù)據(jù)。

          創(chuàng)建blob對象

          創(chuàng)建blob對象本質(zhì)上和創(chuàng)建一個其他對象的方式是一樣的,都是使用Blob()的構(gòu)造函數(shù)來進行創(chuàng)建。構(gòu)造函數(shù)接受兩個參數(shù):

          第一個參數(shù)為一個數(shù)據(jù)序列,可以是任意格式的值。

          第二個參數(shù)是一個包含兩個屬性的對象{type:MIME的類型,endings:決定第一個參數(shù)的數(shù)據(jù)格式,可以取值為"transparent"或者"native"(transparent的話不變,是默認值,native的話按操作系統(tǒng)轉(zhuǎn)換)。}

          Blob()構(gòu)造函數(shù)允許使用其他對象創(chuàng)建一個Blob對象,比如用字符串構(gòu)建一個blob


          既然是對象,那么blob也擁有自己的屬性以及方法

          屬性

          布爾值,指示Blob.close()是否在該對象上調(diào)用過。關(guān)閉的blob對象不可讀。

          Blob對象中所包含數(shù)據(jù)的大小(字節(jié))。

          一個字符串,表明該Blob對象所包含數(shù)據(jù)的MIME類型。如果類型未知,則該值為空字符串。

          方法

          關(guān)閉Blob對象,以便能釋放底層資源。

          返回一個新的Blob對象,包含了源Blob對象中指定范圍內(nèi)的數(shù)據(jù)。其實就是對這個blob中的數(shù)據(jù)進行切割,我們在對文件進行分片上傳的時候需要使用到這個方法。

          看到上面這些方法和屬性,使用過HTML5提供的File接口的應(yīng)該都很熟悉,這些屬性和方法在File接口中也都有。其實File接口就是基于Blob,繼承blob功能并將其擴展為支持用戶系統(tǒng)上的文件,也就是說:

          File接口中的Flie對象就是繼承與Blob對象。

          blob對象的使用

          上面說了很多關(guān)于Blob對象的一些概念性的東西,下面我們來看看實際用途。

          分片上傳

          首先說說分片上傳,我們在進行文件上傳的時候,因為服務(wù)器的限制,會限制每一次上傳到服務(wù)器的文件大小不會很大,這個時候我們就需要把一個需要上傳的文件進行切割,然后分別進行上傳到服務(wù)器。

          假如需要做到這一步,我們需要解決兩個問題:

          首先怎么切割的問題上面已經(jīng)有過說明,因為File文件對象是繼承與Blob對象的,因此File文件對象也擁有slice這個方法,我們可以使用這個方法將任何一個File文件進行切割。

          代碼如下:


          通過上面的方法。我們就得到了一個切割之后的File對象組成的數(shù)組blobs;

          接下來要做的時候就是講這些文件分別上傳到服務(wù)器。

          在HTTP1.1以上的協(xié)議中,有Transfer-Encoding這個編碼協(xié)議,用以和服務(wù)器通信,來得知當前分片傳遞的文件進程。

          這樣解決了這兩個問題,我們不僅可以對文件進行分片上傳,并且能夠得到文件上傳的進度。

          粘貼圖片

          blob還有一個應(yīng)用場景,就是獲取剪切板上的數(shù)據(jù)來進行粘貼的操作。例如通過QQ截圖后,需要在網(wǎng)頁上進行粘貼操作。

          粘貼圖片我們需要解決下面幾個問題

          首先我們可以通過paste事件來監(jiān)聽用戶的粘貼操作:


          然后通過事件對象中的clipboardData對象來獲取圖片的文件數(shù)據(jù)。

          clipboardData對象介紹

          介紹一下clipboardData對象,它實際上是一個DataTransfer類型的對象,DataTransfer是拖動產(chǎn)生的一個對象,但實際上粘貼事件也是它。

          clipboardData的屬性介紹


          items介紹

          items是一個DataTransferItemList對象,自然里面都是DataTransferItem類型的數(shù)據(jù)了。

          屬性

          items的DataTransferItem有兩個屬性kind和type


          方法


          在原型上還有一些其他方法,不過在處理剪切板操作的時候一般用不到了。

          type介紹

          一般types中常見的值有text/plain、text/html、Files。


          有了上面這些方法,我們可以解決第二個問題即獲取到剪切板上的數(shù)據(jù)。


          最后我們需要將獲取到的數(shù)據(jù)渲染到網(wǎng)頁上。

          其實這個本質(zhì)上就是一個類似于上傳圖片本地瀏覽的問題。我們可以直接通過HTML5的File接口將獲取到的文件上傳到服務(wù)器然后通過講服務(wù)器返回的url地址來對圖片進行渲染。也可以使用fileRender對象來進行圖片本地瀏覽。

          fileRender對象簡介

          從Blob中讀取內(nèi)容的唯一方法是使用FileReader。

          FileReader接口有4個方法,其中3個用來讀取文件,另一個用來中斷讀取。無論讀取成功或失敗,方法并不會返回讀取結(jié)果,這一結(jié)果存儲在result屬性中。


          FileReader接口包含了一套完整的事件模型,用于捕獲讀取文件時的狀態(tài)。


          通過上面的方法以及事件,我們可以發(fā)現(xiàn),通過readAsDataURL方法及onload事件就可以拿到一個可本地瀏覽圖片的DataURL。

          最終代碼如下:


          這樣我們就可以監(jiān)聽到用戶的粘貼操作,并且將用戶粘貼的圖片文件實時的渲染到網(wǎng)頁之中了。

          總結(jié)

          以上是我對Blob對象的一些學(xué)習(xí)分享,希望在實際應(yīng)用上能對大家有所幫助。也希望大家多多支持小編。

          問題復(fù)現(xiàn)

          之前在做 html 內(nèi)容導(dǎo)出為 pdf、圖片時,先是用 html2canvas 生成截屏,再進一步轉(zhuǎn)換為 pdf 文件,感興趣的同學(xué)可以看下這篇一文搞定前端 html 內(nèi)容轉(zhuǎn)圖片、pdf 和 word 等文件,截圖得到的圖片內(nèi)容、質(zhì)量都沒有什么問題。

          不過最近有個同事反應(yīng),他導(dǎo)出的圖片有 bug,這我倒挺好奇的,因為這個導(dǎo)出功能已經(jīng)用了很久,并沒有人反饋過有問題(除了那個 pdf 翻頁內(nèi)容被截斷的問題,求助 jym :前端有好的解決方法嗎?),于是我要了他的文檔,果不其然,出現(xiàn)了下面紅框所示的問題。

          檢查一下它的 DOM 結(jié)構(gòu),發(fā)現(xiàn)是下面這樣,猜測是就是這個原因?qū)е碌摹?/span>

          為了驗證自己的猜想,淺淺調(diào)試一下 html2canvas 的源碼,看下 html2canvas 是怎樣一個流程,它是如何將 html內(nèi)轉(zhuǎn)成 canvas 的。

          調(diào)試開始

          在 html2canvas 執(zhí)行的地方打個斷點,開始調(diào)試代碼:

          進入 html2canvas 內(nèi)部,可以看到內(nèi)部執(zhí)行的是 renderElement 方法:

          咱們直接進入到 renderElement 方法內(nèi)部,看下它的執(zhí)行流程:

          renderElement 執(zhí)行流程

          判斷傳入節(jié)點是否有效

          • Node.ownerDocument:只讀屬性,返回當前節(jié)點的頂層的 document 對象;
          • document.defaultView:屬性返回當前 document 對象所關(guān)聯(lián)的 window 對象,如果沒有,會返回 null。

          這里主要判斷節(jié)點,快速跳過,繼續(xù)執(zhí)行 。

          合并配置項

          將用戶傳入的 options 與默認的 options 合并

          構(gòu)建配置項,將傳入的 opts 與默認配置合并,同時初始化一個 context 上下文對象(緩存、日志等):

          緩存對象 cache

          其中 cache 為緩存對象,主要是避免資源重復(fù)加載的問題。

          原理如下:

          如果遇到圖片鏈接為 blob,在加載完成后,會添加到緩存 _cache 中:

          下次使用直接通過 this._cache[src] 從緩存中獲取,不用再發(fā)送請求:

          同時,cache控制圖片的加載和處理,包括使用 proxy 代理和使用 cors 跨域資源共享這兩種情況資源的處理。

          繼續(xù)往下執(zhí)行

          克隆原始 DOM

          使用 DocumentCloner 方法克隆原始 DOM,避免修改原始 DOM。

          使用 clonedReferenceElement 將原始 DOM 進行克隆,并調(diào)用 toIFrame 將克隆到的 DOM 繪制到 iframe 中進行渲染,此時在 DOM 樹中會出現(xiàn) class 為 html2canvas-container 的 iframe 節(jié)點,通過 window.getComputedStyle 就可以拿到要克隆的目標節(jié)點上所有的樣式了。

          繪制 canvas

          前面幾步很簡單,主要是對傳入的 DOM 元素進行解析,獲取目標節(jié)點的樣式和內(nèi)容。重點是 toCanvas 即將 DOM 渲染為 canvas 的過程,html2canvas 提供了兩種繪制 canvas 的方式:

          1. 使用 foreignObject 方式繪制 canvas
          2. 使用純 canvas 方法繪制

          咱們接著執(zhí)行,當代碼執(zhí)行到這里時判斷是否使用 foreignObject 的方式生成 canvas:

          使用 foreignObject方式繪制 canvas

          首先了解下 foreignObject 是什么?

          1. svg 中的xmlns 全稱是“XML Namespaces”,即 XML 命名空間,正是它的存在,在瀏覽器中 svg 才能正常渲染(下面這段代碼是在 iconfont 上隨便復(fù)制的一個 icon svg 代碼);
          2. xml復(fù)制代碼 <svg t="1692613183565" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="20058" width="200" height="200">
            <path d="M548.571429 292.571429v182.784L731.428571 475.428571v73.142858l-182.857142-0.073143V731.428571h-73.142858V548.498286L292.571429 548.571429v-73.142858l182.857142-0.073142V292.571429h73.142858z" fill="#626B7D" p-id="20059"></path>
            </svg>
          3. foreignObject 允許包含來自不同的 XML 命名空間的元素,借助 <foreignObject> 標簽,我們可以直接將 DOM 節(jié)點作為 foreignObject 插入 SVG 節(jié)點中進行渲染,如下:
          4. xml復(fù)制代碼<svg xmlns="http://www.w3.org/2000/svg">
            <foreignObject width="120" height="50">
            <body xmlns="http://www.w3.org/1999/xhtml">
            <div>測試測試</div>
            </body>
            </foreignObject>
            </svg>
          5. 可以看到 <foreignObject> 標簽里面有一個設(shè)置了 xmlns="http://www.w3.org/1999/xhtml" 命名空間的 <body> 標簽,此時 <body> 標簽及其子標簽都會按照 XHTML 標準渲染,實現(xiàn)了 SVG 和 XHTML 的混合使用。
          6. 這樣只需要指定對應(yīng)的命名空間,就可以把它嵌套到 foreignObject 中,然后結(jié)合 SVG,直接渲染。
          7. 對于不同的命名空間,瀏覽器解析的方式也不一樣,所以在 SVG 中嵌套 HTML,解析 SVG 的時候遇到 http://www.w3.org/2000/svg 轉(zhuǎn)化 SVG 的解析方式,當遇到了 http://www.w3.org/1999/xhtml 就使用 HTML 的解析方式。
          8. 這是為什么 SVG 中可以嵌套 HTML,并且瀏覽器能夠正常渲染。
          9. foreignObject 已經(jīng)支持除 IE 外的主流瀏覽器

          弄懂 foreignObject 后,我們嘗試將 foreignObjectRendering 設(shè)置為 true,看看它是如何生成 canvas 的:

          js復(fù)制代碼Html2canvas(warp, {
            useCORS: true,
            foreignObjectRendering: true,
          })

          在此處打個斷點:

          進入 ForeignObjectRenderer 類中

          這里通過 ForeignObjectRenderer 實例化一個 renderer 渲染器實例,在 ForeignObjectRenderer 構(gòu)造方法中初始化 this.canvas 對象及其上下文 this.ctx

          調(diào)用 render 生成 canvas,進入到 render 方法:

          render 方法執(zhí)行很簡單,首先通過 createForeignObjectSVG 將 DOM 內(nèi)容包裝到<foreignObject>中生成 svg:

          生成的 svg 如下所示:

          接著通過。loadSerializedSVG 將上面的 SVG 序列化成 img 的 src(SVG 直接內(nèi)聯(lián)),調(diào)用this.ctx.drawImage(img, ...); 將圖片繪制到 this.canvas 上,返回生成好的 canvas 即可。

          接著點擊下一步,直到回到最開始的斷點處,將生成好的 canvas 掛在到 DOM 上,如下:

          js
          復(fù)制代碼document.body.appendChild(canvas)

          這就解決了???收工!!!

          NoNoNo,為什么使用純 canvas 繪制就有問題呢? 作為 bug 終結(jié)者,問題必須找出來,干就完了 。

          而且使用 foreignObject 渲染還有其他問題,我們后面再說。

          foreignObject 繪制 canvas 的流程

          1. 首先克隆原始 DOM,并將所有樣式都轉(zhuǎn)為行內(nèi)樣式,避免修改到頁面;
          2. 構(gòu)建 ForeignObjectRenderer 渲染器實例,調(diào)用 renderer 方法;
          3. 將 DOM 內(nèi)容通過 createForeignObjectSVG 轉(zhuǎn)為 svg 元素;
          4. 通過 loadSerializedSVG 將上面的 svg 序列化成 img 的 src;
          5. 通過 drawImage 繪制圖片到 canvas 上。

          使用純 canvas 繪制

          要想使用純 canvas 方式繪制,那么就需要將 DOM 樹轉(zhuǎn)換為 canvas 可以識別的數(shù)據(jù)類型,html2canvas 使用 parseTree 方法來實現(xiàn)轉(zhuǎn)換,我們來看下它的執(zhí)行過程。

          直接在調(diào)用 parseTree 方法處打斷點,進入到 parseTree 方法內(nèi):

          parseTree 的作用是將克隆 DOM 轉(zhuǎn)換為 ElementContainer 樹。

          首先將根節(jié)點轉(zhuǎn)換為 ElementContainer 對象,接著再調(diào)用 parseNodeTree 遍歷根節(jié)點下的每一個節(jié)點,轉(zhuǎn)換為 ElementContainer 對象。

          ElementContainer 對象主要包含 DOM 元素的信息:

          ts復(fù)制代碼type TextContainer = {
            // 文本內(nèi)容
            text: string;
            // 位置和大小信息
            textBounds: TextBounds[];
          }
          
          export class ElementContainer {
            // 樣式數(shù)據(jù)
            readonly styles: CSSParsedDeclaration;
            // 當前節(jié)點下的文本節(jié)點
            readonly textNodes: TextContainer[] = [];
            //  除文本節(jié)點外的子元素
            readonly elements: ElementContainer[] = [];
            // 位置大小信息(寬/高、橫/縱坐標)
            bounds: Bounds;
            // 標志位,用來決定如何渲染的標志
            flags = 0;
            ...
          }

          ElementContainer 對象是一顆樹狀結(jié)構(gòu),層層遞歸,每個節(jié)點都包含以上字段,形成一顆 ElementContainer 樹,如下:

          繼續(xù)下一步

          通過 CanvasRenderer 創(chuàng)建一個渲染器 renderer,創(chuàng)建 this.canvasthis.ctx上下文對象與 ForeignObjectRenderer 類似

          得到渲染器后,調(diào)用 render 方法將 parseTree 生成的 ElementContainer 樹渲染成 canvas,在這里就與 ForeignObjectRenderer 的 render 方法產(chǎn)生差別了。

          層疊上下文

          概念不懂就看 MDN:層疊上下文

          首先我們都知道 CSS 是流式布局,也就是在沒有浮動(float)和定位(position)的影響下,是不會發(fā)生重疊的,從上到下、由外到內(nèi)按照 DOM 樹去布局。

          而浮動和定位的元素會脫離文檔流,形成一個層疊上下文,所以如果想正常渲染,就需要得到它們的層疊信息。

          可以想象一下:在我們的視線與網(wǎng)頁之間有一條看不見的 z 軸,層疊上下文就是一塊塊薄層,而這些薄層中有很多 DOM 元素,這些薄層根據(jù)層疊信息在這個 z 軸上排列,最終形成了我們看到的絢麗多彩的頁面。

          畫個圖好像更形象些:

          白色為正常元素,黃色為 float 元素,藍色為 position 元素

          更多詳細資料請閱讀:深入理解 CSS 中的層疊上下文和層疊順序

          canvas 在繪制節(jié)點時需要先計算出整個目標節(jié)點里子節(jié)點渲染時所展現(xiàn)的不同層級,因為 Canvas 繪圖需要根據(jù)樣式計算哪些元素應(yīng)該繪制在上層,哪些在下層。元素在瀏覽器中渲染時,根據(jù) W3C 的標準,所有的節(jié)點層級布局,需要遵循層疊上下文和層疊順序的標準

          調(diào)用 parseStackingContexts 方法將 parseTree 生成的 ElementContainer 樹轉(zhuǎn)為層疊上下文。

          ElementContainer 樹中的每一個 ElementContainer 節(jié)點都會產(chǎn)生一個 ElementPaint 對象,最終生成層疊上下文的 StackingContext 如下:

          數(shù)據(jù)結(jié)構(gòu)如下:

          ts復(fù)制代碼// ElementPaint 數(shù)據(jù)結(jié)構(gòu)如下
          ElementPaint: {
            // 當前元素的container
            container: ElementContainer
            // 當前元素的border信息
            curves: BoundCurves
          }
          
          // StackingContext 數(shù)據(jù)結(jié)構(gòu)如下
          {
            element: ElementPaint;
            // z-index為負的元素行測會給你的層疊上下文
            negativeZIndex: StackingContext[];
            // z-index為零或auto、transform或者opacity元素形成的層疊上下文
            zeroOrAutoZIndexOrTransformedOrOpacity: StackingContext[];
            // 定位或z-index大于等于1的元素形成的層疊上下文
            positiveZIndex: StackingContext[];
            // 非定位的浮動元素形成的層疊上下文
            nonPositionedFloats: StackingContext[];
            // 內(nèi)聯(lián)的非定位元素形成的層疊上下文
            nonPositionedInlineLevel: StackingContext[];
            // 內(nèi)聯(lián)元素
            inlineLevel: ElementPaint[];
            // 非內(nèi)聯(lián)元素
            nonInlineLevel: ElementPaint[];
          }

          渲染層疊內(nèi)容時會根據(jù) StackingContext 來決定渲染的順序。

          繼續(xù)下一步,調(diào)用 renderStack 方法,renderStack 執(zhí)行 renderStackContent 方法,咱們直接進入 renderStackContent 內(nèi):

          canvas 繪制時遵循 w3c 規(guī)定的渲染規(guī)則 painting-order,renderStackContent 方法就是對此規(guī)則的一個代碼實現(xiàn),步驟如下:

          此處的步驟 1-7 對應(yīng)上圖代碼中的 1-7:

          1. 渲染當前層疊上下文的元素的背景和邊框;
          2. 渲染具有負 z-index 級別的子層疊上下文(最負的第一個);
          3. 對于流式布局、非定位的子元素調(diào)用 renderNodeContent 和 renderNode 進行渲染:
          4. 渲染所有未定位的浮動子元素,對于其中每一個,將該元素視為創(chuàng)建了一個新的堆棧上下文;
          5. 渲染正常流式布局、內(nèi)聯(lián)元素、非定位的子元素;
          6. 渲染 z-index 為 0 或 auto,或者 transform、opacity 等屬性的子元素;
          7. 渲染由 z-index 大于或等于 1 的子元素形成的層疊上下文,按 z-index 順序(最小的在前)。

          可以看到遍歷時會對形成層疊上下文的子元素遞歸調(diào)用 renderStack,最終達到對整個層疊上下文樹進行遞歸的目的:

          而對于未形成層疊上下文的子元素,就直接調(diào)用 renderNode 或 renderNodeContent 這兩個方法,兩者對比,renderNode 多了一層渲染節(jié)點的背景色和邊框的方法(renderNode 函數(shù)內(nèi)部調(diào)用 renderNodeBackgroundAndBorders 和 renderNodeContent 方法)。

          renderNodeContent 用于渲染一個元素節(jié)點里面的內(nèi)容,分為八種類型:純文本、圖片、canvas、svg、iframe、checkbox 和 radio、input、li 和 ol

          除了 iframe 的繪制比較特殊:重新生成渲染器實例,調(diào)用 render 方法重新繪制,其他的繪制都是調(diào)用 canvas 的一些 API 來實現(xiàn),比如繪制文字主要用 fillText 方法、繪制圖片、canvas、svg 都是調(diào)用 drawImage 方法進行繪制。

          所有可能用到的 API

          最終繪制到 this.canvas 上返回,至此,html2canvas 的調(diào)試就結(jié)束了。

          純 canvas 繪制流程

          1. 首先克隆原始 DOM,避免修改到頁面;
          2. 使用 parseTree 遞歸遍歷 html,生成 ElementContainer 樹(與原始 DOM 層級結(jié)構(gòu)類似);
          3. 構(gòu)建 CanvasRenderer 渲染器實例,調(diào)用 renderer 方法;
          4. 遍歷上一步生成的 ElementContainer,根據(jù)層疊規(guī)則生成層疊上下文StackingContext(與原始 DOM 層級結(jié)構(gòu)區(qū)別較大);
          5. 遍歷層疊上下文,遞歸地對層疊上下文各層中的節(jié)點和子層疊上下文進行解析并按順序繪制在 Canvas 上,針對要繪制的每個節(jié)點,主要有以下兩個過程:
          6. 創(chuàng)建畫布,根據(jù)上一步生成的層疊對象遞歸渲染,最終將 html 內(nèi)容繪制到畫布 canvas 上。

          針對最初的 Bug 分析

          ok,當調(diào)試了一遍 html2canvas 的流程之后,再回到我們的問題上,很顯然就是 canvas 渲染的時候的問題,也就是 renderNodeContent 方法,那我們直接在這里打個斷點進行調(diào)試(為了方便我只輸入一行文字進行調(diào)試),只有當是文本節(jié)點時會進入到此斷點,等到 mark 標簽中對應(yīng)的元素進入斷點時,查看:

          可以看到此時 width 和 height 已經(jīng)是父節(jié)點的寬高,果真如此 。

          解決方案

          既然已經(jīng)知道了問題所在,那么我們開始解決問題,有以下兩種解決方案可供參考:

          foreignObjectRendering

          在 html2canvas 配置中設(shè)置 foreignObjectRenderingtrue,此問題就可以解決嗎?

          然而現(xiàn)實并沒有這么簡單,這樣又會引出新的問題:導(dǎo)出的圖片內(nèi)容丟失

          這是為什么呢?

          通過 W3C 對SVG 的介紹可知:SVG 不允許連接外部的資源,比如 HTML 中圖片鏈接、CSS link 方式的資源鏈接等,在 SVG 中都會有限制。

          解決方法:需要將圖片資源轉(zhuǎn)為 base64,然后再去生成截圖,foreighnObject 這種方法更適合截取內(nèi)容為文字內(nèi)容居多的場景。

          對包含背景色的內(nèi)聯(lián)標簽截斷處理

          在對內(nèi)聯(lián)元素進行截斷前,如何確定 p 標簽中的 mark 標簽有沒有換行? 因為我們沒必要對所有內(nèi)聯(lián)標簽做處理。

          如果 mark 標簽的高度超過 p 標簽的一半時,就說明已經(jīng)換行了,然后將 <mark>要求一</mark> 替換為 <mark>要</mark><mark>求</mark><mark>一</mark> 即可,代碼如下:

          ts復(fù)制代碼const handleMarkTag = (ele: HTMLElement) => {
            const markElements = ele.querySelectorAll('mark')
            for (let sel of markElements) {
              const { height } = sel.getBoundingClientRect()
              let parentElement = sel.parentElement
              while (parentElement?.tagName !== 'P') {
                parentElement = parentElement?.parentElement!
              }
              const { height: parentHeight } = (
                parentElement as unknown as HTMLElement
              ).getBoundingClientRect()
              // mark的高度沒有超過p標簽的一半時 則沒有換行
              if (height < parentHeight / 2) continue
              // 超過一半時說明換行了
              const innerText = sel.innerText
              const outHtml = sel.outerHTML
              let newHtml = ''
              innerText.split('')?.forEach((text) => {
                newHtml += outHtml.replace(innerText, text)
              })
              sel.outerHTML = newHtml
            }
          }

          ok,再次嘗試一下,完美解決,這下可以收工了。

          總結(jié)

          通過對一個不是 bug 的 bug 的分析,嘗試調(diào)試了一遍 html2canvas 的代碼,弄懂了瀏覽器截圖的原理及 html2canvas 的核心流程,并從中學(xué)到了幾點新知識:

          1. svg xmnls 作用以及渲染 HTML 內(nèi)容的 foreignObject 標簽;
          2. 使用 foreignObject 實現(xiàn)快速截圖的方法;
          3. CSS 層疊上下文的概念;
          4. canvas 繪圖的一些方法。。。

          發(fā)現(xiàn) canvas 真是一個有趣的東西,什么都能畫,像我現(xiàn)在用于畫圖的工具excalidraw、圖表庫g6、g2、echarts都是用的 canvas 搞的,看來得抽時間學(xué)習(xí)一下 canvas,不要等到“書到用時方恨少“。

          以上就是本文的全部內(nèi)容,希望這篇文章對你有所幫助,歡迎點贊和收藏 ,如果發(fā)現(xiàn)有什么錯誤或者更好的解決方案及建議,歡迎隨時聯(lián)系。

          作者:翔子丶 鏈接:https://juejin.cn/post/7277045020423798840 來源:稀土掘金 著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。

          、Blob(Binary Large Object)定義:二進制類型的大對象數(shù)據(jù),在 JavaScript 中 Blob 對象表示不可變的原始數(shù)據(jù)。

          2、語法:

          var aBlob = new Blob(blobParts, options);

          其中:blobParts是一個由 ArrayBuffer、Blob、DOMString 等對象構(gòu)成的數(shù)組;options是一個可選項,由type和endings組成,type代表了被放入到 blob 中的內(nèi)容的 MIME 類型,endings用于指定包含行結(jié)束符 \n 的字符串如何被表示(native表示行結(jié)束符\n被更改為適合宿主操作系統(tǒng)的換行符,transparent會保持 blob 中保存的行結(jié)束符不變)。

          定義Blob

          3、Blob屬性和方法:兩個只讀屬性size和type,其中size屬性用于表示數(shù)據(jù)的大小(以字節(jié)為單位),type 屬性為MIME 類型的字符串。slice([start[, end[, contentType]]])返回一個源指定范圍內(nèi)的Blob 對象;stream()返回一個讀取 blob 內(nèi)容的ReadableStream;text()返回一個 Promise 對象且包含 blob 所有內(nèi)容的 UTF-8 格式的 USVString;arrayBuffer()返回一個 Promise 對象且包含 blob 所有內(nèi)容的二進制格式的 ArrayBuffer。

          Blob屬性和方法

          4、Blob URL/Object URL 是一種偽協(xié)議,允許 Blob作為鏈接的URL源,如a.href、img.src等。

          創(chuàng)建 Blob URLurl=URL.createObjectURL(Blob),覽器器為 URL.createObjectURL 生成的 URL 存儲了一個 URL → Blob 映射,此類 URL 較短,例如

          blob:http://domain/b3ad7623-60bb-4eff-9b9d-f925438b97c7

          Blob 本身仍駐留在內(nèi)存中,在不需要時,可以調(diào)用URL.revokeObjectURL(url)來刪除引用。

          5、base64也可以作為<img src= />的源,格式為

          data:[<mediatype>][;base64],<data>

          其中mediatype 是個MIME 類型的字符串,如 image/png,默認值為 text/plain;charset=US-ASCII,例如:

          <img src="data:image/png;base64,R0lGODlheABaAPf/AAC...">


          主站蜘蛛池模板: 日韩精品一区二区三区大桥未久| 高清精品一区二区三区一区| 日本一区二三区好的精华液| 国产激情精品一区二区三区| 亚洲中文字幕一区精品自拍| 国产裸体歌舞一区二区| 精品国产免费观看一区| 国产精品一区二区不卡| 亚洲国产情侣一区二区三区| 激情一区二区三区| 久久se精品一区精品二区| 一区二区免费电影| 精品一区中文字幕| 色一乱一伦一区一直爽| 国产剧情一区二区| 国产传媒一区二区三区呀| 麻豆国产在线不卡一区二区| 99精品国产高清一区二区| 日韩人妻精品无码一区二区三区| 久久精品国产第一区二区| 一区二区三区免费视频观看| 日本人真淫视频一区二区三区| 国产一区二区在线|播放| 正在播放国产一区| 日韩精品一区二区三区在线观看| 国产精品第一区揄拍无码| 精品一区二区三区在线观看l | 精品一区中文字幕| 精品免费AV一区二区三区| 色欲AV蜜桃一区二区三| 国产乱码精品一区二区三区四川人| 国产成人一区二区三中文| 婷婷亚洲综合一区二区| 免费国产在线精品一区| 免费无码一区二区| 亚洲综合无码精品一区二区三区| 久久国产免费一区二区三区| 久久精品一区二区三区AV| 中文字幕AV无码一区二区三区| 成人免费一区二区三区在线观看| 无码人妻一区二区三区在线水卜樱|