微信小程序字体加载并缓存

4,435 阅读1分钟

背景

通过微信小程序 wx.loadFontFace 加载第三方字体:

wx.loadFontFace({
  family: 'Bitstream Vera Serif Bold',
  source: 'url("https://sungd.github.io/Pacifico.ttf")',
  success: console.log
})

但是这样每次进入小程序都会请求字体,浪费资源(特别字体文件还不小),所以打算直接存,下次直接读本地字体文件

解决方式

graph LR
A[Start] --> B{本地有字体};
B -- Yes --> C[显示字体];
B -- No --> D[请求网络字体] --> E[字体存储];
D ---> C;
C ----> F[End]

这里最大的问题就是,如何加载本地字体

官方文档并没有说能支持本地字体文件,于是实测吧:

  • 模拟器可以正常加载显示 ✅
  • 真机(iOS 测的)不行 ❌

那既然直接字体文件不行,类比 CSS,base64 呢?CSS 不仅支持在线 url,也能使用 base64,咱直接转读文件,转成 base64 给 wx.loadFontFace 试一下:

  • 模拟器可以 ✅
  • 真机也可以 ✅

于是基于下载字体、缓存文件、文件转 base64 字体,简单撸了个 behavior:

仅参考,文件我存在微信云服务里,普通 cdn 文件的需要修改下载 api

export default Behavior({
  methods: {
    _downloadFont (fileID, filePath, fontFamily) {
      wx.cloud.downloadFile({
        fileID, // cloud 文件 id
        success: res => {
          wx.getFileSystemManager().saveFile({ // 下载成功后保存到本地
            tempFilePath: res.tempFilePath,
            filePath,
            success: res => {
              // 加载字体
              this._loadFontFace(fontFamily, res.savedFilePath)
            }
          })
        }
      })
    },
    // 加载文件字体转 base64,load
    _loadFontFace (fontFamily, filePath) {
      wx.getFileSystemManager().readFile({ //读文件
        filePath, // 本地文件地址
        encoding: 'base64',
        success: res => {
          wx.loadFontFace({
            global: true, // 是否全局生效
            family: fontFamily, // 字体名称
            source: `url("data:font/woff2;charset=utf-8;base64,${res.data}")`,
          })
        }
      })
      // wx.loadFontFace({
      //   family: fontFamily,
      //   source: `url("${filePath}")`, // 不支持本地路径
      // })
    },
    // fileID: 云文件 id
    // filename: 存储文件路径
    // fontFamily: css 中字体的 family
    loadCloudFontFace (fileID, filename, fontFamily) {
      const filePath = `${wx.env.USER_DATA_PATH}/${filename}`

      wx.getFileSystemManager().access({
        path: filePath,
        success: () => {
          this._loadFontFace(fontFamily, filePath)
        },
        fail: () => {
          this._downloadFont(fileID, filePath, fontFamily)
        }
      })
    }
  },
})

使用:

Page({
  behaviors: [loadFontBehavior],
  onLoad() {
    this.loadCloudFontFace('cloud://space-xxxxx/font/Font.otf', 'Font.otf', 'FontName')
  }
})
.text {
    font-family: "FontName";
}

其他

代码没有考虑一些边界 case,仅供参考思路