之前有做过一个业务,需要在文本框输入文字的时候动态计算一行文字的宽度。并由此知道当前输入的这段文字会有几行。
如何计算?
每个文字的个数*宽度?理论上可行,但是有一个问题是不同类型的文字对应的宽度是不一样的,数字,汉字,字母乃至每个标点符号 宽度各不相同。所以实际处理起来,这个肯定会埋坑。
canvas
measureText
api ? 理论可行,实际问题 :字体,字间距,。。。引起的其他误差。(其实这些误差也是很容易清除的,所以这些问题也就不叫问题了,我没有采用这种方法完全是因为当时没去往这个方向想)。
不卖关子了,最后我采用的方法是这样的: 首先,我们用一个pre元素(characterElement) 将其所有css属性重置为和我们要计算的文本框的属性相一致。然后把他放置到视口之外,最后结合dom方法计算出其宽度,然后除以文本框的宽度:
function calcRows(texaWidth,charWidth ){
return Math.ceil( charWidth / texaWidth )
}
这是最初的想法,但是这里有个问题是对于换行时的误差处理,因为当当前文字不足以全部放进textarea的时候,是会换行的,而换行前和换行后之间是有空隙
的,这个空隙累积之后会对后续结果的计算带来比较大的偏差影响。所以我们还需要计算出换行后上一行多出来的空间所占的元素:
function calcOffset(){
let texaWidth = textArea.getBoundingClientRect().width;
let offset = 0;
for( let i = 1, before = i-1,L = textArea.value.length; i <= L; i++ ){
let str = textArea.value.slice( before, i);
characterElement.innerHTML = str;
let verifyWidth = characterElement.getBoundingClientRect().width;
console.log(verifyWidth,texaWidth)
if( verifyWidth >= texaWidth ){
// 记录换行下标
before = i-1;
offset += ( verifyWidth - texaWidth )
}
}
return offset;
}
有了全部的文字宽度和换行带来的误差累积我们可以很容易就计算出当前这段文字到底占了几行,即:
characterElement.innerHTML = textArea.value;
let verifyWidth = characterElement.getBoundingClientRect().width + calcOffset();
let texaWidth = textArea.getBoundingClientRect().width;
let rows = Math.ceil( verifyWidth / texaWidth )
最后一步需要考虑的是,对于calcOffset
函数,每一个文字输入都会产生一个巨量的运算时间,并且随着文字输入的增加,其计算量的增长会非常恐怖,因此, 我们需要一个策略去缓存我们已经计算过的文字:
let cache={
lastIndex:0,
offset:0
};
function calcOffset(){
let texaWidth = textArea.getBoundingClientRect().width;
let before = cache.lastIndex;
for( let i = before + 1,L = textArea.value.length; i <= L; i++ ){
let str = textArea.value.slice( before, i);
characterElement.innerHTML = str;
let verifyWidth = characterElement.getBoundingClientRect().width;
if( verifyWidth >= texaWidth ){
// 记录换行下标
cache.lastIndex = i - 1;
before = i-1;
cache.offset += ( verifyWidth - texaWidth )
}
}
return cache.offset;
}
OK了,这样基本不会有太大问题了,在文本框的input
回调里调用这些函数就可以得到精确地行数了,当然可以debounce一下得到性能更优的版本。
后记: 以上代码源于实际项目中的一个评论框,大体样子跟下图差不多,因为涉及到自动换行并且他右边的那一部分是跟文字在同一行的 所以需要去精确地计算文字的宽度 。