自定义字体服务 - 基于Node的Web字体解决方案

news2024/11/17 23:34:46
  • 自定义字体服务 - 基于Node的Web字体解决方案
    • 背景
    • 技术选型
    • @font-face介绍
    • 实现方案(介绍字体设计、转换、兼容性处理等技术实现细节。)
    • 接口实现方式(实现中遇到的问题和解决方案)
    • 总结
    • 开源地址
    • 参考

背景

在前端开发中,字体是非常重要的组成部分,因为字体可以为页面提供良好的可读性和设计感。在设计中,经常需要使用一些特殊的字体,以突出设计效果或满足特殊的审美需求。但是,在使用一些奇怪字体的需求下,使用图片来替代字体可能会影响页面性能,特别是图片大小和加载时间的问题。

一般来说,为了解决这个问题,我们会使用CSS3中的@font-face提供的自定义字体功能。通过使用@font-face,我们可以自定义一个字体文件,将字体文件嵌入到网页中,从而避免使用图片来替代字体。这种方法可以解决一些特殊字体的问题,但是可能会面临一些挑战。英文字体文件通常比中文字体文件小得多。一个常见的英文字体文件大小通常不超过100KB,而一个中文字体文件可能会超过10MB。这是因为中文字体通常包含大量的字符,包括不同的汉字、符号和字体效果等。这种情况下,为了一两个字或者一行字,就需要加载几MB大小的整个字体文件,显然是不划算的。此外,在网络差的环境下,即使用户浏览完页面,字体文件也可能还没有加载完成,这时候已经没有意义了。
换个角度想想,虽然一个中文字体包含了几千个常用字,但一个网页去掉重复字的情况下,往往只包含数十个到数百个字。因此,是否有必要加载一个完整包含数千个字的字体文件,可以考虑只加载网页需要的字体,这样能够减小加载字体文件的大小,同时也能保证字体加载的速度。

技术选型

针对上面的两个问题,常见的解决方案有:

  1. font-spider :字蛛是一个智能 WebFont 压缩工具,它能自动分析出页面使用的 WebFont 并进行按需压缩。https://github.com/aui/font-spider

    * 优点:提供大量可商用字体,便捷获取。
    * 缺点:需要付费,版权复杂,可用性有限。
    
  2. font-carrier:font-carrier是一个功能强大的字体操作库,使用它你可以随心所欲地操作字体。https://github.com/purplebamboo/font-carrier

    * 优点:可以收集和备份已经购买的商用字体。
    * 缺点:需要确保拥有商用字体的正版授权。
    
  3. 有字库:有字库是全球第一中文web font(网络字体)服务平台,致力于美化网页界面,降低网页设计和维护的难度,同时提升效率。http://www.webfont.com/

    * 优点:提供大量可商用字体,便捷获取。
    * 缺点:需要付费,版权复杂,可用性有限。
    
  4. fontmin: fontmin是一个纯 JavaScript 字体子集化方案,提供了 APICLI的使用方式。是一个非常成熟实用的字体优化工具。http://ecomfe.github.io/fontmin/#feature

    * 优点:可以进行字体压缩和子集化优化。
    * 缺点:需要安装依赖,优化效果取决于字体本身。
    

@font-face介绍

@font-face 是 CSS 的一个规则,用于定义自定义字体。它允许网页设计者使用自定义字体,而不必依赖用户的计算机上已安装的字体。
使用 @font-face 规则,可以将字体文件(通常是 TrueType 字体文件或 OpenType 字体文件)嵌入到网页中,并通过 CSS 指定字体的名称和样式

以下是一个示例,演示如何使用 @font-face 定义并应用自定义字体:

@font-face {
  font-family: 'MyCustomFont';
  src: url('myfont.ttf');
}

body {
  font-family: 'MyCustomFont', sans-serif;
}

在上面的示例中,myfont.ttf 是字体文件的路径。通过 @font-face 规则定义了一个名为 ‘MyCustomFont’ 的自定义字体,然后在 body 元素中应用了这个字体。

实现方案(介绍字体设计、转换、兼容性处理等技术实现细节。)

综合考虑之后,我们采用的是fontmin。
Alt text

这是官网的一个例子,从一个大小为4.2M,包含7500+的字体包中,提取了7个字,输出的子集字体大小在4.5K左右。

结合我们的业务场景,为了方便管理,我们结合Node.js,开发了字体服务管理系统,实现了字体文件的上传和管理,目前支持TTF和OTF两种字体格式,可以根据字体文件和需要的文字,动态压缩并输出子集字体同时上传到cdn,同时提供了一键复制css的功能,复制完成后直接放在自己的css文件中,就可以直接使用。

使用步骤

第一步:在字体库管理列表维护所使用的字体文件,得到字体库名称以及将字体库同步传到cdn,获得url地址;
Alt text
第二步:在字体配置管理页面根据需要的字体和使用的文字得到压缩后的使用地址;
Alt text
第三步:拿到“复制地址”里的链接,通过@font-face设置自定义字体,就能实现我们想要的效果啦;

// 定义字体名称,src里就是复制的地址
@font-face {
  font-family: yezi; //自己起个字体名字
  src: url("https://xxx/yezi_5f27fae9ad916de19343e2b8a1635680.ttf");
}
// 使用
.font-test{
  font-size: 46px;
  font-family: yezi;  //设置成你上面自定义的字体
}

Alt text

接口实现方式(实现中遇到的问题和解决方案)

在维护的自己的字体文件时,能拿到字体包对应的key值,此key值是唯一的,在压缩字体的时候,基于接口设计了通用的解决方案,接口接收使用的字体包对应的key和需要压缩的文字,压缩的时候通过调用接口生成子集文件并同步上传到cdn,返回cdn地址。

  https://example.com/api/font?key=yezi&fontlibfile=url&word=word&pageNme=pageNmae
// key 表示使用的字体包对应的key
// fontlibfile 表示使用的字体包对应的url(我们的字体包是存在cdn上的)
// word 表示需要提取的文字
// pageNme 为了记录是哪个项目和页面使用(只针对我们的业务场景)

接口实现,因为我们的字体包存在cdn上,fontmin不支持远程文件做为源字体包,所以我们需要先将字体包下载下来临时存放在本地,压缩完成之后再删掉。

export default async (fontlibfile: string, key: string,  pageName: string, word: string) => {
  // fontlibfile 表示使用的字体包对应的url(我们的字体包是存在cdn上的)
  // key 表示使用的字体包对应的key
  // word 表示需要提取的文字
  // pageNme 为了记录是哪个项目和页面使用(只针对我们的业务场景)
    
  let data: any = {};
  let text = word;

  if (fontlibfile) data.fontlibfile = fontlibfile;
  if (text) data.word = text;
  if (pageName) data.pageName = pageName;
  if (key) data.key = key;

  data.fontUrl =`https://example.com/${text}.ttf`;

  if (!fontlibfile) throw new Error('字体库或key不存在');
  try {
    const cacheFile = path.join(__dirname, `./font/`);
    // 用于存放下载下来的源字体包
    if (!fs.existsSync(cacheFile)) {
      fs.mkdirSync(cacheFile, {recursive: true});
    } else {
      log.info("临时存放目录已存在");
    };
    const filePath = await tempDownload(fontlibfile);  // 获取下载之后的源字体文件地址
    const extname = path.extname(filePath);
    const useTtfType = (extname === '.ttf' || extname === '.TTF') ? true : false;  //因为支持ttf和otf两种格式,需要区分一下
    const fontmin = new Fontmin()
      .src(filePath)
      .use(useTtfType ? Fontmin.glyph({ text }) : Fontmin.otf2ttf({ text }))  //不管是ttf还是otf,最终我们都转成ttf格式

      const content:any = await new Promise((resolve, reject) => {
        fontmin.run((error, files) => {
          if (error) {
            return reject(error);
          }
          return resolve(files[0].contents);
        });
      });
    await writeFile(`${cacheFile}${text}.ttf`, content);
    const file = fs.readFileSync(`${cacheFile}${text}.ttf`);
    let res;
    res = await IMGCLIENT.client.put(`dm_auto_config_server/${text}.ttf`, file); // 将压缩后得到的子集字体文件上传到cdn
    fs.unlink(`${__dirname}/font/${text}.ttf`, (err) => {
      if(err) {
        log.info('删除文件失败');
      };
    });
    return res.url; // 拿到压缩后的子集字体文件地址
  } catch(error) {
    throw new Error('字体文件压缩失败');
  }
}

为了解决文字重复和避免重复压缩同样字体造成资源浪费,需要对文字进行去重,并考虑缓存。

    // 对文字去重以及排序
    let text = Array.from(new Set(word))
    .sort()
    .join('');

    const textMd5 = `${key}_${md5(text)}`;  // 并将上面的代码中的text替换成textMd5
    const exist = await GetFontManagByBufferName(`${textMd5}`); // 去数据库中差是否有缓存
    if (exist !== null) {
      throw new Error(`字体配置中已经存在了字体库为${fontlibName}key值为${key}的字体配置`);  // 直接去复制已有的文件就好了
    }
    

如果对已经压缩好的文字进行编辑,但是已经有很多页面使用了,那编辑的时候还要避免子集文件地址的变更。不然一旦重新编辑,地址就会发生改变,那已经使用的页面还需要更换对应的地址并重新发布,非常不友好。

// 编辑时入参增加 id:新建时生成的唯一值,bufferName:新建时生成的由key和textMd5组合而成

const textMd5 = id ? bufferName : `${key}_${md5(text)}`;

解决了地址不能变更的事情,新的问题又来了,那就是cdn缓存,因为我们的文件是自动上传到cdn的,地址不变,如果不强制清缓存,请求不到新的资源。

// 我们的资源是放在阿里云上的,查了api,发现api里有上传时强制不缓存的设置

res = await IMGCLIENT.client.put(`dm_auto_config_server/${textMd5}.ttf`, file, { headers: {'Cache-Control': 'no-cache, no-store, max-age=0, must-revalidate', 'Pragma': 'no-cache', 'Expires': -1} });

到这里,自定义字体服务的核心功能就完成了。来看下效果吧
效果
原来的叶子字体包大小是4.5M,压缩之后提取了我们需要的文字,大小为4.1k。

总结

在这个自定义字体服务的例子中,我们实现了对字体的优化和管理。通过合理的设计和优化,我们可以减小字体文件的大小,提高网页的加载速度和用户体验。但是,这只是优化字体的一个方面。为了确保字体的质量和用户体验,我们还需要注意字体的可读性、风格、版权等问题。同时,在开发过程中,我们也需要考虑字体的兼容性和响应式设计等方面,以适应不同设备和不同屏幕尺寸的用户。
fontmin 虽然很厉害,但不是万能的,由于 font 文件表之间极其复杂的相互关联,转换得到的子字体将会破坏一部分关联信息(比如会影响字体竖排时的间距)。在压缩数字的时候,如果不加入一个标点符号,会导致压缩失败,等等。希望对大家有所帮助,如果有疑问可以多多交流与反馈,共同改进这个方案。

参考

  1. FontFace https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face
  2. fontmin http://ecomfe.github.io/fontmin/#feature
  3. 基于 Node.js 的 WebFont 解决方案

作者:洞窝-小米

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/791882.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MySQL中LEFT JOIN的用法

原理 LEFT JOIN 关键字返回左表(table1)中的所有记录,以及右表(table2)中的匹配记录(如果有) 注意: LEFT JOIN 关键字返回左表(Customers)中的所有记录&…

家具小程序搭建攻略

要想快速搭建一个家具小程序商城,乔拓云平台是一个非常方便的选择。下面就来详细介绍一下制作的具体步骤。 首先,登录乔拓云平台,进入后台管理页面。在页面上找到【商城】选项,点击【去管理】进入后台管理页面。 在后台管理页面中…

【算法与数据结构】222、LeetCode完全二叉树的节点个数

文章目录 一、题目二、一般遍历解法三、利用完全二叉树性质四、完整代码 所有的LeetCode题解索引,可以看这篇文章——【算法和数据结构】LeetCode题解。 一、题目 二、一般遍历解法 思路分析:利用层序遍历,然后用num记录节点数量。其他的例如…

lama cleaner

这里写自定义目录标题 安装参数包含的额外plugins 安装 conda create --name lamacleaner python3.10 pip install -r requirements.txt pip install gfpgan pip install realesrgan pip install rembg pip install .如果安装本package报错,可以尝试改&#xff1…

【2023】Nacos下载与安装配置(2.2.3版本示例)

1、Nacos概述 1.1 什么是Nacos Nacos 是阿里巴巴推出来的一个新开源项目,这是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。 Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服…

AndroidStudio Memory profiler(内存分析器)

1.Record Java/Kotlin allocations 查看java 层中对象的调用栈和短时间内创建对象的次数。可用于内存抖动快速分析,可用快速查找到该对象的调用栈(等同于mat) 从上图可见,短时间内创建了23个char[] 数组,其中最大的char[] 占用20k, 查看cll stack 调用…

前端将css.html.js打包到一起打开

过程我是按照下面的执行的,大家可以直接参考这个博客里的过程,下面我记录一下遇到的一些问题,我的电脑是mac 打包教程 1.执行命令npm install electron 在安装Electron时报错command sh -c node install.js 在指令后面添加 --ignore-scripts…

火山引擎DataLeap如何解决SLA治理难题(二):申报签署流程与复盘详解

申报签署流程详解 火山引擎DataLeap SLA保障的前提是先达成SLA协议。在SLA保障平台中,以 申报单签署的形式达成SLA协议。平台核心特点是 优化了SLA达成的流程,先通过 “系统卡点计算”减少待签署任务的数量,再通过 “SLA推荐计算”自动签署部…

No module named ‘_bz2‘

【问题】 mmlab使用custom_imports加载失败,调试到里面去发现import时引发error: ModuleNotFoundError: No module named ‘_bz2’ 【解决】 参考https://stackoverflow.com/questions/50335503/no-module-named-bz2-in-python3靠后的回答。 下载文件…

Vue学习Day2——指令补充

一、指令修饰符 1、什么是指令修饰符&#xff1f; ​ 所谓指令修饰符就是通过“.”指明一些指令后缀 不同的后缀封装了不同的处理操作 —> 简化代码 2、按键修饰符 keyup.enter —>当点击enter键的时候才触发 代码演示&#xff1a; <!DOCTYPE html> <html…

大模型开发(十三):Function calling调用外部工具API,实现实时天气查询

全文共1.2w余字&#xff0c;预计阅读时间约34~50分钟 | 满满干货(附代码案例)&#xff0c;建议收藏&#xff01; 本文目标&#xff1a;完整构建一个借助Function calling功能调用外部工具API的开发流程&#xff0c;实现天气信息的实时查询 本文代码切换使用gpt3.5和gpt4接口&a…

【雕爷学编程】Arduino动手做(88)---水流量传感器模块3

37款传感器与执行器的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止这37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&am…

低代码开发为什么能长盛不衰?

低代码是一种通过可视化的界面与配置为开发者提供一个快速创建应用软件的开发环境的技术。2018年&#xff0c;西门子收购低代码企业Mendix、美国低代码独角兽企业Outsystems获得1.5亿美元的融资&#xff0c;这两个事件的发生将低代码市场带入资本方的视野&#xff0c;中国低代码…

背包问题(1)------动态规划

你有一个背包&#xff0c;地上一堆物品&#xff0c;挑选一些物品放入背包中&#xff0c;最大能够挑选出来的价值是多少 背包可以装满&#xff0c;背包也是可以不必都装满 一)01背包问题 【模板】01背包_牛客题霸_牛客网 (nowcoder.com) 1)求这个背包最多可以装多大价值的物品&a…

Java编程实现遍历两个MAC地址之间所有MAC的方法

Java编程实现遍历两个MAC地址之间所有MAC的方法 本文实例讲述了java编程实现遍历两个MAC地址之间所有MAC的方法。分享给大家供大http://家参考&#xff0c;具体如下&#xff1a; 在对发放的设备进行后台管理时,很多时候会用到设备MAC这个字段,它可以标识唯一一个设备。然而在数…

【Axure高保真原型】图片放大镜效果

今天和大家分享图片放大镜效果的原型模板&#xff0c;鼠标移入图片区域后&#xff0c;会显示放大的方框&#xff0c;方框会跟随图片移动&#xff0c;右侧会显示方框区域的大图&#xff0c;具体效果可以观看下方视频或者打开预览地址体验。 【原型效果】 【Axure高保真原型】图…

一文详解Spring Bean循环依赖

一、背景 有好几次线上发布老应用时&#xff0c;遭遇代码启动报错&#xff0c;具体错误如下&#xff1a; Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name xxxManageFacadeImpl: Bean with name xxxManageFa…

centos7搭建airflow2.6.3教程

一、环境说明&#xff1a; python3.9.6mysql5.7数据库 基础环境自行安装&#xff0c;本教程不包含基础环境部分 二、安装airflow2.6.3 1.安装Linux系统依赖模块 yum -y install zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel…

PSP - 使用 MMseqs2 工具快速搜索蛋白质序列数据库 (GMGC)

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/131934642 MMseq2 是非常强大和高效的生物信息学软件&#xff0c;可以在极短的时间内对大规模的核苷酸和蛋白质序列进行搜索和聚类。主要特点有&a…