使用JavaScript或Vue框架解决SVG转图片时自定义字体无法正确显示的问题

在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库,我们可以更高效地处理文件导出过程,提升整体功能的稳定性和可靠性。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值