目录
一、介绍:scalable vector graphic
SVG 是可缩放矢量图形(Scalable Vector Graphics)的缩写。它是一种用于描述二维矢量图形的 XML 格式的标记语言。与像素图形(如 JPEG、PNG 等)不同,像素图形是基于像素的位图,SVG 使用的是基于几何形状的描述,因此它可以无限放大而不失真,适用于各种尺寸的显示设备,从小到移动设备的屏幕到大到高分辨率的显示器,甚至打印品质的输出。
SVG 具有以下特点:
-
可缩放性:SVG 图像可以无限缩放而不会失真,因为它们是基于
数学描述
的矢量图形。具体而言,SVG 图像是由几何形状(如直线、圆、曲线等)和其属性(如颜色、填充等)的数学描述组成的。这些描述不依赖于特定的分辨率或像素密度,而是以数学公式
的形式保存。另外,SVG 中的图形元素是相对位置和比例保持一致的,无论图像的尺寸如何变化,图形元素之间的相对位置和比例都保持不变,因此在放大时不会出现失真。 -
文本可搜索和编辑:SVG 内的文本和图形元素是可编辑和可搜索的,这使得 SVG 图像非常适合用于包含文本和图形的
图表
、地图
等。 -
支持互动性和动画:SVG 支持添加交互式元素和动画效果,可以通过 CSS 或 JavaScript 进行控制。
-
跨浏览器兼容性:现代 Web 浏览器(如 Chrome、Firefox、Safari 等)都支持 SVG,使得 SVG 成为 Web 开发中常用的图形格式之一。
-
可压缩性和无损压缩:SVG 图像可以通过压缩算法进行压缩,以减小文件大小,从而加快加载速度。而且由于 SVG 使用文本文件格式(XML)来存储图形信息,它可以通过无损压缩算法(如 gzip)进行压缩。这意味着即使文件大小很小,也可以保留所有的细节和精度。
SVG 被广泛用于 Web 开发
、数据可视化
、图表绘制
、图标设计
等领域,因为它提供了一种灵活、清晰且可交互的图形表示方式。
二、基本使用
- 直接在 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>
效果如下
- 使用
<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,因此点击事件无法触发。
- 使用 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>
- 使用
<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>
- 使用 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"></i>
可以看到,通过 <i>
字体标签并指定自定义图标。
SVG 字体通常用于 Web 开发中,特别是在需要自定义图标或自定义符号的情况下。它们的一些特点包括:
-
矢量图形:SVG 字体中的每个字符都是一个矢量图形路径,因此它们可以在任何尺寸下无损放大。
-
灵活性:与传统字体相比,SVG 字体更灵活,因为它们可以包含任意形状和复杂的图形作为字符。
-
图标字体:SVG 字体经常用于创建图标字体。开发者可以将一系列图标转换为 SVG 字体,并使用 CSS 控制字体的大小、颜色等属性来显示图标。
-
跨浏览器兼容性:现代 Web 浏览器支持在 CSS 中引用 SVG 字体,并能够在页面上正确显示。
使用 SVG 字体可以让开发者轻松地在网页中嵌入自定义图标或符号,并且由于其矢量性质,图标可以在不同的显示设备和分辨率下保持清晰度。
当使用工具如 fontello
、Font Awesome
、Material Icons
等预定义的图标字体时,这些工具实际上会将图标打包为字体文件(通常是 TrueType 字体或 OpenType 字体),而不是单独的 SVG 文件。
这些字体文件中包含了一系列图标,每个图标对应于字体中的一个字符。具体的原理如下:
-
字体文件包含图标路径:在字体文件中,每个字符都被映射到一个特定的图标路径。这些路径描述了每个图标的矢量图形。
-
CSS 类与字符关联:
图标字体工具
会为每个图标定义一个对应的 CSS 类。这些 CSS 类通常由工具自动生成,并且与字体文件中的字符映射关系紧密。通过应用相应的 CSS 类,可以在页面中显示特定的图标。 -
字体文件的引入:在使用图标字体之前,需要将字体文件引入到项目中。这通常通过在 HTML 或 CSS 中使用
@font-face
规则来实现。 -
使用 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 文件中的特定图标时,原理如下:
- 创建 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>
- 在 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>
- 图标显示原理:
- 当浏览器解析 HTML 时,遇到
<use>
标签时,它会查找指定的 SVG 文件(在xlink:href
属性中定义的文件路径)。 - 然后,浏览器会在 SVG 文件中查找与
<use>
标签中指定的id
属性相匹配的<symbol>
元素。 - 最后,浏览器将找到的
<symbol>
元素的内容复制到当前文档中,并将其插入到<use>
标签所在的位置,从而显示相应的图标。
通过这种方式,可以在 HTML 中轻松地引用 SVG Sprite 文件中的特定图标,并实现图标的复用。
五、动态 Fetch CDN SVG 图标
在业务开发中,可能有些自定义图标我们是存放在 cdn 上面的,那这时候业务代码想要使用,可能需要使用 JavaScript 去加载 svg 才能使用它完全的特性。
如下实现一个 SvgIconCDN
组件,支持:
- 从 cdn 加载 svg
- 支持设置颜色
- 支持设置宽高,并根据当前比例自适应
- 支持如下使用姿势
<SvgIconCdn
url="https://example.com/path/to/svg/icon_close.svg"
:width="16"
:height="16"
/>
而且我们要能够处理加载下来的 svg 转换为统一的可用 svg。因为:
- svg 可能自带颜色,比如 fill 或者 stroke 写死了,那对于后续颜色自定义是无法生效的
- svg 可能自带宽高绝对值,那我们应该将其转换为支持自适应的单位,并保持宽高比
- 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 提供了多种优化策略,包括:
-
移除注释:删除 SVG 文件中的注释,减少文件大小。
-
移除元数据:移除不必要的元数据,例如编辑器生成的信息。
-
优化路径:简化 SVG 中的路径,使其更紧凑,但保持形状不变。
-
删除不可见元素:删除不显示在 SVG 视口中的元素,以减少文件大小。
-
压缩文本:对 SVG 文件中的文本进行压缩,减小文件体积。
-
优化颜色和样式:合并相同颜色和样式的元素,以减少重复信息。
-
移除冗余属性:删除 SVG 元素中的冗余属性,例如默认的
fill="black"
。 -
转换形状:将复杂的图形转换为简单的形状,例如将
<path>
转换为<rect>
。 -
缩短 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 做上层封装后也能实现类似的定制和交互,并且自由度更高。