关于获取文字渲染实际宽度的问题
·
Yin灏
背景
业务需要求上要获取文字的宽度,根据这个来测算表单 label 和 input 之间的间距,然后进行 label 左对齐。
解决方案
怎么获取文字的像素宽度呢,有几种方案。
- 直接测算一个文字大约宽度是多少,方法比较简单,能满足表单类 90%的场景,比如
export function strCodeLen(str: string) {
let count = 0;
if (str) {
const len = str.length;
for (let i = 0; i < len; i++) {
// 字符编码大于255,说明是双字节字符(即是中文)
if (str.charCodeAt(i) > 255) {
count += 2;
} else {
count++;
}
}
return count;
} else {
return 0;
}
}
// 这个是看单个字符的宽度
console.log(strCodeLen("用户名") * 9);
- 用真实的 dom 做渲染,然后获取 dom 的宽度,计算得到最终的准确的字符像素长度。
function getStrWidth(arr) {
const labelSpan = document.createElement("span");
const container = document.body;
let maxWidth = 0;
if (arr.length > 0) {
labelSpan.style.width = "auto";
labelSpan.style.position = "absolute";
labelSpan.style.whiteSpace = "nowrap";
container.appendChild(labelSpan);
arr.forEach((str) => {
labelSpan.innerHTML = str;
maxWidth = Math.max(labelSpan.offsetWidth, maxWidth);
});
}
container.removeChild(labelSpan);
return maxWidth;
}
- 使用 canvas 来绘制每个文字大小
function getLabelWidthCanvas(arr, defaultFont) {
// 创建一个全局canvas元素
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d') as unknown as CanvasRenderingContext2D;
function getPageFont() {
// 获取<html>元素的样式
const htmlStyles = window.getComputedStyle(document.documentElement);
// 获取font-family属性值
const fontFamily = htmlStyles.getPropertyValue('font-family');
return fontFamily;
}
const font = defaultFont || `12px $ {
getPageFont()
}`;
let maxWidth = 0;
if (arr.length > 0) {
arr.forEach(str = >{
const width = getStringPixelWidth(str, font);
maxWidth = Math.max(width, maxWidth);
});
}
return maxWidth;
function getStringPixelWidth(text: string, font: string) {
// 设置字体样式
context.font = font;
// 使用measureText方法获取文本宽度
const metrics = context.measureText(text);
// 返回文本宽度,即像素长度
return metrics.width;
}
}
方案对比
第一种方案:没有考虑一些特殊字符,字体大小和字体类型,简单粗暴,没有考虑兼容性,最后会遇上各种问题。
第二种方案:没有第一种方案的问题,但是数据量大,页面会不停的回流,毕竟 dom 元素内容在变化,有性能问题。
第三种方案:会有第一种方案的字体大小问题,需要手动设置字体大小,但是不存在 dom 渲染性能问题,直接存储在内存中。
canvas 和 span 时间对比
// Canvas method
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d");
function getStringPixelWidthCanvas(text, font) {
context.font = font;
const metrics = context.measureText(text);
return metrics.width;
}
// Span method
function getStringPixelWidthSpan(text, font) {
const span = document.createElement("span");
document.body.appendChild(span);
span.style.position = "absolute";
span.style.whiteSpace = "nowrap";
span.style.font = font;
span.textContent = text;
const width = span.offsetWidth;
document.body.removeChild(span);
return width;
}
// Test data
const textArray = [
"你好,世界!",
"Hello, World!",
"こんにちは、世界!",
"안녕하세요, 세상!",
];
const font = "16px Arial";
// Performance test
function testPerformance(methodName) {
const startTime = performance.now();
for (let i = 0; i < 1000; i++) {
textArray.forEach((text) => {
if (methodName === "canvas") {
getStringPixelWidthCanvas(text, font);
} else if (methodName === "span") {
getStringPixelWidthSpan(text, font);
}
});
}
// document.body.removeChild(span);
const endTime = performance.now();
const duration = endTime - startTime;
console.log(`${methodName} method took ${duration.toFixed(2)} milliseconds.`);
}
// Run performance tests
testPerformance("canvas");
testPerformance("span");
数据结果
// 10 个时间对比
canvas method took 0.20 milliseconds.
span method took 8.00 milliseconds.
// 1000 个时间对比
canvas method took 10.50 milliseconds.
span method took 375.50 milliseconds.
结论
canvas 的效果目前看是最好的,推荐使用 canvas 来解决这个问题