看到 TJ 大神 star了dom-to-image,也一直很好奇html
怎麼轉 image
javascript
那麼就翻下源碼,看下是如何實現的,其實一共就不到800行代碼,還蠻容易讀懂的css
使用svg
的一個特性,容許在<foreignobject>
標籤中包含任意的html
內容。(主要是 XMLSerializer | MDN這個api
將dom
轉爲svg
)
因此,爲了渲染那個dom
節點,你須要採起如下步驟:html
clone
原始的 dom
節點computed style
,並將這些樣式添加進新建的style標籤中(不要忘記了clone 僞元素的樣式)@font-face
data:
URLS引用css rules
所有都放進<style>
中,並把標籤加入到clone的節點中去<img>元素
svg
<foreignobject>
標籤中,放入svg
中,而後將其做爲data: url
uint8array
獲取,使用svg做爲源建立一個img
標籤,並將其渲染到新建立的canvas
上,而後把canvas
轉爲base64
import domtoimage from 'dom-to-image'
domtoimage 有以下一些方法:java
* toSvg (`dom` 轉 `svg`) * toPng (`dom` 轉 `png`) * toJpeg (`dom` 轉 `jpg`) * toBlob (`dom` 轉 `blob`) * toPixelData (`dom` 轉 像素數據)
見名知意,名字取得很是好node
下面我挑一個toPng
來簡單解析一下原理,其餘的原理也都是相似的git
儘可能挑最核心的講,但願不會顯得很繁瑣,瞭解核心思想就好
下面介紹幾個核心函數:github
調用順序爲canvas
toPng 調用 Draw Draw 調用 toSvg toSvg 調用 cloneNode
toPng方法:api
// 裏面其實就是調用了 draw 方法,promise返回的是一個canvas對象 function toPng(node, options) { return draw(node, options || {}) .then(function (canvas) { return canvas.toDataURL(); }); }
Draw方法promise
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屬性提供。此屬性表示<svg>標記及其子標記屬於名稱空間爲「http://www.w3.org/2000/svg」的XML方言 .then(fixSvg) .then(function () { return clone })
下面是這篇的重點 把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(/\n/g, '%0A') .then(util.escapeXhtml) .then(function (xhtml) { return ( '<foreignObject x="0" y="0" width="100%" height="100%">' + xhtml + '</foreignObject>' ) }) // 變成svg .then(function (foreignObject) { return ( '<svg xmlns="http://www.w3.org/2000/svg" width="' + width + '" height="' + height + '">' + foreignObject + '</svg>' ) }) // 變成 data: url .then(function (svg) { return 'data:image/svg+xml;charset=utf-8,' + svg }) }