摘要:前言看到大神了,也一直很好奇怎么轉那么就翻下源碼,看下是如何實現的,其實一共就不到行代碼,還蠻容易讀懂的工作原理使用的一個特性,允許在標簽中包含任意的內容。
前言
看到 TJ 大神 star了dom-to-image,也一直很好奇html怎么轉 image
那么就翻下源碼,看下是如何實現的,其實一共就不到800行代碼,還蠻容易讀懂的
工作原理使用svg的一個特性,允許在
所以,為了渲染那個dom節點,你需要采取以下步驟:
遞歸 clone 原始的 dom 節點
獲取 節點以及子節點 上的 computed style,并將這些樣式添加進新建的style標簽中(不要忘記了clone 偽元素的樣式)
嵌入網頁字體
找到所有的@font-face
解析URL資源,并下載對應的資源
base64編碼和內聯資源 作為 data: URLS引用
把上面處理完的css rules全部都放進中,并把標簽加入到clone的節點中去
內嵌圖片
內聯圖片src 的url 進 元素
背景圖片 使用 background css 屬性,類似fonts的使用方式
序列化 clone 的 dom 節點 為 svg
將xml包裝到
將png內容或原始數據作為uint8array獲取,使用svg作為源創建一個img標簽,并將其渲染到新創建的canvas上,然后把canvas轉為base64
完成
核心APIimport domtoimage from "dom-to-image"
domtoimage 有如下一些方法:
* toSvg (`dom` 轉 `svg`) * toPng (`dom` 轉 `png`) * toJpeg (`dom` 轉 `jpg`) * toBlob (`dom` 轉 `blob`) * toPixelData (`dom` 轉 像素數據)
見名知意,名字取得非常好
下面我挑一個toPng來簡單解析一下原理,其他的原理也都是類似的
分析 toPng 原理盡量挑最核心的講,希望不會顯得很繁瑣,了解核心思想就好
下面介紹幾個核心函數:
toPng (包裝了draw函數,沒啥意義)
Draw (dom => canvas)
toSvg (dom => svg)
cloneNode (clone dom樹和css樣式)
makeSvgDataUri (dom => svg => data:uri)
調用順序為
toPng 調用 Draw Draw 調用 toSvg toSvg 調用 cloneNode
toPng方法:
// 里面其實就是調用了 draw 方法,promise返回的是一個canvas對象 function toPng(node, options) { return draw(node, options || {}) .then(function (canvas) { return canvas.toDataURL(); }); }
Draw方法
function draw(domNode, options) { // 將 dom 節點轉為 svg(data: url形式的svg) return toSvg(domNode, options) // util.makeImage 將 canvas 轉為 new Image(uri) .then(util.makeImage) .then(util.delay(100)) .then(function (image) { var canvas = newCanvas(domNode); canvas.getContext("2d").drawImage(image, 0, 0); return canvas; }); // 創建一個空的 canvas 節點 function newCanvas(domNode) { var canvas = document.createElement("canvas"); canvas.width = options.width || util.width(domNode); canvas.height = options.height || util.height(domNode); ...... return canvas; } }
toSvg方法
function toSvg (node, options) { options = options || {} // 設置一些默認值,如果option是空的話 copyOptions(options) return ( Promise.resolve(node) .then(function (node) { // clone dom 樹 return cloneNode(node, options.filter, true) }) // 把字體相關的csstext 全部都新建一個 stylesheet 添加進去 .then(embedFonts) // clone 處理圖片啊,background url("")里面的資源,順便加載好 .then(inlineImages) // 把option 里面的一些 style 放進stylesheet里面 .then(applyOptions) .then(function (clone) { // node 節點序列化成 svg return makeSvgDataUri( clone, // util.width 就是 getComputedStyle 獲取節點的寬 options.width || util.width(node), options.height || util.height(node) ) }) ) // 設置一些默認值 function applyOptions (clone) { ...... return clone } }
cloneNode 方法
function cloneNode (node, filter, root) { if (!root && filter && !filter(node)) return Promise.resolve() return ( Promise.resolve(node) .then(makeNodeCopy) .then(function (clone) { return cloneChildren(node, clone, filter) }) .then(function (clone) { return processClone(node, clone) }) ) // makeNodeCopy // 如果不是canvas 節點的話,就clone // 是的話,就返回 canvas轉image的 img 對象 function makeNodeCopy (node) { if (node instanceof HTMLCanvasElement) { return util.makeImage(node.toDataURL()) } return node.cloneNode(false) } // clone 子節點 (如果存在的話) function cloneChildren (original, clone, filter) { var children = original.childNodes if (children.length === 0) return Promise.resolve(clone) return cloneChildrenInOrder(clone, util.asArray(children), filter).then( function () { return clone } ) // 遞歸 clone 節點 function cloneChildrenInOrder (parent, children, filter) { var done = Promise.resolve() children.forEach(function (child) { done = done .then(function () { return cloneNode(child, filter) }) .then(function (childClone) { if (childClone) parent.appendChild(childClone) }) }) return done } } // 處理添加dom的css,處理svg function processClone (original, clone) { if (!(clone instanceof Element)) return clone return Promise.resolve() // 讀取節點的getComputedStyle,添加進css中 .then(cloneStyle) // 獲取偽類的css,添加進css .then(clonePseudoElements) // 讀取 input textarea 的value .then(copyUserInput) // 設置svg 的 xmlns // 命名空間聲明由xmlns屬性提供。此屬性表示
下面是這篇的重點 把 html 節點序列化成 svg
// node 節點序列化成 svg function makeSvgDataUri (node, width, height) { return Promise.resolve(node) .then(function (node) { node.setAttribute("xmlns", "http://www.w3.org/1999/xhtml") // XMLSerializer 對象使你能夠把一個 XML 文檔或 Node 對象轉化或“序列化”為未解析的 XML 標記的一個字符串。 // 要使用一個 XMLSerializer,使用不帶參數的構造函數實例化它,然后調用其 serializeToString() 方法: return new XMLSerializer().serializeToString(node) }) // escapeXhtml代碼是string.replace(/#/g, "%23").replace(/ /g, "%0A") .then(util.escapeXhtml) .then(function (xhtml) { return ( "參考鏈接" + xhtml + " " ) }) // 變成svg .then(function (foreignObject) { return ( "" ) }) // 變成 data: url .then(function (svg) { return "data:image/svg+xml;charset=utf-8," + svg }) }
CSSStyleDeclaration.setProperty() - Web API 接口 | MDN
dom-to-image
XML DOM - XMLSerializer 對象
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/93194.html
摘要:經歷月份開放的簡歷,收到了蠻多詢問和面試,算是招人旺季,需要跳槽的小伙伴抓住機會?,F在是面試了家公司左右,有些高頻問題會標記次數總次數,可供大家參考。最后祝大家面試順利,拿到心儀的,寫錯的地方請不吝賜教,謝謝。 經歷 7月份開放的簡歷,收到了蠻多詢問和面試,算是招人旺季,需要跳槽的小伙伴抓住機會。一開始廣泛看面試題,沒抓住重點復習,有很多平時也沒怎么用到,導致一開始面試的時候,問的問題...
摘要:經歷月份開放的簡歷,收到了蠻多詢問和面試,算是招人旺季,需要跳槽的小伙伴抓住機會。現在是面試了家公司左右,有些高頻問題會標記次數總次數,可供大家參考。最后祝大家面試順利,拿到心儀的,寫錯的地方請不吝賜教,謝謝。 經歷 7月份開放的簡歷,收到了蠻多詢問和面試,算是招人旺季,需要跳槽的小伙伴抓住機會。一開始廣泛看面試題,沒抓住重點復習,有很多平時也沒怎么用到,導致一開始面試的時候,問的問題...
摘要:經歷月份開放的簡歷,收到了蠻多詢問和面試,算是招人旺季,需要跳槽的小伙伴抓住機會?,F在是面試了家公司左右,有些高頻問題會標記次數總次數,可供大家參考。最后祝大家面試順利,拿到心儀的,寫錯的地方請不吝賜教,謝謝。 經歷 7月份開放的簡歷,收到了蠻多詢問和面試,算是招人旺季,需要跳槽的小伙伴抓住機會。一開始廣泛看面試題,沒抓住重點復習,有很多平時也沒怎么用到,導致一開始面試的時候,問的問題...
摘要:但是我一直信奉一個原則,即但凡復雜的知識,理解之后都只需要記憶簡單的東西,而想簡單精確描述一個復雜知識,是極困難的事。兩個相同的節點,虛擬會認為是同一個節點,從而對其進行比較。 前言: 關于react的虛擬dom以及每次渲染更新的dom diff,網上文章很多。但是我一直信奉一個原則,即:但凡復雜的知識,理解之后都只需要記憶簡單的東西,而想簡單、精確描述一個復雜知識,是極困難的事。 正...
閱讀 1867·2019-08-29 16:44
閱讀 2172·2019-08-29 16:30
閱讀 780·2019-08-29 15:12
閱讀 3531·2019-08-26 10:48
閱讀 2659·2019-08-23 18:33
閱讀 3780·2019-08-23 17:01
閱讀 1943·2019-08-23 15:54
閱讀 1302·2019-08-23 15:05