最近有一个需求,需求方一定要求加载自有的字体,一共5个包,加起来要60M,而且因为有后台上传的文章,还不能用字体精简工具压缩字体。
在调用官方的wx.loadfontface的时候,开始看着一切正常,但真机调试的时候,发现每个页面都在重新下载字体。
先不说每个页面抖动的问题,就是流量也扛不住他这么造啊。
社区里面看到有很多人在反馈这个bug,从20年到23年都有
本来给客户反馈说微信bug,他们也勉强接受,但是上线10天,跑了1000多rmb的流量钱,实在是顶不住,压力又给到开发这边。。。
后面在掘金看到一篇文章(https://juejin.cn/post/7103928278225780743?searchId=2023081018280858E8AD21B4FDD711555A)
本地路径不行,那把本地文件转成base64曲线救国确实是一条思路。
原文是behavior做的,而且是用云开发,但我们的业务场景是需要全局生效,每个页面都写一个behavior也太累了。
于是改造成以下模式。
先创建一个loadFont.js,代码如下
注释写的比较详细了,简述思路:
先用wx.getFileSystemManager().access()看本地有没有,有就直接load,没有就先下载
wx.downloadFile把字体下到本地,再从本地wx.getFileSystemManager().readFile()取出文件并转Base64
最后source: `url("data:font/woff2;charset=utf-8;base64,${res.data}")`,搞定!
// 建议循环调用方法,而不是这个方法内循环下载
// 下载字体文件,注意要把字体域名加到后台downloadFile白名单中
function _downloadFont(fontUrl, filePath, fontFamily) {
wx.downloadFile({
url: fontUrl,
success: res => {
wx.getFileSystemManager().saveFile({ // 下载成功后保存到本地
tempFilePath: res.tempFilePath,
filePath,
success: res => {
// 加载字体
_loadFontFace(fontFamily, res.savedFilePath)
}
})
}
})
}
// 加载文件字体转 base64,load
function _loadFontFace(fontFamily, filePath) {
// 读文件
wx.getFileSystemManager().readFile({
filePath, // 本地文件地址
encoding: 'base64',
success: res => {
wx.loadFontFace({
global: true, // 是否全局生效
scopes: ['webview', 'native'], //native可能有点问题,超哥生个海报试一下
family: fontFamily, // 字体名称
source: `url("data:font/woff2;charset=utf-8;base64,${res.data}")`,
success(res) {
console.log(fontFamily + '加载成功:' + res.status)
},
fail: function (res) {
console.log(fontFamily + '加载失败' + res)
},
})
}
})
}
// fontUrl: 字体地址
// filename: 存储文件路径
// fontFamily: css 中字体的 family
function loadCloudFontFace(fontUrl, filename, fontFamily) {
const filePath = `${wx.env.USER_DATA_PATH}/${filename}`
wx.getFileSystemManager().access({
path: filePath,
success: () => {
_loadFontFace(fontFamily, filePath)
console.log('从本地加载了字体');
},
fail: () => {
_downloadFont(fontUrl, filePath, fontFamily)
console.log('从外部加载了字体', fontUrl);
}
})
}
module.exports = {
loadCloudFontFace
}
由于业务本身的需求,是全局所有字体都用,所以,直接在app.js中(实际上我们是抽了一个文件单独维护的,这里就简化吧)
const loadFont = require('./loadFont.js');
loadFont.loadCloudFontFace('https://xxx.com/wxapp/fonts/STHeiti.woff2', 'STHeiti.woff2', 'STHeiti')
并且,在app.wxss,让font-family全局给一个默认字体(事实上还有英文字体,这里简化了)
page {
font-family: 'STHeiti';
}
当然也支持每个页面独立配置不同字体,方法大同小异。
真机测试,终于只加载一次网络字体了。
其他注意的事项:
- 由于第一次仍然要加载字体,会产生抖动,我们的做法是,尽量不在首页用文字,大量用图片替代,同时在后台加载字体,供其他页面使用。
- 对于个别页面需要加载字体时,可以出个loading框(虽然体验依然不好,但我尽力了)
- 直接load网络字体,会存在跨域的问题,有的时候,这个问题确实很难解决(字体一般都用cdn、对象存储一类的,如果有权限操作配置很简单,但很多情况下开发人员确实不好增加跨域配置)。采用这个方案,由于是本地,就不存在跨域问题了,一举多得。
最后还是要吐槽下,这个需求,并不小众。最早从20年就有bug反馈,官方也有回复,但到23年了这个bug还存在,也着实无奈。
试验了之后发现,在目前的基础库(3.5.0),wx.loadFontFace 的 scopes 如果含有 native,这种加载方式就会报错,而且模拟器和真机都无法生效。而新版的 Canvas 2D 是需要 scopes 含有 native 才能显示加载的字体。
但是旧版使用 wx.createCanvasContext 创建的 Canvas 可以支持显示 scopes 是 webview 加载的字体。
所有如果要用这种方式载入字体又要能在 canvas 中生效,只能用旧版的 wx.createCanvasContext。
使用该方案已解决目前遇到的问题
用了楼上的办法 下载了字体 ttf woff 都有,加载的时候都报错 加载失败 ???
现在我看loadFontFace是已经有缓存的了,不用这么麻烦
感谢,用了您的方法只加载一次了,但每次进页面读取文件还是会有轻微的字体抖动