需求:截取页面某一区域生成图片
解决方法: 使用# html2canvas,具体实现方式网上有很多就不一一叙述了
遇到的问题
区域内的svg无法正常生成图片;网络上很多文章介绍可以使用canvg;但自己观察后发现,页面中使用antv的svg图标可以正常绘制,但使用阿里妈妈矢量图标整理下载的svg图标集无法正常绘制,说明最新版本的 html2canvas是可以正常绘制svg图标的,但具体问题出现在哪里呢?
各种搜索过后发现主要处在svg中的use标签上,因为 <use> 标签引用的是在 HTML 文档之外的符号定义,而 html2canvas 在转换时无法正确处理这些引用。
可以正常转成图片的svg
不能正常转成图片的svg
解决方案找到三种:
1.使用canvg手动将 SVG 渲染到 canvas 上,然后再使用 html2canvas。
结果:失败
2.将 SVG 符号转换为 Base64 编码,然后在 html2canvas 转换时使用这个编码。
结果:生成的Base64为空白图片,失败
3.如何将带use的SVG转换为普通的SVG元素(将svg+use变为svg+path)
转化思路: 找到所有需要转化的use标签=》通过use标签的xlink:href的名称找到原始svg的相关的path内容=》将use中的内容替换成path的内容;
**下图为原始path内容**
具体代码如下:
// 获取SVG中所有的use元素
var useElements = document.querySelectorAll('use');
let arr = [...useElements]
// 遍历每个use元素
arr.forEach(function(useElement) {
// 获取use元素引用的ID
var href = useElement.getAttribute('href');
if (href && href.startsWith('#')) {
var id = href.substring(1);
// 查找对应的实际元素
var referencedElement = document.getElementById(id);
// 克隆实际元素并替换use元素
var clonedElement = referencedElement.cloneNode(true);
useElement.parentNode.replaceChild(clonedElement, useElement);
}
});
转换成功发现,依旧没有图标生成,翻看dom样式发现是因为原始的path的值与当前设置svg的大小比例不一致,原始的path没有后期设置大小,普遍偏大,所以下一步任务将原始的path的值按照当前svg的比例等比缩小
// 查找对应的实际元素svg里面的path集合
var paths = document.getElementById(id).querySelectorAll("path");
// 获取SVG的实际宽度和高度
var svgW = useElement.parentNode.style.width;
var svgWidth = useElement.parentNode.width
var svgHeight = useElement.parentNode.height
// 计算所有path的包围框
var totalBBox = { x: Infinity, y: Infinity, width: 0, height: 0 };
paths.forEach(function(path) {
//获取每个path的位置信息
var bbox = path.getBBox();
totalBBox.x = Math.min(totalBBox.x, bbox.x);
totalBBox.y = Math.min(totalBBox.y, bbox.y);
totalBBox.width = Math.max(totalBBox.width, bbox.x + bbox.width);
totalBBox.height = Math.max(totalBBox.height, bbox.y + bbox.height);
});
// 计算缩放比例
var scaleX = svgWidth / totalBBox.width;
var scaleY = svgHeight / totalBBox.height;
var scale = Math.min(scaleX, scaleY);
// 平移path使其居中
var translateX = (svgWidth - totalBBox.width * scale) / 2 - totalBBox.x * scale;
var translateY = (svgHeight - totalBBox.height * scale) / 2 - totalBBox.y * scale;
// 将原始path转化为等比例的普通svg
paths.forEach((path) => {
var bbox = path.getBBox();
var transform = 'scale(' + scale + ') translate(' + translateX + ',' + translateY + ')';
path.setAttribute('transform', transform);
});
结果:成功
最终代码如下
//将页面指定节点内容转为图片
function canvasToPicture(dom,useLabel) {
//将svg+use转换为svg+path
toSimpleSvg('svg')
// 转成图片,生成图片地址
html2canvas(dom, { useCORS: true, allowTaint: true }).then((canvas) => {
//将canvas通过toDataURL转成图片url
let imgURL = canvas.toDataURL("image/png");
return imgURL
});
}
//将svg+use转换为svg+path
function toSimpleSvg(useLabel){
// 获取所有iconfont的svg的use标签
var useElements = document.querySelectorAll(useLabel);
// 遍历每个svg中的use元素
let arr = [...useElements]
arr.forEach((useElement)=> {
// 获取use元素引用的ID
var href = useElement.getAttribute('xlink:href');
if (href && href.startsWith('#')) {
var id = href.substring(1);
// 查找对应的实际元素svg里面的path集合
var paths = document.getElementById(id).querySelectorAll("path");
// 获取SVG的实际宽度和高度
var svgW = useElement.parentNode.style.width;
var svgWidth = useElement.parentNode.width
var svgHeight = useElement.parentNode.height
// 计算所有path的包围框
var totalBBox = { x: Infinity, y: Infinity, width: 0, height: 0 };
paths.forEach(function(path) {
//获取每个path的位置信息
var bbox = path.getBBox();
totalBBox.x = Math.min(totalBBox.x, bbox.x);
totalBBox.y = Math.min(totalBBox.y, bbox.y);
totalBBox.width = Math.max(totalBBox.width, bbox.x + bbox.width);
totalBBox.height = Math.max(totalBBox.height, bbox.y + bbox.height);
});
// 计算缩放比例
var scaleX = svgWidth / totalBBox.width;
var scaleY = svgHeight / totalBBox.height;
var scale = Math.min(scaleX, scaleY);
// 平移path使其居中
var translateX = (svgWidth - totalBBox.width * scale) / 2 - totalBBox.x * scale;
var translateY = (svgHeight - totalBBox.height * scale) / 2 - totalBBox.y * scale;
// 将原始path转化为等比例的普通svg
paths.forEach((path) => {
// 应用缩放和平移变换到每个path
var transform = 'scale(' + scale + ') translate(' + translateX + ',' + translateY + ')';
// 保存原始数据
var clonedElement = path.cloneNode(true);
// 缩放paht
clonedElement.setAttribute('transform', transform);
// 将path 放入到svg中转化为普通的svg
useElement.parentNode.appendChild(clonedElement)
});
}
});
}