HOW - 图形格式SVG及其应用(图标库&字体)

本文介绍了SVG(可缩放矢量图形)的基本概念、特点和应用场景,包括其可缩放性、文本可搜索和编辑、支持互动和动画等。讨论了SVG在HTML中的基本使用方法,如直接内嵌SVG代码、使用``标签引用和使用``或``标签。此外,还探讨了SVG字体的创建和使用,SVG Sprite图标复用,动态Fetch CDN SVG图标的方法以及SVG压缩的重要性。最后对比了SVG与Canvas在不同场景下的适用性。 摘要由CSDN通过智能技术生成

一、介绍:scalable vector graphic

SVG 是可缩放矢量图形(Scalable Vector Graphics)的缩写。它是一种用于描述二维矢量图形的 XML 格式的标记语言。与像素图形(如 JPEG、PNG 等)不同,像素图形是基于像素的位图,SVG 使用的是基于几何形状的描述,因此它可以无限放大而不失真,适用于各种尺寸的显示设备,从小到移动设备的屏幕到大到高分辨率的显示器,甚至打印品质的输出。

SVG 具有以下特点:

  1. 可缩放性:SVG 图像可以无限缩放而不会失真,因为它们是基于数学描述的矢量图形。具体而言,SVG 图像是由几何形状(如直线、圆、曲线等)和其属性(如颜色、填充等)的数学描述组成的。这些描述不依赖于特定的分辨率或像素密度,而是以数学公式的形式保存。另外,SVG 中的图形元素是相对位置和比例保持一致的,无论图像的尺寸如何变化,图形元素之间的相对位置和比例都保持不变,因此在放大时不会出现失真。

  2. 文本可搜索和编辑:SVG 内的文本和图形元素是可编辑和可搜索的,这使得 SVG 图像非常适合用于包含文本和图形的图表地图等。

  3. 支持互动性和动画:SVG 支持添加交互式元素和动画效果,可以通过 CSS 或 JavaScript 进行控制。

  4. 跨浏览器兼容性:现代 Web 浏览器(如 Chrome、Firefox、Safari 等)都支持 SVG,使得 SVG 成为 Web 开发中常用的图形格式之一。

  5. 可压缩性和无损压缩:SVG 图像可以通过压缩算法进行压缩,以减小文件大小,从而加快加载速度。而且由于 SVG 使用文本文件格式(XML)来存储图形信息,它可以通过无损压缩算法(如 gzip)进行压缩。这意味着即使文件大小很小,也可以保留所有的细节和精度。

SVG 被广泛用于 Web 开发数据可视化图表绘制图标设计等领域,因为它提供了一种灵活、清晰且可交互的图形表示方式。

二、基本使用

  1. 直接在 HTML 中嵌入 SVG 代码:

将 SVG 代码直接放入 HTML 文件中,这样可以轻松地对 SVG 元素应用 CSS 样式和 JavaScript 交互。

<!DOCTYPE html>
<html>
<head>
<style>
svg {
  width: 100px;
  height: 100px;
}
circle {
  fill: green;
}
</style>
</head>
<body>
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
      <circle cx="50" cy="50" r="40" />
    </svg>
</body>
</html>

效果如下
请添加图片描述

  1. 使用 <img> 标签

将 SVG 文件作为图像文件引用。这种方法的优点是可以像使用其他图像格式一样使用 SVG,简单医用。

然而,这种方法也有一些限制:

2.1 无法直接操作 SVG 内部元素:由于 SVG 被视为一种静态图像,因此无法直接通过 DOM 或 JavaScript 来操作 SVG 内部的元素。假设 SVG 文件包含一个圆形元素,你想要通过 JavaScript 动态地改变它的颜色。如果将 SVG 文件作为图像引用,你无法直接访问 SVG 元素来修改其属性,例如 document.querySelector('circle').setAttribute('fill', 'blue')

2.2 不支持内联样式和脚本:在以 <img> 标签引入 SVG 文件时,SVG 图像不支持内联样式和脚本,这意味着无法通过 HTML 的 style 属性或者 SVG 内部的 <style> 元素来应用样式,也无法使用内联的 JavaScript 代码。假设 SVG 文件包含一个带有内联样式的矩形元素,并且带有一些 JavaScript 脚本来处理交互行为,示例如下:

<!-- SVG 文件内容(rectangle.svg) -->
<svg width="100" height="100">
  <rect x="10" y="10" width="80" height="80" fill="blue" onclick="alert('Clicked!')" />
</svg>

在这个例子中,尽管 SVG 文件中有一个蓝色的矩形元素,并且带有一个点击事件,但是当它被引用为图像,就无法执行内联的 JavaScript,因此点击事件无法触发。

  1. 使用 CSS background-image

<img> 标签用法限制一致。

<!DOCTYPE html>
<html>
<head>
<style>
  .icon {
    width: 100px;
    height: 100px;
    background-image: url('https://example.com/path/to/svg/image.svg');
    background-size: cover;
  }
</style>
</head>
<body>
    <div class="icon"></div>
</body>
</html>
  1. 使用 <object><embed> 标签:

这两个标签都允许将外部 SVG 文件嵌入到 HTML 文档中。使用这些方法,可以在一定程度上访问和操作 SVG 文档的 DOM,并且支持内联样式和脚本,但它们之间存在一些差异。

<!DOCTYPE html>
<html>
<body>
    <object data="https://example.com/path/to/svg/image.svg" type="image/svg+xml" width="100" height="100"></object>
</body>
</html>
<!DOCTYPE html>
<html>
<body>
    <embed src="https://example.com/path/to/svg/image.svg" type="image/svg+xml" width="100" height="100">
</body>
</html>
  1. 使用 JavaScript
<div id="svg-container"></div>
<script>
  fetch('https://example.com/path/to/svg/image.svg')
    .then(response => response.text())
    .then(svgData => {
      document.getElementById('svg-container').innerHTML = svgData;
    });
</script>

三、SVG 字体:自定义图标或者符号

更多关于前端开发中字体介绍请阅读:WHAT - 前端开发中的字体

SVG 字体是一种使用 SVG(可缩放矢量图形)格式来定义的字体。与传统的字体文件(如 TrueType 字体或 OpenType 字体)不同,SVG 字体是使用 XML 格式来描述字符的矢量图形。每个字符被表示为一个矢量图形路径,这使得 SVG 字体在任何尺寸下都可以无损放大而不失真。

fontello - icon fonts generator 提供的为例
请添加图片描述
请添加图片描述

<i class="demo-icon icon-glass">&#xe800;</i>

可以看到,通过 <i> 字体标签并指定自定义图标。

SVG 字体通常用于 Web 开发中,特别是在需要自定义图标或自定义符号的情况下。它们的一些特点包括:

  1. 矢量图形:SVG 字体中的每个字符都是一个矢量图形路径,因此它们可以在任何尺寸下无损放大。

  2. 灵活性:与传统字体相比,SVG 字体更灵活,因为它们可以包含任意形状和复杂的图形作为字符。

  3. 图标字体:SVG 字体经常用于创建图标字体。开发者可以将一系列图标转换为 SVG 字体,并使用 CSS 控制字体的大小、颜色等属性来显示图标。

  4. 跨浏览器兼容性:现代 Web 浏览器支持在 CSS 中引用 SVG 字体,并能够在页面上正确显示。

使用 SVG 字体可以让开发者轻松地在网页中嵌入自定义图标或符号,并且由于其矢量性质,图标可以在不同的显示设备和分辨率下保持清晰度。

当使用工具如 fontelloFont AwesomeMaterial Icons 等预定义的图标字体时,这些工具实际上会将图标打包为字体文件(通常是 TrueType 字体或 OpenType 字体),而不是单独的 SVG 文件。
请添加图片描述
这些字体文件中包含了一系列图标,每个图标对应于字体中的一个字符。具体的原理如下:

  1. 字体文件包含图标路径:在字体文件中,每个字符都被映射到一个特定的图标路径。这些路径描述了每个图标的矢量图形。

  2. CSS 类与字符关联图标字体工具会为每个图标定义一个对应的 CSS 类。这些 CSS 类通常由工具自动生成,并且与字体文件中的字符映射关系紧密。通过应用相应的 CSS 类,可以在页面中显示特定的图标。

  3. 字体文件的引入:在使用图标字体之前,需要将字体文件引入到项目中。这通常通过在 HTML 或 CSS 中使用 @font-face 规则来实现。

  4. 使用 CSS 类显示图标:一旦字体文件引入到项目中,就可以在需要的地方通过指定相应的 CSS 类来显示特定的图标。这些 CSS 类会将指定的字符(通常是 Unicode 字符私有区域字符)与字体文件中的图标路径关联起来,从而实现显示相应的图标。

举例来说,假设使用 Font Awesome,我们想要显示一个家的图标:

<!-- 引入 Font Awesome 字体文件 -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">

<!-- 使用 Font Awesome 提供的 CSS 类显示图标 -->
<i class="fas fa-home"></i>

在这个例子中,fas 是 Font Awesome 的 CSS 类前缀,而 fa-home 则是对应于“家”图标的 CSS 类。当页面加载时,浏览器会根据指定的 CSS 类显示相应的图标,这些图标实际上是通过字体文件中的字符路径渲染出来的。

但注意,在 HTML 中,<i> 标签通常用于表示斜体文本,在某些情况下可以被用于表示图标。然而,使用 <i> 标签来表示图标不是最佳的做法,因为 <i> 标签的本意是用于表示斜体文本,而不是图标。

四、SVG Sprite:自定义图标

创建一个 SVG Sprite 文件是一种将多个 SVG 图标打包到单个文件中的方法,这样可以减少 HTTP 请求的数量,并提高网站的性能。

使用 <use> 标签引用 SVG Sprite 文件中的特定图标时,原理如下:

  1. 创建 SVG Sprite 文件
  • 首先,将所有要使用的 SVG 图标整合到一个 SVG 文件中,称为 SVG Sprite 文件。
  • 每个图标应该位于单独的 <symbol> 标签内,使用 id 属性为每个图标定义一个唯一的标识符。
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
  <symbol id="icon-home" viewBox="0 0 24 24">
    <path d="M12 20V14H16V20H19V12H22L12 3L2 12H5V20H8V14H12Z" />
  </symbol>
  <symbol id="icon-search" viewBox="0 0 24 24">
    <path d="M15.5 14C16.88 14 18 12.88 18 11.5C18 10.12 16.88 9 15.5 9C14.12 9 13 10.12 13 11.5C13 12.88 14.12 14 15.5 14ZM22 20.56L18.37 16.94C19.61 15.45 20.5 13.61 20.5 11.5C20.5 6.81 16.69 3 12 3C7.31 3 3.5 6.81 3.5 11.5C3.5 16.19 7.31 20 12 20C13.94 20 15.66 19.15 16.94 17.94L20.57 21.57L22 20.56Z" />
  </symbol>
</svg>
  1. 在 HTML 中引用 SVG Sprite 文件
  • 在需要使用图标的页面中,通过 <use> 标签引用 SVG Sprite 文件,并指定要使用的图标的标识符(即 id 属性值)。
  • xlink:href 属性指定 SVG Sprite 文件的路径,后面跟着 # 和图标的标识符。
<svg class="icon">
  <use xlink:href="sprites.svg#icon-home"></use>
</svg>

<svg class="icon">
  <use xlink:href="sprites.svg#icon-search"></use>
</svg>
  1. 图标显示原理
  • 当浏览器解析 HTML 时,遇到 <use> 标签时,它会查找指定的 SVG 文件(在 xlink:href 属性中定义的文件路径)。
  • 然后,浏览器会在 SVG 文件中查找与 <use> 标签中指定的 id 属性相匹配的 <symbol> 元素。
  • 最后,浏览器将找到的 <symbol> 元素的内容复制到当前文档中,并将其插入到 <use> 标签所在的位置,从而显示相应的图标。

通过这种方式,可以在 HTML 中轻松地引用 SVG Sprite 文件中的特定图标,并实现图标的复用。

五、动态 Fetch CDN SVG 图标

在业务开发中,可能有些自定义图标我们是存放在 cdn 上面的,那这时候业务代码想要使用,可能需要使用 JavaScript 去加载 svg 才能使用它完全的特性。

如下实现一个 SvgIconCDN 组件,支持:

  1. 从 cdn 加载 svg
  2. 支持设置颜色
  3. 支持设置宽高,并根据当前比例自适应
  4. 支持如下使用姿势
<SvgIconCdn
  url="https://example.com/path/to/svg/icon_close.svg"
  :width="16"
  :height="16"
/>

而且我们要能够处理加载下来的 svg 转换为统一的可用 svg。因为:

  1. svg 可能自带颜色,比如 fill 或者 stroke 写死了,那对于后续颜色自定义是无法生效的
  2. svg 可能自带宽高绝对值,那我们应该将其转换为支持自适应的单位,并保持宽高比
  3. svg 可能不带 fill 属性,那也是不利于后续颜色自定义的

具体代码:

<script lang="ts">
import {
  defineComponent,
  ref,
  watch,
  createVNode,
  nextTick,
  computed,
} from "vue";
import store from '@/store';
const cacheSvg = {};
export default defineComponent({
  props: {
    url: {
      type: String,
      required: true
    },
    width: {
      type: Number,
      required: true,
    },
    height: {
      type: Number,
      required: true,
    },
  },
  setup(props) {
    // store.state.scale - rem 比例
    const fontSizeWidth = computed(() => store.state.scale * props.width);
    const fontSizeHeight = computed(() => store.state.scale * props.height);
    
    const iconRef: any = ref(null);

    const fetchSvg = (url: any) => {
      return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        xhr.open("get", url, true);
        xhr.responseType = "blob";
        xhr.onload = () => {
          if (xhr.status === 200) {
            const reader: any = new FileReader();
            reader.readAsText(xhr.response);
            reader.onload = () => {
                let resultStr = reader.result;
                // 提取遮罩内的内容
                // 避免在后面的转换中影响到遮罩内容
                const maskTagStrArr = reader.result.match(/<mask([\s\S]*?)<\/mask>/g);
                if (maskTagStrArr) {
                  for (const index in maskTagStrArr) {
                    resultStr = resultStr.replace(maskTagStrArr[index], `__maskTag${index}__`);
                  }
                }

                // 完成四类属性的转换
                // width="200" height="100" -> width="1em" height=".5em"
                // fill="red" -> fill="currentColor"
                // stroke="red" -> stroke="currentColor"
                // opacity
                const aspectRatio = props.width / props.height;
                resultStr = resultStr.replace(/(svg.*)width="(\d)*"/g, '$1width="1em"')
                  .replace(/(svg.*)height="(\d)*"/g, `$1height="${ 1/aspectRatio }em"`)
                  .replace(/\bfill="\S*?"/g, 'fill="currentColor"')
                  .replace(/\bstroke="\S*?"/g, 'fill="currentColor"')
                  .replace(/\bopacity="\S*?"/g, 'opacity="1"');

                // 恢复遮罩内的内容
                if (maskTagStrArr) {
                  for (const index in maskTagStrArr) {
                    resultStr = resultStr.replace(`__maskTag${index}__`, maskTagStrArr[index]);
                  }
                }

                // 为没有 fill="currentColor" 属性的 svg 补充该属性 方便外部设置颜色
                resolve(resultStr.includes("fill") ? resultStr : resultStr.replace(/(svg)(.*)(width="1em")/g, '$& fill="currentColor"'));
            };
          }
        };
        xhr.onerror = () => reject("");
        xhr.send();
      });
    };

    
    const sleepFunc = (ms = 0) => {
      return new Promise((resolve) => setTimeout(resolve, ms));
    };
    const getContentFromCache = async (url: any) => {
      if (cacheSvg[url]) {
        // cacheSvg[url] === dom content
        // 如果有缓存 直接读取
        return cacheSvg[url];
      }
      if (cacheSvg[url] !== void 0) {
        // cacheSvg[url] === ''
        // 为什么要设置这个逻辑?
        // 因为当一个页面多次使用到这个组件时,并且引用的url一样,添加该逻辑可以等待最初的资源加载完成后进行复用,避免一个页面多次请求同一个资源
        while (!cacheSvg[url]) {
          await sleepFunc();
        }
      } else {
        // 如果该url是初次请求
        cacheSvg[url] = '';
        const svgContent = await fetchSvg(url);
        cacheSvg[url] = svgContent;
      }
      return cacheSvg[url];
    };

    const getNameFromUrl = (url: any) => {
      const lastIndex = url.lastIndexOf("/");
      const dotIndex = url.lastIndexOf(".");
      const str = url.substring(lastIndex + 1, dotIndex);
      return str.replace(/\B([A-Z])/g, "-$1").toLowerCase();
    };

    watch(() => props.url, async () => {
      if (!/.*\.svg/.test(props.url)) {
        return;
      }
      const svgHtml = await getContentFromCache(props.url);
      nextTick(() => {
        iconRef.value && (iconRef.value.innerHTML = svgHtml);
      });
    }, {
      immediate: true
    });

    const iconClass = `my-project-svg-icon__${getNameFromUrl(props.url)}`;
    return () => createVNode("div", {
      "ref": iconRef,
      "class": `ucls-svg-icon ${iconClass}`,
      "style": `font-size: ${fontSizeWidth.value}px; width: ${fontSizeWidth.value}px; height: ${fontSizeHeight.value}px`,
    });
  },
});
</script>
<style lang="scss" scoped>
.my-project-svg-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
</style>

注意,该方案会导致 cdn 流量的损耗。

六、SVG 压缩

SVGO(SVG Optimizer)是一个用于优化 SVG 文件的工具,它可以帮助减小 SVG 文件的大小,从而加快页面加载速度。SVGO 可以通过移除无用的信息、优化路径、压缩文本等方式来减小 SVG 文件的体积,而不影响图像的质量和可视效果。

SVGO 提供了多种优化策略,包括:

  1. 移除注释:删除 SVG 文件中的注释,减少文件大小。

  2. 移除元数据:移除不必要的元数据,例如编辑器生成的信息。

  3. 优化路径:简化 SVG 中的路径,使其更紧凑,但保持形状不变。

  4. 删除不可见元素:删除不显示在 SVG 视口中的元素,以减少文件大小。

  5. 压缩文本:对 SVG 文件中的文本进行压缩,减小文件体积。

  6. 优化颜色和样式:合并相同颜色和样式的元素,以减少重复信息。

  7. 移除冗余属性:删除 SVG 元素中的冗余属性,例如默认的 fill="black"

  8. 转换形状:将复杂的图形转换为简单的形状,例如将 <path> 转换为 <rect>

  9. 缩短 ID 和类名:将 ID 和类名缩短为更短的形式,以减小文件大小。

非常推荐在使用 svg 之前进行压缩处理。。另外 SVGO 可以作为命令行工具或 Node.js 模块使用,并且可以与构建工具(如 Webpack、Gulp 等)集成,从而在开发过程中自动优化 SVG 文件。通过使用 SVGO,开发者可以更轻松地减小网页中 SVG 图像的大小,提高网页的性能表现。

七、svg vs canvas

如果接触过绘制业务,应该知道 ant x6 用的是 svg、ant g6 用的是canvas,为什么?

参考阅读:选择 Canvas 还是 SVG

Canvas 适合小画布、大数据量渲染的场景(如热力图、表格、散点图),Svg 则适合画布大、内存占用要求高(如移动端)、缩放、平移等高频操作帧率要求高的场景(如流程图绘制)。

比较流行的看法是 SVG 做定制和交互更有优势,因为有类似 DOM 的结构,能快速应用浏览器底层的鼠标事件、CSS 样式、CSS3 动画等。不过,基于 Canvas 做上层封装后也能实现类似的定制和交互,并且自由度更高。

  • 27
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值