摘要:前言看到大神了,也一直很好奇怎么转那么就翻下源码,看下是如何实现的,其实一共就不到行代码,还蛮容易读懂的工作原理使用的一个特性,允许在标签中包含任意的内容。
前言
看到 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 对象
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/93194.html
摘要:经历月份开放的简历,收到了蛮多询问和面试,算是招人旺季,需要跳槽的小伙伴抓住机会。现在是面试了家公司左右,有些高频问题会标记次数总次数,可供大家参考。最后祝大家面试顺利,拿到心仪的,写错的地方请不吝赐教,谢谢。 经历 7月份开放的简历,收到了蛮多询问和面试,算是招人旺季,需要跳槽的小伙伴抓住机会。一开始广泛看面试题,没抓住重点复习,有很多平时也没怎么用到,导致一开始面试的时候,问的问题...
摘要:经历月份开放的简历,收到了蛮多询问和面试,算是招人旺季,需要跳槽的小伙伴抓住机会。现在是面试了家公司左右,有些高频问题会标记次数总次数,可供大家参考。最后祝大家面试顺利,拿到心仪的,写错的地方请不吝赐教,谢谢。 经历 7月份开放的简历,收到了蛮多询问和面试,算是招人旺季,需要跳槽的小伙伴抓住机会。一开始广泛看面试题,没抓住重点复习,有很多平时也没怎么用到,导致一开始面试的时候,问的问题...
摘要:经历月份开放的简历,收到了蛮多询问和面试,算是招人旺季,需要跳槽的小伙伴抓住机会。现在是面试了家公司左右,有些高频问题会标记次数总次数,可供大家参考。最后祝大家面试顺利,拿到心仪的,写错的地方请不吝赐教,谢谢。 经历 7月份开放的简历,收到了蛮多询问和面试,算是招人旺季,需要跳槽的小伙伴抓住机会。一开始广泛看面试题,没抓住重点复习,有很多平时也没怎么用到,导致一开始面试的时候,问的问题...
摘要:但是我一直信奉一个原则,即但凡复杂的知识,理解之后都只需要记忆简单的东西,而想简单精确描述一个复杂知识,是极困难的事。两个相同的节点,虚拟会认为是同一个节点,从而对其进行比较。 前言: 关于react的虚拟dom以及每次渲染更新的dom diff,网上文章很多。但是我一直信奉一个原则,即:但凡复杂的知识,理解之后都只需要记忆简单的东西,而想简单、精确描述一个复杂知识,是极困难的事。 正...
阅读 1869·2019-08-29 16:44
阅读 2174·2019-08-29 16:30
阅读 782·2019-08-29 15:12
阅读 3532·2019-08-26 10:48
阅读 2660·2019-08-23 18:33
阅读 3782·2019-08-23 17:01
阅读 1944·2019-08-23 15:54
阅读 1305·2019-08-23 15:05