当前位置:网站首页 > 编程语言 > 正文

字体图标和svg图标的区别是什么(字体图标 svg)



在 Web 开发中,经常会需要在页面中引入一些特殊字体,这些字体通常不在系统字体库的范围内,并且动辄 4~5MB,甚至有些字体超过 10MB,会影响用户加载体验,尤其在手机端使用移动网络的情况下。

针对不同的业务场景,通常会有以下几种解决方案:

当需要显示特殊字体的文案为固定文案时,直接使用切图可能是最常见的一种解决方案,无论是透明的 PNG 切图,还是用矢量的 SVG,都能在便捷度和加载体验上有不错的体验。当然,使用 PNG 的时候需要注意高分辨率屏幕的适配,使用1倍图在高分辨率的屏幕下肉眼很容易看出模糊。

当需要显示特殊字体的文案是动态文案时,就没办法直接用文字切图来实现了。通常在对用户加载体验没有特别要求的场景下,可直接通过 @font-face 来直接引入特殊字体的文件进来。缺点也显而易见,正如前文所说,动辄 4~5MB,甚至超过 10MB 的字体文件,会比较影响用户的加载体验,在首屏需要显示的场景下,用户往往会先看到先渲染默认字体,再闪现成特殊字体的情况。

字体裁剪技术,可通过省略字体变体(如某些字体会包含多个粗细)、裁剪字体字符集(省略不常用的字符)、通过其他压缩算法(如 woff、woff2)等方式来降低字体体积。当文案内容相对可控的情况下,用该方案可比直接加载原始字体要节省更多体积。但若字符裁剪过多,会导致缺少部分字符,在显示时出现字体不一致的情况;若字符裁剪过少,体积仍会较大。并且通常情况下,裁剪后的字符数也是远大于用户实际需要渲染的字符数,还是会有非常大的不必要的下载流量。

在动态渲染特殊字体文案的场景下,为了提升用户的加载体验,我们研发了一个创新的解决方案:「Font2svg」。在介绍实现原理前,先简单介绍下字体相关的基础知识。

计算机中,字符是任意单个文本元素,例如字母、数字、标点符号、汉字甚至 Emoji 表情等。字符通常是构建文本的基本单位。

字符集是一组预定义的字符的集合。它是对字符进行分类和组织的方式,以便在计算机系统中能够使用。会为每一个字符分配一个唯一的 ID,叫做 Code Point(码点、内码),常见字符集:ASCII、GB2312、GBK、Unicode 等。

字符编码是一种将字符映射到特定二进制数的规则,以便在计算机中存储。通常字符集和字符编码都是成对出现,如 ASCII、GB2312、GBK 等,都是既代表了字符集,也代表了对应的字符编码。而 Unicode 比较特殊,有多种字符编码,如 UTF-8、UTF-16 等。

字体家族是指具有相似设计特征的一组字体。这些字体共享类似的外观,但在细节上可能略有不同,如粗细、斜体等。例如:「思源宋体」就是一个字体家族。

字体样式是字体家族中特定字体的变体,它定义了字体的外观。例如:Light、Regular、Bold、Heavy 等。

字型是一个字体家族下的特定样式的字体,例如:思源宋体-Bold。

字形是指一个单独字符在特定的字体(字型)中的具体外观或形状。每个字符都有对应的字形,它们描述了字符的细节和轮廓。如下图,就是「哔」这个字符在不同的字体下不同的字形:

字体度量是指字体中字符的尺寸和位置信息。它包括了字符的宽度、高度、上沿线(ascender)、下沿线(descender)、基线(baseline)等数据。字体度量在排版中非常重要,它决定了字符之间的间距和对齐方式,确保文本在屏幕或打印上的合理布局。

字体轮廓是字形的矢量图形表示,一个字形由若干轮廓组成,每个轮廓由若干条线段或贝塞尔曲线闭合而成。根据字体格式不同,使用的贝塞尔曲线的阶数也有区别。TrueType(TTF)字体采用二次贝塞尔曲线(3点控制),而 OpenType(OTF)字体采用三次贝塞尔曲线(4点控制)。

一个轮廓组成的字母C:

多个轮廓组成的字母B:

二次贝塞尔曲线:

三次贝塞尔曲线:

本方案的原理本质上还是利用 SVG 来渲染特殊字体,通过上面的介绍,我们知道,字体的轮廓由若干线段和贝塞尔曲线组成,得知了线段的坐标和贝塞尔曲线的控制点,我们就可以绘制出轮廓的 SVG,而有了 SVG 我们就可以用来显示。因此我们可以通过解析字体文件,按需获取对应字符的 SVG 来避免加载整个字体文件。如何去唯一表示一个特定字符呢?那就是通过字符的 Unicode 编码。如下图所示,我们在渲染「哔哩哔哩乾杯」这几个字符的时候,只需要根据「哔」、「哩」、「乾」、「杯」这四个字符的 Unicode 编码去获取他们的 SVG,然后组合起来显示即可。

考虑到性能,如果每个字符都实时去请求服务端生成 SVG 的话,并发小的时候没影响,并发大了的话,这个字体转 SVG 的服务就会成为瓶颈。不过由于这些 SVG 生成之后就几乎不会变,所以可以预先全部生成好,放到 CDN 上,每次直接根据 Unicode 编码去 CDN 上获取指定字符的 SVG 即可。这样既避免了服务端的压力,还利用了 CDN 的特性,可以让用户更快的获取到所需的资源。

「字体转 SVG」只是第一步,在实际渲染中,还需要修改 SVG 的样式,例如字体颜色、下划线等,因此我们还做了一个前端的组件,用来对获取到的 SVG 进行样式的处理以还原字体的样式。这个组件实现了根据所需字符「动态加载 SVG」以及「SVG 复原字体样式」,在使用起来只需要传入字体名称、所需显示的字符、一些字体样式等,大大减少了前端接入的复杂度。

整体流程如下图,页面开发阶段,在 Font2svg 后台预先上传字体文件,解析所有字符的 SVG,并预先上传至对象存储/CDN 上。在页面加载时,通过前端组件传入需要渲染的字符和对应字体的配置,从 CDN 拉取特定字体特定字符的 SVG,并根据样式对字符的 SVG 进行二次编辑,复原字体上色、描边、下划线等样式。

后台效果预览:

字体解析服务通过 Python 实现,借助 freetype(https://github.com/rougier/freetype-py)这个库对字体进行解析,遍历字体中的每一个字符,每个字符的字形(glyph)包含了轮廓(outline)和度量(metrics)数据,通过 outline.decompose 方法,可以将字符的轮廓数据分解为一系列的路径段,其中每个路径段都是一个点序列,表示字符的一部分。这些路径段可以是直线段、二次贝塞尔曲线或三次贝塞尔曲线。通过这些路径段的类型和点坐标,再通过 svgpathtools 这个库绘制成 SVG 的路径。

除了 SVG 的路径之外,还有一个很重要的内容,就是 viewBox。因为在字体中,表达一个字形,除了轮廓之外,还有一个很重要的 metrics 信息,metrics 信息没有还原好,在字体排版的时候就会出现问题。如果以轮廓本身的大小作为容器的话,一些未占满空间的文字,或者一些标点符号,在排版时,就会被缩放的很大;而如果以字体的上沿线和下沿线来计算一个方形容器显示的话,对于中文字符问题不大,但对于半角字符,其中间的间隙就会显得太大,如下图:

因此,我们需要从 Metrics 中提取每个字形的基本信息。以横排为例,取每个字形自己的宽度来绘制 viewBox,高度还是通过上下沿线相减来计算。这样在排版时就可以得到想要的效果了:

在有了 path、viewBox、width、height 等信息之后,我们就可以直接生成能表达具体字形所需要的 SVG 文件了。

还有一个特殊字符,那就是空格,他是不包含任何轮廓的,解析轮廓的时候结果是空的。在生成 SVG 的时候,如果 path 是为空会失败,这时候可以生成一个和 viewBox 同样大小的透明度为 0 的轮廓用来占位。

在前面的步骤中,我们为每个字形预先生成好了对应的 SVG 文件,但我们知道,SVG 文件是一个静态的文件,其字体颜色等样式是固定的,在前端使用的时候,如何去动态设置颜色等样式呢?

我们在前端去加载 SVG 字体文件的时候,并不是直接通过 img 标签的方式来引入 SVG,而是在前端组件中去下载 SVG 文件内容并解析 DOM 对象,然后根据需要去修改对应的属性或添加额外的样式,例如修改 path 的 fill 属性来设置颜色,修改完样式后再把修改后的 SVG 标签直接插入到文档中。还有一些字体全局的样式信息,例如下划线的位置、粗细等,在不同的字体中,也是有不一样的配置的。我们需要想办法把这些信息也通过 SVG 传递过来。解决方法也很简单,我们在 SVG 的根节点上添加一个自定义的属性,将所需传递的信息转成 JSON 格式然后塞到这个属性里,前端在解析 SVG 的 DOM 时,把这个属性中的数据取出来解析并渲染就可以了。

上面截图中,underline 和 underlineThickness 就分别代表了下划线位置和粗细信息,在解析到这两个信息后再做一些转换处理,可通过 ::before 伪元素来绘制下划线:

创作中心每周会给 UP 主推送荣誉周报,页面中首屏有非常大的篇幅显示的是本周关键词,效果如下图:

这个场景有两处使用了特殊字体「方正FW筑紫黑」,一个是卡片标题「本周关键词」这几个字,这个是固定标题,用传统的切图方式也是 OK 的,但下面的关键词是根据 UP 主上周的投稿、评论、弹幕、播放等数据由服务端动态下发的,这个就无法通过切图来实现了,需要穷举的关键词特别多。

在使用本方案之前,该页面使用的是直接引入字体文件来设置字体。由于该页面加载资源较多,在加载时不可避免会出现字体闪烁、图片资源加载过程页面抖动的问题,在手机上通过 4G 网络访问时尤其明显。为了避免这种情况,该页面渲染之前加入了一个 Loading 页,在 Loading 页上加载所需的所有资源,包括图片、字体文件等。

使用本方案优化前后的数据对比如下:

尽管本方案所需加载的资源量相比直接引入字体包要小很多,但请求数量会根据字符数有所上升,在极端情况下,网络失败会导致资源请求失败;另外,一些字体包含的字符数有限,在请求这个字体本身就不存在的字符的时候,也会出现请求失败的情况。在缺失 SVG 的情况下如何进行兜底的显示也是我们需要考虑的内容。

在组件侧获取不到对应的 SVG 时,可通过系统默认字体直接在字符位置进行渲染作为兜底,与浏览器默认的处理方式一致。具体效果如下:

由于兜底的默认字体与目标字体不同,他们的容器大小和下划线位置、下划线粗细等设置均有可能不一样,所以在开启了下划线的情况下,会出现下划线高度和粗细不一致的现象,如下图:

为了解决这个问题,我们在渲染下划线的时候,对于兜底的默认字体,不采用 css 样式来绘制下划线,而是与 SVG 一样,通过伪元素并且使用目标字体的下划线设置来进行绘制,最终效果如下图:

在本文中,我们深入探讨了 Font2svg 方案的技术原理和实现细节。我们通过将字体转换成 SVG 进行渲染,降低了用户渲染特殊字体动态文字所需下载的文件大小,提高了加载速度,从而优化了特殊字体在 Web 页面中的加载体验。

这套方案在动态渲染特殊字体文案的场景下具有广泛的应用前景,不只是在 Web 端,在客户端上也同样适用。它能为设计师和开发者提供更灵活、高效的特殊字体渲染方案,让设计师不会再因为字体包体积而放弃使用一些艺术字体,同时也让开发者不再为字体包的大小而头疼。

到此这篇字体图标和svg图标的区别是什么(字体图标 svg)的文章就介绍到这了,更多相关内容请继续浏览下面的相关推荐文章,希望大家都能在编程的领域有一番成就!

版权声明


相关文章:

  • 环回地址怎么算(环回地址怎么看)2025-07-28 18:27:06
  • 拆封鼠标什么意思(拆封鼠标为什么这么便宜)2025-07-28 18:27:06
  • 哔哩哔哩如何二维码登录(哔哩哔哩如何二维码登录游戏)2025-07-28 18:27:06
  • 密码官网(音乐密码官网)2025-07-28 18:27:06
  • 十大最强法则排名(十大最强法则排名第一)2025-07-28 18:27:06
  • max31865三线(ma5ma10ma20三线详解视频)2025-07-28 18:27:06
  • ip域址反查询(ip地址反查网站)2025-07-28 18:27:06
  • 本机信息怎么查看vivo(vivo手机如何查看本机)2025-07-28 18:27:06
  • sigmoid什么意思(silm什么意思中文)2025-07-28 18:27:06
  • 拒绝我访问该文件夹(拒绝我访问该文件夹怎么解决)2025-07-28 18:27:06
  • 全屏图片