前端動態字體解決方案


image

某些場景需要動態加載網絡字體,整體思路為: 當需要加載字體時,向DOM添加@font-face,動態加載字體

問題在於字體包的大小,中文包小則1MB,大則10MB,而實際使用到的字符可能僅有幾個,全量加載會造成帶寬浪費

此時可用字體子集化來解決,用戶需要加載字體時,向服務器發送字體信息和實際使用字符,服務器生成字體子集返回,以下表為參考,子集中的1個字符僅占原字體的 0.0014% ~ 0.0134%

字符集 字符數 單字符占比
GB2312 漢字6763個 圖形682個 0.0134%
GBK 漢字21003個 0.0047%
GB18030 漢字70244個 0.0014%

子集化字體工具

Fontmin

Fontmin是百度出品的一款字體子集化工具

官方網站:http://ecomfe.github.io/fontmin

命令行調用

npm i -g fontmin  // 安裝
fontmin -h  // 幫助

nodejs調用

const Fontmin = require('fontmin')

new Fontmin()
    .src('ysbth.ttf') // 源
    .dest('./build') // 目標路徑
    .use(Fontmin.glyph({
        text: 'text' // 子集化字符
    }))
    .run((err, files) => {
        if (err) throw err
    })

sfnttool

這是一個Google開源項目sfntly中的一個工具

官方倉庫:https://github.com/googlefonts/sfntly

使用方法:

  1. 安裝ant: 搜索apache ant,左側 Download -> Binary Distributions,選一個包下載解壓,bin 配置到環境變量

  2. 下載編譯: 編譯后會在dist/tools/sfnttool生成sfnttool.jar文件

git clone https:xxx
cd sfntly/java
ant
  1. 調用: 進入jar包同目錄,執行
java -jar sfnttool.jar -h  // 幫助

在java中使用命令行調用sfnttool

需要注意的是傳遞的字符串參數存在轉義字符,需稍加處理

String minStr = "子集化字符串";

/**
 * 對部分字符進行處理
 *   \   —>   \\
 *   "   —>   \"
 */ 
minStr = minStr.replace("\\", "\\\\")
               .replace("\"", "\\\"");

Process proc = Runtime.getRuntime().exec(
                 String.format("java -jar sfnttool.jar -s \"%s\" %s %s", minStr, "源字體目錄", "子集化后保存目錄"),
                 null,
                 new File("jar包所在目錄"));

// 等待操作完成,不加則為異步
proc.waitFor();

前端動態加載字體

服務器收到請求后生成子集字體包,將字體包網絡路徑返回給前端,然后通過此方法加載字體:

let fontname = "xxx.xxx"
let fontpath = "http://xxx/xxx.xxx"

let style = document.createElement('style')
style.type = "text/css"
style.innerText = '@font-face{font-family:' + fontname + ';src:url("' + fontpath + '")}'
document.getElementsByTagName('head')[0].appendChild(style)

邏輯優化

每次發送請求給服務器時,如果沒有對內容進行唯一性判斷,可能會遇到生成子集完全相同的情況,浪費帶寬和算力,所以前端請求前應該進行如下操作:

  1. 對子集化字符串進行排序去重
let minStr = "xxx"

// 切割成單個字符數組
let minStrCharArr = minStr.match(/.{1}/g)
let simpStrArr = []

// 去重
for(let i = 0; i < minStrCharArr.length; i++) {
  let char = minStrCharArr[i]
  let haveFlag = false
  for(let j = 0; j < simpStrArr.length; j++) {
    if(simpStrArr[j] == minStrCharArr[i]) {
      haveFlag = true
      break
    }
  }
  if(!haveFlag) {
    simpStrArr.push(char)
  }
}

// 排序
for(let i = 0; i < simpStrArr.length; i++) {
  for(let j = i + 1; j < simpStrArr.length; j++) {
    if(simpStrArr[j] > simpStrArr[i]) {
      let c = simpStrArr[i]
      simpStrArr[i] = simpStrArr[j]
      simpStrArr[j] = c
    }
  }
}

String.prototype.replaceAll = function(s1,s2){ 
  return this.replace(new RegExp(s1,"gm"),s2)
}

minStr = simpStrArr.toString().replaceAll(",", "")
}
let fontname = "xxx.xxx"
let minStr= "xxx"

// 前端和后端約定好,以md5(字體包名+子集化字符串)的形式命名,前端憑約定計算出要請求的路徑和字體名
let webFamilyName = md5(fontName + minStr)
let styleinner = '@font-face{font-family:' + webFamilyName + ';src:url("http://xxx/' + webFamilyName + '.xxx")}'

// 如果之前生成過同樣的子集字體包,就不請求服務器直接使用
let styles = document.getElementsByTagName('head')[0].getElementsByTagName('style')
for(let i = 0; i < styles.length; i++) {
  if(styles[i].innerText == styleinner) {
    // 直接使用
  }
}

借鑒

學習一下 "字由" 的解決方案,發現它會生成各種格式的字體以提高兼容性,可以借鑒


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM