在开发中遇到了需要获取一段文字的长度的技术性需求,这篇文章将会讲述我是如何解决这样的问题的
浏览器环境下
解决方法一: SPAN标签
我最开始的解决方法是创建一个span
元素,然后innerHTML
设置文本在设置好样式后插入到body
中,读取宽高
function getCharSize(char, style = {}){
let {
fontSize = "12px",
fontFamily = "SimSun"
} = style
let span = document.createElement("span")
span.style.font = `${fontSize} ${fontSize}`
span.style.lineHeight = fontSize
span.innerHTML = str
document.body.appendChild(span)
let rect = span.getBoundingClientRect()
let width = rect.width
let height = rect.height
document.body.removeChild(span)
return {
width,
height
}
}
问题
-
不同浏览器的差异
不同浏览器获取的高度宽高有一些差别
chrome:
firefox:
不知道为何
firefox
的高总是比chrome
高2px,但这个我们可以通过直接获取传入的fontSize
作为高,这样就可以统一了function getCharSize(char, style = {}){ let { fontSize = 14, fontFamily = "SimSun" } = style /*其他操作*/ return { width, height: fontSize } }
-
浏览器对字体大小的限制
chrome默认最小字体为12px,基本是人尽皆知的
这里可以使用scale的方式实现
function getCharSize(char, style = {}){ let { fontSize = 14, fontFamily = "SimSun" } = style /*其他操作*/ let scale = fontSize / 20 span.style.fontSize = `${20}px` span.style.transform = `scale(${scale})` span.style.display = "inline-block" //让scale生效 /*其他操作*/ return { width, height: fontSize } }
优点
兼容几乎所有浏览器
缺点
-
会受一些潜在的全局样式影响
解决方法二:Canvas measureText函数
除了使用span来获取浏览器表现的大小这样直接的方式以外,还有可以通过使用canvasAPI的CanvasRenderingContext2D.measureText()方式来快速获取
const ctx = document.createElement('canvas').getContext('2d')
function getCharSizeByCanvas(char, style = {}){
let {
fontSize = 14,
fontFamily = "SimSun"
} = style
ctx.font = `${fontSize}px ${fontFamily}`
let text = ctx.measureText(char)
let result = {
height: fontSize,
width: text.width
}
return result
}
问题
-
chrome浏览器存在BUG,如果canvas不在DOM树上设置字体大小小于12px时,字体大小会强制设置为12px
//setfont before append const canvas1 = document.createElement('canvas') canvas1.width = 100 canvas1.height = 100 const ctx1 = canvas1.getContext('2d') ctx1.font = "8px Arial" ctx1.fillText(ctx1.font, 0, 50) document.body.appendChild(canvas1) //set font after append const canvas2 = document.createElement('canvas') canvas2.width = 100 canvas2.height = 100 const ctx2 = canvas2.getContext('2d') document.body.appendChild(canvas2) ctx2.font = "8px Arial" ctx2.fillText(ctx2.font, 0, 50)
代码整理
下面给出优化后的代码
方法一:span
let span = document.createElement("span")
span.style.positon = "ablsolute"
function getCharSize(char, style = {}){
let {
fontSize = 14,
fontFamily = "SimSun"
} = style
let scale = fontSize / 20
span.style.fontSize = "20px"
span.style.fontFamily = fontFamily
span.style.lineHeight = "0"
span.style.transform = `scale(${scale})`
span.style.display = "inline-block"
span.innerHTML = char
document.body.appendChild(span)
let rect = span.getBoundingClientRect()
let width = rect.width
document.body.removeChild(span)
return {
width,
height: fontSize
}
}
方法二:canvas计算
let canvas = document.createElement('canvas')
canvas.style.positon = "ablsolute"
let ctx = canvas.getContext('2d')
function getCharSizeByCanvas(char, style = {}){
let {
fontSize = 14,
fontFamily = "Arial"
} = style
document.body.appendChild(canvas)
ctx.font = `${fontSize}px ${fontFamily}`
document.body.removeChild(canvas)
let text = ctx.measureText(char) // TextMetrics object
ctx.fillText(char, 50, 50)
let result = {
height: fontSize,
width: text.width
}
return result
}
性能比较
数据为进行10000次单字符计算
-
都需要插入DOM的情况下(方法二兼容chrome,且字体都为12px以下)
方法二效率比方法一快: 150%左右
-
方法二字体都为12px以上
方法二效率比方法一快: 1500%左右
从效率上来讲,canvas效率是极高的,同时canvas还有还在制定的标准,可以提供更加详细的文本信息,chrome只需要去chrome://flags/
开启Experimental canvas features
就可以提前使用该功能
参考资料
CanvasRenderingContext2D.measureText()
END
2018-03-31 完成
2017-12-13 立项