Notion – The all-in-one workspace for your notes, tasks, wikis, and databases.
使用svg的一个特性,允许在<foreignobject>标签中包含任意的html内容。
dom 转换为 XHTML
XMLSerializer 对象使你能够把一个 XML 文档或 Node 对象转化或“序列化”为未解析的 XML 标记的一个字符串要使用一个 XMLSerializer,使用不带参数的构造函数实例化它 ,然后调用其 serializeToString() 方法;
递归去克隆dom节点
插入字体
处理图片 图片都处理为dataUrl
示例一
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body onload="load()">
<div id="long">
<span>1111</span>
<h1>222222</h1>
hello SeriousLose</div>
</body>
</html>
<script>
function load() {
let long = document.getElementById('long');
let xmlStr = new XMLSerializer();
let xml = xmlStr.serializeToString(long);
console.log(xml);
let dom = `data:image/svg+xml;charset=utf-8,<svg viewBox="0 0 200 200" xmlns="<http://www.w3.org/2000/svg>">
<style>
polygon { fill: black }
div {
color: white;
font:12px serif;
height: 100%;
overflow: auto;
}
</style>
<polygon points="5,5 195,10 185,185 10,195" />
<!-- Common use case: embed HTML text into SVG -->
<foreignObject x="20" y="20" width="160" height="160">
${xml}
</foreignObject>
</svg>`;
let img = new Image();
img.src = dom;
document.body.appendChild(img);
img.onload = () => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;
ctx.drawImage(img, 0, 0);
canvas.toBlob(function (blob) {
let url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.style.display = 'none';
document.body.appendChild(a);
a.href = url;
a.download = 'image';
a.click();
window.URL.revokeObjectURL(url);
}, 'image/png');
};
}
</script>
使用svg中的<foreignobject>
用 canvas 渲染出处理好的 dataUrl 数据
canvas转化为 bold类型;
源码地址 dom-to-image/dom-to-image.js at master · tsayen/dom-to-image · GitHub
// toPng 方法
function toPng(node, options) {
return draw(node, options || {}).then(function (canvas) {
return canvas.toDataURL();
});
}
// draw 方法
function draw(domNode, options) {
// 将dom节点转为svg
return toSvg(domNode, options)
// 拿到的svg是image data URL,这里进一步通过svg创建图片
.then(util.makeImage)
.then(util.delay(100))
.then(function (image) {
// 通过图片创建canvas并返回
var canvas = newCanvas(domNode);
canvas.getContext("2d").drawImage(image, 0, 0);
return canvas;
});
// 新建canvas节点,处理dataUrl资源,和options参数
function newCanvas(domNode) {
var canvas = document.createElement("canvas");
canvas.width = options.width || util.width(domNode);
canvas.height = options.height || util.height(domNode);
if (options.bgcolor) {
var ctx = canvas.getContext("2d");
ctx.fillStyle = options.bgcolor;
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
return canvas;
}
}
// toSvg
function toSvg(node, options) {
options = options || {};
copyOptions(options);
return Promise.resolve(node)
.then(function (node) {
// 递归克隆dom节点
return cloneNode(node, options.filter, true);
})
// 嵌入字体,找出所有font-face样式,添加入一个新的style里面
.then(embedFonts)
// 将图片链接转换为dataUrl形式使用
.then(inlineImages)
// 将options里面的一些style放进style里面
.then(applyOptions)
.then(function (clone) {
// 创建svg,将dom节点通过 XMLSerializer().serializeToString() 序列化为字符串
// 然后用 foreignObject 包裹,就能将dom转为svg。
return makeSvgDataUri(
clone,
options.width || util.width(node),
options.height || util.height(node)
);
});
// 处理一些options的样式
function applyOptions(clone) {
if (options.bgcolor) clone.style.backgroundColor = options.bgcolor;
if (options.width) clone.style.width = options.width + "px";
if (options.height) clone.style.height = options.height + "px";
if (options.style)
Object.keys(options.style).forEach(function (property) {
clone.style[property] = options.style[property];
});
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);
});
function makeNodeCopy(node) {
// 遇到canvas转为image对象
if (node instanceof HTMLCanvasElement)
return util.makeImage(node.toDataURL());
// 克隆第一层
return node.cloneNode(false);
}
// 克隆子节点
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;
}
);
// 递归克隆
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;
}
}
function processClone(original, clone) {
if (!(clone instanceof Element)) return clone;
return Promise.resolve()
.then(cloneStyle)
.then(clonePseudoElements)
.then(copyUserInput)
.then(fixSvg)
.then(function () {
return clone;
});
// 克隆节点上面所有使用的样式。
function cloneStyle() {
// 顺便提提,为什么不用style,因为如果什么样式也没有设置的话,style是光秃秃的
// 而getComputedStyle则能获取到应用在节点上面所有样式
copyStyle(window.getComputedStyle(original), clone.style);
function copyStyle(source, target) {
if (source.cssText) target.cssText = source.cssText;
else copyProperties(source, target);
function copyProperties(source, target) {
util.asArray(source).forEach(function (name) {
target.setProperty(
name,
source.getPropertyValue(name),
source.getPropertyPriority(name)
);
});
}
}
}
// 提取伪类样式,放到css
function clonePseudoElements() {
[":before", ":after"].forEach(function (element) {
clonePseudoElement(element);
});
function clonePseudoElement(element) {
var style = window.getComputedStyle(original, element);
var content = style.getPropertyValue("content");
if (content === "" || content === "none") return;
var className = util.uid();
clone.className = clone.className + " " + className;
var styleElement = document.createElement("style");
styleElement.appendChild(
formatPseudoElementStyle(className, element, style)
);
clone.appendChild(styleElement);
function formatPseudoElementStyle(className, element, style) {
var selector = "." + className + ":" + element;
var cssText = style.cssText
? formatCssText(style)
: formatCssProperties(style);
return document.createTextNode(selector + "{" + cssText + "}");
function formatCssText(style) {
var content = style.getPropertyValue("content");
return style.cssText + " content: " + content + ";";
}
function formatCssProperties(style) {
return util.asArray(style).map(formatProperty).join("; ") + ";";
function formatProperty(name) {
return (
name +
": " +
style.getPropertyValue(name) +
(style.getPropertyPriority(name) ? " !important" : "")
);
}
}
}
}
}
// 处理输入内容
function copyUserInput() {
...
}
// 处理svg,创建命名空间
function fixSvg() {
if (!(clone instanceof SVGElement)) return;
clone.setAttribute("xmlns", "<http://www.w3.org/2000/svg>");
...
}
}
}
// makeSvgDataUri
function makeSvgDataUri(node, width, height) {
return (
Promise.resolve(node)
.then(function (node) {
// 将dom转换为字符串
node.setAttribute("xmlns", "<http://www.w3.org/1999/xhtml>");
return new XMLSerializer().serializeToString(node);
})
.then(util.escapeXhtml)
.then(function (xhtml) {
return (
'<foreignObject x="0" y="0" width="100%" height="100%">' +
xhtml +
"</foreignObject>"
);
})
/**
* 顺带提一提
* 不指定xmlns命名空间是不会渲染的
* xmlns="<http://www.w3.org/2000/svg>"
*/
.then(function (foreignObject) {
return (
'<svg xmlns="<http://www.w3.org/2000/svg>" width="' +
width +
'" height="' +
height +
'">' +
foreignObject +
"</svg>"
);
})
.then(function (svg) {
return "data:image/svg+xml;charset=utf-8," + svg;
})
);
}