предисловие
ВидетьТиДжей Богзвездаdom-to-image, всегда было любопытноhtml
как повернутьimage
Затем загляните в исходный код и посмотрите, как он реализован.На самом деле, всего в нем меньше 800 строк кода, который довольно легко читается.
Принцип работы
использоватьsvg
особенность, которая позволяет<foreignobject>
Произвольные тегиhtml
содержание. (в основномXMLSerializer | MDNэтоapi
Будуdom
Перевести вsvg
)
Итак, для того, чтобы отобразить этоdom
узел, необходимо выполнить следующие действия:
- рекурсия
clone
оригинальныйdom
узел - Получите узел и дочерние узлы на
computed style
и добавьте эти стили в новый тег стиля (не забудьте стили псевдоэлемента клонирования) - Встроить веб-шрифты
- найти все
@font-face
- Проанализируйте ресурс URL и загрузите соответствующий ресурс.
- кодирование base64 и встраивание ресурсов как
data:
ссылка на URL - закончить выше
css rules
положить все в<style>
и добавьте метку к узлу клона
- Встроенные изображения (все преобразованы в dataUrl)
- Встроить URL-адрес источника изображения в
<img>元素
- Фоновое изображение Используйте свойство background css, аналогично тому, как используются шрифты.
- Сериализовать узел клона dom как
svg
- оберните xml в
<foreignobject>
маркировать, ставитьsvg
, затем используйте его какdata: url
- получить содержимое png или необработанные данные как
uint8array
чтобы получить, создайте его, используя svg в качестве источникаimg
label и визуализировать его во вновь созданный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
Постарайтесь выбрать самые основные, надеюсь, это не будет слишком сложно, просто поймите основные идеи.
Вот несколько основных функций:
- toPng (обернул функцию рисования, бессмысленно)
- Рисовать (дом => холст)
- toSvg (дом => svg)
- cloneNode (клонирование дерева dom и стилей css)
- makeSvgDataUri (dom => svg => данные: 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();
});
}
Метод рисования
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)
// 处理img和background url('')里面的资源,转成dataUrl
.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
})
}