在Vue流行的今天,许多应用中的特效和画板功能需要将SVG转换为图片以便用户下载。最近,在我负责的一个项目中,由于使用了自定义字体,遇到了一个问题:SVG无法以正确的方式转换为图片下载,导致缺失了自定义样式。
为解决这一问题,我最初采用了将SVG渲染到Canvas中的方案。通过利用Canvas的toDataURL()
方法,可以获取到转换后的图片数据。随后,通过创建一个隐藏的<a>
标签并设置其href
属性为图片数据的URL,触发下载操作。以下是一个使用Vue 3的示例代码:
<template>
<div>
<!-- 假设你的SVG内容在这里 -->
<svg ref="svgElement" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
<!-- SVG内容,包含自定义字体 -->
<text font-family="abc" font-size="22" x="20" y="20">自定义字体</text>
</svg>
<button @click="downloadSvgAsImage">下载SVG为图片</button>
</div>
</template>
<script setup>
import {
ref
} from 'vue';
const svgElement = ref(null);
// SVG 转图片并下载的函数
function downloadSvgAsImage() {
if (!svgElement.value) {
return;
}
const serializer = new XMLSerializer();
const svgStr = serializer.serializeToString(svgElement.value);
const blob = new Blob([svgStr], {
type: 'image/svg+xml;charset=utf-8'
});
const url = URL.createObjectURL(blob);
const img = new Image();
img.onload = function() {
// 创建一个canvas元素
const canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0, img.width, img.height);
// 将canvas转换为图片
const dataUrl = canvas.toDataURL('image/png');
// 创建一个隐藏的a标签用于下载
const a = document.createElement('a');
a.href = dataUrl;
a.download = 'custom-font-svg.png';
a.style.display = 'none';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
};
img.src = url;
};
</script>
<style scoped>
/* 在这里定义你的自定义字体 */
/* 推荐一个免费的字体库,部分收费:https://font.chinaz.com/ */
@font-face {
font-family: 'abc';
src: url('@/assets/fonts/abc.ttf');
font-weight: normal;
font-style: normal;
}
</style>
在上述情况中,当SVG中包含text标签并应用了自定义字体时,发现导出的图片并未正确呈现这些自定义样式。为了解决这个问题,曾尝试使用第三方库,如html2canvas或dom-to-image,期望通过截图的方式来实现。然而,令人遗憾的是,这种方法仍然未能有效解决问题。
经过我的深入剖析,问题的根源在于资源文件未能正确加载,因此需要确保在SVG的defs
标签中对其进行定义。然而,令人遗憾的是,尽管我尝试了多次,却始终未能成功解决这个问题。网络上提供的案例虽然为我提供了一些思路,但并未能实际解决面临的问题。
最后,我灵光一闪,如果我们尝试以编程的方式直接绘制字体,而不是依赖外部资源文件,是否能够绕过这个难题呢?于是,我尝试编写了以下代码来实现这一想法:采用canvas的2D画笔实现
<template>
<div>
<!-- 假设你的SVG内容在这里 -->
<svg ref="svgElement" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
<!-- SVG内容,包含自定义字体 -->
<text font-family="abc" font-size="22" x="20" y="20">自定义字体</text>
</svg>
<button @click="downloadSvgAsImage">下载SVG为图片</button>
</div>
</template>
<script setup>
import {
ref
} from 'vue';
const svgElement = ref(null);
// SVG 转图片并下载的函数
function downloadSvgAsImage() {
if (!svgElement.value) {
return;
}
// 提取所有text标签信息,根据自己实际情况修改
const texts = document.getElementsByTagName("text");
const serializer = new XMLSerializer();
let svgStr = serializer.serializeToString(svgElement.value);
//删除掉文字,解决自定义字体
svgStr = svgStr.replaceAll(/<text(?:\s+[^>]+)?>([\s\S]*?)<\/text>/g, '');
const blob = new Blob([svgStr], {
type: 'image/svg+xml;charset=utf-8'
});
const url = URL.createObjectURL(blob);
const img = new Image();
img.onload = function() {
// 创建一个canvas元素
const canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0, img.width, img.height);
// 关键代码
for (let i = 0; i < texts.length; i++) {
const text = texts[i];
// 重新绘制文本,这次在画布上
ctx.font = `${text.getAttribute('font-size')}px ${text.getAttribute('font-family')}`;
//ctx.fillStyle = 'black';// 颜色
// 如果是渐变颜色,创建一个线性渐变对象,svg对应defs下的linearGradient
//const lg = ctx.createLinearGradient(0, 0, img.width, 0);
// 添加颜色停止点
//lg.addColorStop(0, 'rgba(0, 0, 0,1)'); // 在%位置
//lg.addColorStop(1, 'rgba(255, 0, 0,1)'); // 在%位置
//ctx.fillStyle = lg;
ctx.fillText(text.textContent, text.getAttribute('x'), text.getAttribute('y'));
}
// 将canvas转换为图片
const dataUrl = canvas.toDataURL('image/png');
// 创建一个隐藏的a标签用于下载
const a = document.createElement('a');
a.href = dataUrl;
a.download = 'custom-font-svg.png';
a.style.display = 'none';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
};
img.src = url;
};
</script>
<style scoped>
/* 在这里定义你的自定义字体 */
/* 推荐一个免费的字体库,部分收费:https://font.chinaz.com/ */
@font-face {
font-family: 'abc';
src: url('@/assets/fonts/abc.ttf');
font-weight: normal;
font-style: normal;
}
</style>
当然,这个解决方案提供了一个最原始且核心的实现方式,但我们可以进一步对代码进行优化以提升效率和用户体验。例如,在导出文件时,可以利用file-saver
这一第三方库来简化文件保存的操作,并确保文件的正确性和完整性。通过整合file-saver
库,我们可以更高效地处理文件导出过程,提升整体功能的稳定性和可靠性。