【每日一面】獲取文字的真實寬度
簡潔版
代碼如下:
/**
* 創建用于獲取文字寬度的 DOM,全局唯一
* @returns
*/
const createTextDom = (fontSize?: number): HTMLElement => {
let dom = document.getElementById('get-width-of-text-dom');
if (dom) {
if (fontSize) {
dom.style.setProperty('font-size', fontSize);
} else {
dom.style.removeProperty('font-size');
}
return dom;
} else {
dom = document.createElement('span');
dom.id = 'get-width-of-text-dom';
dom.style.display = 'inline-block';
dom.style.visibility = 'hidden';
dom.style.zIndex = '-200';
dom.style.position = 'fixed';
document.body.append(dom);
return dom;
}
};
/**
* 獲取文字的寬度
* @param text 文本
* @param fontSize 字體大小
* @returns
*/
export function getWidthOfText(text: string, fontSize = 16): number {
const dom = createTextDom(fontSize);
dom.textContent = text;
return dom.clientWidth;
}
話多版
為什么需要 “獲取文字真實寬度”?
聽上去好像沒什么用,也很少會遇到問這種問題的面試場景,但現在大廠的面試除了基本的八股外,也在開始搞一些自己的題庫用來判斷候選人的能力,這種實際就比較靈活化了,無法預測,但也遇到過本文的問題,主要考察兩個方面,一是有沒有遇到相關的場景,二是前端基本功。
- 使用場景
- canvas 繪圖:保證內容相對的居中
- 折行問題:Table 組件的表頭一行展示完全,出現折行會使表頭臃腫(尤其 i18n 場景下,不同語言的文字是不同寬度的)
- 基本功
- DOM 渲染原理
- 瀏覽器樣式計算
- 邊界問題考慮(不同字體、換行、空格)
JavaScript 方案
核心原理
通過隱藏的 DOM 節點來計算文字真實寬度。
避坑指南
- 出現計算結果偏差,文字寬度與實際顯示的不一致
計算節點(即我們創建的用于計算寬度的 DOM 節點),在字體相關的樣式上和目標節點不匹配;計算節點出現了換行,導致寬度被壓縮;包含特殊字符(如 emoji)。
- 性能問題
頻繁創建、刪除 dom 節點會觸發瀏覽器頻繁的重排和重繪,對于大批量數據展示過程中,頁面就會很卡。一般選擇復用計算節點+防抖/節流等。
Canvas 特化方案
繪圖場景,我們通常很少會直接操作 DOM,盡可能的在 canvas 內部進行處理。
核心原理
canvas 提供了 measureText() 方法直接計算文字的寬度。
代碼示例:
// 1. 初始化 canvas,后續我們可以拿到這個 canvas 實例
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
const drawText = 'Canvas繪圖文字';
// 2. 繪制前,會設置相關的文字屬性
ctx.font = 'bold 24px Arial';
ctx.fillStyle = '#333';
// 3. 開始繪制前獲取到文字寬度用于做一些其他的事情
const textWitdh = ctx.measureText(drawText).width.toFixed(2);
// 4. 假設我們要將文字居中在畫布上
const x = (canvas.width - textWidth) / 2;
const y = 50; // 假設居中高度為 50
ctx.fillText(drawText, x, y);
高度的計算還是用 measureText 方法,只是相對比寬度復雜一些
避坑指南
- canvas 特化方案不支持富文本的寬度,這種情況需要我們手動進行推算(寫計算公式應用)。
面試追問
- 問:文字的寬度由什么決定的?瀏覽器渲染文字的過程。
文字寬度 = 每個字符的寬度之和 + 字符間的間距(letter-spacing),受字體(font-family)、字號(font-size)、字體樣式(font-weight/italic) 直接影響;
瀏覽器渲染文字時,會先根據 font 相關樣式從字體文件中讀取字符的“advance width”(字符前進寬度,即字符本身寬度 + 默認間距),再累加得到整個文本的寬度,此過程屬于“樣式計算(Recalculate Style)” 階段。
一般會由這個文字渲染過度到瀏覽器渲染的過程等等,這道題本身就是一個簡單的出發點,不難。
- 問:能否直接用
div的offsetWidth直接獲取?
看實現方式,如果 div沒有邊距(padding/margin),那么就可以直接獲取到文字寬度,如果我們創建的 dom 有默認的邊距,記得減掉這部分。此外,還有換行的問題,我們需要配置 white-space: nowrap避免文字換行導致的寬度變小的問題。
- 問:你實現的這個版本,可以在任何地方用嗎?不行的話,怎么修改?
不行,這個版本并未包含字體相關的屬性設置,對于頁面多數位置都可以直接使用,但是一些顯式的指定了字體屬性的,不能直接使用,需要修改。
使用 getComputedStyle來獲取目標位置的文字實際樣式,同步到用于模擬計算的 DOM 節點,從而保證適配效果。
- 問:跳出這個問題,我們在使用
getComputedStyle做樣式同步之后,會觸發重排還是重繪?
需要根據同步的樣式包含什么樣的改動來判斷是觸發了重排還是重繪。簡單舉兩個例子即可。
- 問:還有一個
getBoundingClientRect方法,你這里怎么不選擇這個方法?和offsetWidth有什么差別?
前者獲取到的時元素內容區域的寬度(不含 padding、border),后者返回的是包含 padding、border 的寬度。不選擇這個方法是因為沒有必要,我只需要獲取寬度即可。
- 問:如何計算高度?數據量大的時候如何計算文字寬度?
這個就過于深度的追問了,是另一道比較完全的面試題了。
本文首發于,公眾號訂閱請關注:

