html2canvas原理,html2image原理简述

1460000013540928?w=1794&h=648

前言

看到 TJ 大神 star了dom-to-image,也一直很好奇html怎么转 image

那么就翻下源码,看下是如何实现的,其实一共就不到800行代码,还蛮容易读懂的

工作原理

使用svg的一个特性,允许在标签中包含任意的html内容。(主要是 XMLSerializer | MDN这个api将dom转为svg)

所以,为了渲染那个dom节点,你需要采取以下步骤:

递归 clone 原始的 dom 节点

获取 节点以及子节点 上的 computed style,并将这些样式添加进新建的style标签中(不要忘记了clone 伪元素的样式)

嵌入网页字体

找到所有的@font-face

解析URL资源,并下载对应的资源

base64编码和内联资源 作为 data: URLS引用

把上面处理完的css rules全部都放进

内嵌图片

内联图片src 的url 进 元素

背景图片 使用 background css 属性,类似fonts的使用方式

序列化 clone 的 dom 节点 为 svg

将xml包装到标签中,放入svg中,然后将其作为data: url

将png内容或原始数据作为uint8array获取,使用svg作为源创建一个img标签,并将其渲染到新创建的canvas上,然后把canvas转为base64

完成

核心API

import domtoimage from 'dom-to-image'

domtoimage 有如下一些方法:

* toSvg (`dom` 转 `svg`)

* toPng (`dom` 转 `png`)

* toJpeg (`dom` 转 `jpg`)

* toBlob (`dom` 转 `blob`)

* toPixelData (`dom` 转 像素数据)

见名知意,名字取得非常好

下面我挑一个toPng来简单解析一下原理,其他的原理也都是类似的

分析 toPng 原理

尽量挑最核心的讲,希望不会显得很繁琐,了解核心思想就好

1460000013540929?w=915&h=404

下面介绍几个核心函数:

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属性提供。此属性表示标记及其子标记属于名称空间为“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 (

'' +

xhtml +

''

)

})

// 变成svg

.then(function (foreignObject) {

return (

'' +

foreignObject +

''

)

})

// 变成 data: url

.then(function (svg) {

return 'data:image/svg+xml;charset=utf-8,' + svg

})

}

参考链接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值