vue.js

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript类库 > vue.js > Vue3全局动态切换字体

Vue3+Pinia实现全局动态切换字体方案的完整落地方案

作者:@赵士杰

本文手把手教你用Vue3+Pinia实现高性能全局字体切换方案,从字体加载原理、CSS变量驱动到字体子集化优化,你将学会一套低侵入、高性能的字体管理方案,彻底解决字体切换性能与体验问题

本文将从字体加载底层原理出发,结合 Vue3 + Pinia 技术栈,拆解了 Vue3 + Pinia 全局字体切换方案。核心是通过「CSS 变量统一入口 + Pinia 响应式驱动」实现低侵入、高性能的字体切换,同时通过字体子集化、预加载等手段解决体积和加载问题。

一、核心思路

本文涉及的全局字体切换的主要逻辑是「 CSS 变量+ 响应式状态驱动」,核心方案可概括为:

CSS 全局变量(载体) + Pinia 状态管理(驱动) + localStorage 持久化(记忆) + @font-face(自定义字体)

具体执行链路:

二、前置知识:Web 字体加载与渲染机制

在动手实现前,先理解浏览器处理字体的核心机制,避免踩坑。

2.1 font-family 继承与回退规则

font-family 遵循「就近覆盖、层级继承」原则:

2.2 @font-face 自定义字体基础

@font-face 是自定义字体的核心,用于注册字体别名与资源映射:

@font-face {
  font-family: 'xxx';            /* 字体别名(全局唯一) */
  font-weight: 400;              /* 字重 */
  font-style: normal;            /* 字体样式 */
  font-display: swap;            /* 解决FOIT闪烁问题 */
  src: url('./xxx.woff2') format('woff2'); /* 资源地址 */
}

关键属性解析:

2.3 浏览器字体加载特性

在浏览器环境中,字体资源的请求优先级低于 JavaScript、核心 CSS 资源。浏览器会优先完成页面骨架渲染,再后台异步拉取字体文件,保障页面首屏快速呈现。浏览器解析CSS代码时,会同步扫描所有 @font-face 规则并完成字体别名预注册,但不会立即下载字体文件,仅当页面DOM元素实际引用该字体时,才会触发网络资源请求。

同时字体资源的请求优先级低于JavaScript、核心 CSS 等关键资源,浏览器会优先完成页面骨架渲染,再在后台异步拉取字体文件,以此保障首屏页面能够快速呈现。因此浏览器异步加载字体资源时,不会阻塞 HTML 结构解析、CSS样式解析,仅会影响最终的文本渲染流程。也正是基于这套异步加载机制,字体加载过程会衍生出两种主流的文本渲染现象:

三、字体资源预处理:轻量化与合规化

3.1 字体格式选型

前端可用的自定义字体主要包含 TTF、WOFF、WOFF2 三种格式,不同格式在压缩率、浏览器兼容性、加载性能上差异显著,是字体资源预处理的首要选型依据。

工程规范:线上环境优先部署 WOFF2 为主资源,WOFF 作为低版本浏览器降级兜底。

3.2 免费商用字体资源获取

下表整理了常用的免费可商用字体资源网站,包含中/英文字体专用平台,网站名称可点击直达官网,可按需下载对应字体的 TTF/WOFF2 文件:

网站名称商用授权优点
Google FontsSIL OFL 1.1,完全免费商用全开源无版权风险,Web 端集成极简,全球 CDN
猫啃网 Maoken人工核验每款字体授权,全部 OFL 开源无广告、不用登录、版权审核严格,无侵权隐患
阿里巴巴普惠体官网阿里开源协议,永久免费商用字形现代适配 UI,国内 CDN 加载极快,网页首选
OPPO Sans(OPPO开放平台)官方全量免费商用,禁止二次改编/转售圆润科技感UI字体,原生适配移动端,自带官方CDN,中英文阅读性极佳
JetBrains Mono(官方)Apache 2.0 开源协议,全场景免费商用代码等宽字体,适合技术类网站

项目使用自定义 Web 字体时,版权为第一优先级,禁止随意下载商用付费字体,避免侵权索赔。

3.3 字体体积优化:子集化(核心)

即便使用 WOFF2,全量中文字体体积往往达到数 MB,会严重拖慢首屏加载。可以通过「子集化」剔除未使用字符,进行体积瘦身。

子集化策略

注意:子集化后的字体无法渲染未包含字符,生产环境需提前统计业务字符范围,避免出现文字空白乱码。

本地子集化工具(Python)

以下为完整可直接运行的 Python 脚本,依托 fonttools 官方工具链实现本地字体子集化,无损压缩生成标准 WOFF2 文件,相比在线工具更安全(无字体上传泄露风险)、可定制化更强:

需安装字体解析、Brotli 压缩核心依赖,终端执行以下命令:

# 安装核心依赖:fonttools(字体处理)、brotli(woff2压缩)、lxml(解析依赖)
pip install fonttools brotli lxml
# 国内镜像加速(安装失败时使用)
pip install fonttools brotli lxml -i https://pypi.tuna.tsinghua.edu.cn/simple

修改并执行以下脚本:

import os
import subprocess
def ttf_to_mini_woff2(ttf_path):
    """将TTF字体子集化并转换为WOFF2格式"""
    dir_path = os.path.dirname(ttf_path)
    filename = os.path.basename(ttf_path).replace('.ttf', '.woff2')
    output_path = os.path.join(dir_path, filename)
    # 仅保留常用字符:ASCII + 中文常用字 + 标点
    cmd = [
        "pyftsubset",
        ttf_path,
        "--unicodes=U+0020-007E,U+3000-303F,U+4E00-9FFF,U+FF00-FFEF",
        "--flavor=woff2",  # 输出WOFF2格式
        "--drop-tables=GPOS,GSUB,FFTM",  # 剔除无用表
        f"--output-file={output_path}"
    ]
    subprocess.run(cmd, check=True)
    print(f"✅ 生成轻量化字体:{output_path}")
if __name__ == "__main__":
    # 替换为你的TTF字体路径
    font_path = '/Users/xxx/Downloads/AlibabaPuHuiTi.ttf'
    ttf_to_mini_woff2(font_path)

关键参数说明:

字符集范围(--unicodes):默认保留ASCII字符、中文常用字、全角标点;可按需精简,如后台系统可剔除生僻汉字区间

剔除字体表(--drop-tables):删除排版、矢量渲染等前端无用数据表,进一步压缩体积约10%-15%

--no-hinting:关闭字体像素微调,Web端无视觉影响,显著减小文件体积

四、项目实战:从配置到落地

4.1 项目目录结构

依次创建如下目录结构:

项目根目录/
├── public/
│   └── fonts/                  # 字体静态资源目录
│       ├── AlibabaPuHuiTi-Regular.woff2
│       └── JetBrainsMono-Regular.woff2
├── src/
│   ├── assets/
│   │   ├── styles/
│   │   │   ├── global.scss     # 全局样式入口(统一引入字体、主题样式)
│   │   │   └── theme.scss      # 全局CSS字体变量、回退链定义
│   │   └── css/
│   │       └── font-face.css   # 全局@font-face字体注册声明
│   ├── stores/
│   │   └── fontStore.ts        # Pinia字体状态、切换逻辑、持久化
│   ├── components/
│   │   └── FontSwitch.vue      # 字体设置UI交互组件
│   ├── types/
│   │   └── font.d.ts           # 字体枚举、TS类型定义
│   └── main.ts                 # 项目入口、全局样式注册
└── package.json

4.2 字体文件规范存放

4.3 注册自定义字体

在字体样式文件src/assets/css/font-face.css 中通过 @font-face 声明自定义字体,定义自定义字体名称与字体文件的映射关系,url 基于 public 绝对路径引用:

/* 阿里巴巴普惠体 */
@font-face {
  font-family: 'AlibabaPuHuiTi';
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  src: url('./AlibabaPuHuiTi.woff2') format('woff2');
}
/* JetBrains Mono 代码字体 */
@font-face {
  font-family: 'JetBrainsMono';
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  src: url('./JetBrainsMono.woff2') format('woff2');
}

4.4 定义全局 CSS 变量

src/assets/styles/theme.scss 中配置根节点字体变量:

// 根节点字体变量,所有页面继承
:root {
  --font-family: ''; // 动态切换的字体变量
}
// 全局字体回退链:确保字体加载失败时的兜底
html {
  font-family: var(--font-family), system-ui, -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif;
  font-size: 16px;
}

global.scss 中统一导入样式:

// 引入字体注册文件
@import './css/font-face.css';
// 引入主题CSS变量文件
@import './theme.scss';

4.5 Pinia 状态管理

src/types/font.d.ts 定义字体类型:

/** 字体类型枚举 */
export const FONT_TYPE = {
  Default: 'default', // 默认字体
  AlibabaPuHuiTi: 'alibabaPuHuiTi',
  JetBrainsMono: 'jetbrainsMono',
  OPPOSans: 'oppoSans'
} as const;

/** 字体类型别名 */
export type FontType = typeof FONT_TYPE[keyof typeof FONT_TYPE];

/** 字体显示标签 */
export const FONT_LABEL: Record<FontType, string> = {
  [FONT_TYPE.Default]: '默认字体',
  [FONT_TYPE.AlibabaPuHuiTi]: '阿里巴巴普惠体',
  [FONT_TYPE.JetBrainsMono]: 'JetBrains 代码字体',
  [FONT_TYPE.OPPOSans]: 'OPPO Sans'
};

src/stores/fontStore.ts 中实现字体状态逻辑:

import { defineStore } from 'pinia';
import { ref, watch } from 'vue';
import { FONT_TYPE, FontType } from '@/types/font';

// 字体类型与@font-face别名的映射
const fontMap: Record<FontType, string> = {
  [FONT_TYPE.Default]: '',
  [FONT_TYPE.AlibabaPuHuiTi]: 'AlibabaPuHuiTi',
  [FONT_TYPE.JetBrainsMono]: 'JetBrainsMono',
  [FONT_TYPE.OPPOSans]: 'OPPOSans'
};

// 持久化工具:从localStorage读取
const getStoredFont = (): FontType => {
  const stored = localStorage.getItem('fontType');
  return (stored as FontType) || FONT_TYPE.Default;
};

export const useFontStore = defineStore('font', () => {
  // 响应式状态:当前选中字体
  const fontType = ref<FontType>(getStoredFont());

  // 应用字体到CSS变量
  const applyFont = () => {
    const root = document.documentElement;
    const fontFamily = fontMap[fontType.value];
    if (fontFamily) {
      root.style.setProperty('--font-family', fontFamily);
    } else {
      root.style.removeProperty('--font-family'); // 恢复默认
    }
  };

  // 设置字体类型(外部调用)
  const setFontType = (type: FontType) => {
    fontType.value = type;
    localStorage.setItem('fontType', type); // 持久化
  };

  // 监听字体变化,即时应用
  watch(fontType, applyFont, { immediate: true });

  return { fontType, setFontType };
});

4.6 UI 交互组件

创建 src/components/FontSwitch.vue实现字体切换下拉框组件:

<template>
  <div class="font-switch-container">
    <h3 class="font-switch-title">字体设置</h3>
    <el-select 
      v-model="fontStore.fontType" 
      @change="handleFontChange"
      placeholder="请选择字体"
      size="small"
    >
      <el-option
        v-for="[value, label] in Object.entries(FONT_LABEL)"
        :key="value"
        :label="label"
        :value="value"
      />
    </el-select>
  </div>
</template>
<script setup lang="ts">
import { useFontStore } from '@/stores/fontStore';
import { FONT_LABEL, FontType } from '@/types/font';
const fontStore = useFontStore();
// 字体切换回调(可选)
const handleFontChange = (type: FontType) => {
  fontStore.setFontType(type);
  // 可添加埋点、提示等逻辑
};
</script>
<style scoped>
.font-switch-container {
  padding: 16px;
  border-radius: 8px;
  background: #f5f7fa;
}
.font-switch-title {
  margin: 0 0 8px 0;
  font-size: 14px;
  color: #303133;
}
</style>

4.7 应用注册全局样式

src/main.ts 中注册 Pinia 并导入全局样式:

import { createApp } from 'vue';
import { createPinia } from 'pinia';
import App from './App.vue';
import './assets/styles/global.scss'; // 全局样式

const app = createApp(App);
// 注册Pinia
app.use(createPinia());
app.mount('#app');

4.8 新增字体

到此这篇关于Vue3+Pinia实现全局动态切换字体方案的完整落地方案的文章就介绍到这了,更多相关Vue3全局动态切换字体内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文