看到 TJ 大神( github /tj) star了 dom-to-image( github /tsayen/dom-to-image),也一直很好奇html怎么转 image,那么就翻下源码,看下是如何实现的,其实一共就不到800行代码,还蛮容易读懂的。
使用svg的一个特性,允许在<foreign object>标签中包含任意的html内容。(主要是 XMLSerializer | MDN( developer.mozilla 组织网/zh-CN/docs/XMLSerializer)这个api将dom转为svg)
所以,为了渲染那个dom节点,你需要采取以下步骤:
import domtoimage from'dom-to-image'
domtoimage 有如下一些方法:
* toSvg (`dom` 转 `svg`)
* toPng (`dom` 转 `png`)
* toJpeg (`dom` 转 `jpg`)
* toBlob (`dom` 转 `blob`)
* toPixelData (`dom` 转 像素数据)
见名知意,名字取得非常好
下面我挑一个toPng来简单解析一下原理,其他的原理也都是类似的
尽量挑最核心的讲,希望不会显得很繁琐,了解核心思想就好,下面介绍几个核心函数:
调用顺序为
toPng 调用 Draw
Draw 调用 toSvg
toSvg 调用 cloneNode
toPng 方法:
// 里面其实就是调用了 draw 方法,promise返回的是一个canvas对象functiontoPng(node, options) {
return draw(node, options || {})
.then(function (canvas) {
return canvas.toDataURL();
});
}
Draw 方法
functiondraw(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 节点functionnewCanvas(domNode) {
var canvas = document.createElement('canvas');
canvas.width = options.width || util.width(domNode);
canvas.height = options.height || util.height(domNode);
......
return canvas;
}
}
toSvg 方法
functiontoSvg (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 节点序列化成 svgreturn makeSvgDataUri(
clone,
// util.width 就是 getComputedStyle 获取节点的宽
options.width || util.width(node),
options.height || util.height(node)
)
})
)
// 设置一些默认值functionapplyOptions (clone) {
......
return clone
}
}
cloneNode 方法
functioncloneNode (node, filter, root) {
if (!root && filter && !filter(node)) returnPromise.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 对象functionmakeNodeCopy (node) {
if (node instanceof HTMLCanvasElement) { return util.makeImage(node.toDataURL()) }
return node.cloneNode(false)
}
// clone 子节点 (如果存在的话)functioncloneChildren (original, clone, filter) {
var children = original.childNodes
if (children.length === 0) returnPromise.resolve(clone)
return cloneChildrenInOrder(clone, util.asArray(children), filter).then(
function () {
return clone
}
)
// 递归 clone 节点functioncloneChildrenInOrder (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
}
}
functionprocessClone (original, clone) {
if (!(clone instanceof Element)) return clone
returnPromise.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
})

