Javascript 如何在不向DOM渲染任何内容的情况下计算文本高度?
我正在利用一个虚拟化列表(),其中列表项的高度是必需的,并且可能会有很大的变化。由于变化很大,我给图书馆的任何高度估计都会带来糟糕的体验 通常的高度计算方法如下:Javascript 如何在不向DOM渲染任何内容的情况下计算文本高度?,javascript,html,dom,react-virtualized,Javascript,Html,Dom,React Virtualized,我正在利用一个虚拟化列表(),其中列表项的高度是必需的,并且可能会有很大的变化。由于变化很大,我给图书馆的任何高度估计都会带来糟糕的体验 通常的高度计算方法如下: const containerStyle={ 显示:“内联块”, 位置:“绝对”, 可见性:“隐藏”, zIndex:-1, }; 导出常量measureText=(文本)=>{ const container=document.createElement(“div”); container.style=containerStyle;
const containerStyle={
显示:“内联块”,
位置:“绝对”,
可见性:“隐藏”,
zIndex:-1,
};
导出常量measureText=(文本)=>{
const container=document.createElement(“div”);
container.style=containerStyle;
container.appendChild(文本);
文件.正文.附件(容器);
const height=container.clientHeight;
const width=container.clientWidth;
container.parentNode.removeChild(容器);
返回{高度,宽度};
};
不幸的是,当您处理具有不同大小的项目的超大列表时,这是无法实现的。虽然缓存可能会被利用,但当您需要在一开始就知道总高度(所有项目的合并高度)时,即使这样也不太好
第二个经常使用的解决方案是通过HTML画布“measureText
”。性能类似于上面的DOM操作
就我而言,我知道以下几点:
- 容器宽度
- 字体
- 字号
- 所有填充物
- 所有边距
- 任何和所有其他样式,如线条高度
const measureText=(文本,选项)=>{
const{width、font、fontSize、padding、margins、borders、lineHeight}=选项;
//假设这个神奇的功能存在
//这完全取决于宽度、样式和字体信息
const numberOfLines=计算线(文本、选项);
常量内容高度=行数*行高;
const borderHeight=borders.width*2/(这都是伪代码……但不知怎么得到了像素厚度。
常量marginsHeight=margins.top+margins.bottom
常量paddingHeight=padding.top+padding.bottom
返回边距高度+填充高度+边距高度+内容高度;
}
在上面,我们缺少了calculateLines
功能,这似乎是工作中最重要的部分。如何在这方面取得进展?我需要做一些预处理来计算字符宽度吗?因为我知道我使用的字体,这应该不是一个太大的问题,对吗
是否存在浏览器问题?每个浏览器的计算可能会有什么不同
还有其他参数要考虑吗?例如,如果用户有一些为他们放大文本的系统设置(可访问性),浏览器是否通过任何可用的数据来告诉我? 我知道渲染到DOM是最简单的方法,但我愿意将精力投入到公式化解决方案中,即使这意味着每次更改边距等。我需要确保函数的输入得到更新
更新:这可能有助于找到字符宽度:。下面有详细信息:。请转到 更新2:通过使用,宽度计算变得更加简单,因为您只需要测量一个字符的宽度。令人惊讶的是,列表中有一些非常漂亮和流行的字体,如Menlo和Monaco 大更新3:这是一个相当通宵的工作,但是通过更新1中SVG方法的启发,我想出了一个非常有效的方法来计算行数。不幸的是,我发现有1%的时间它会减少1行。下面是大致的代码:const wordWidths = {} as { [word: string]: number };
const xmlsx = const xmlsn = "http://www.w3.org/2000/svg";
const svg = document.createElementNS(xmlsn, "svg");
const text = document.createElementNS(xmlsn, "text");
const spaceText = document.createElementNS(xmlsn, "text");
svg.appendChild(text);
svg.appendChild(spaceText);
document.body.appendChild(svg);
// Convert style objects like { backgroundColor: "red" } to "background-color: red;" strings for HTML
const styleString = (object: any) => {
return Object.keys(object).reduce((prev, curr) => {
return `${(prev += curr
.split(/(?=[A-Z])/)
.join("-")
.toLowerCase())}:${object[curr]};`;
}, "");
};
const getWordWidth = (character: string, style: any) => {
const cachedWidth = wordWidths[character];
if (cachedWidth) return cachedWidth;
let width;
// edge case: a naked space (charCode 32) takes up no space, so we need
// to handle it differently. Wrap it between two letters, then subtract those
// two letters from the total width.
if (character === " ") {
const textNode = document.createTextNode("t t");
spaceText.appendChild(textNode);
spaceText.setAttribute("style", styleString(style));
width = spaceText.getBoundingClientRect().width;
width -= 2 * getWordWidth("t", style);
wordWidths[" "] = width;
spaceText.removeChild(textNode);
} else {
const textNode = document.createTextNode(character);
text.appendChild(textNode);
text.setAttribute("style", styleString(style));
width = text.getBoundingClientRect().width;
wordWidths[character] = width;
text.removeChild(textNode);
}
return width;
};
const getNumberOfLines = (text: string, maxWidth: number, style: any) => {
let numberOfLines = 1;
// In my use-case, I trim all white-space and don't allow multiple spaces in a row
// It also simplifies this logic. Though, for now this logic does not handle
// new-lines
const words = text.replace(/\s+/g, " ").trim().split(" ");
const spaceWidth = getWordWidth(" ", style);
let lineWidth = 0;
const wordsLength = words.length;
for (let i = 0; i < wordsLength; i++) {
const wordWidth = getWordWidth(words[i], style);
if (lineWidth + wordWidth > maxWidth) {
/**
* If the line has no other words (lineWidth === 0),
* then this word will overflow the line indefinitely.
* Browsers will not push the text to the next line. This is intuitive.
*
* Hence, we only move to the next line if this line already has
* a word (lineWidth !== 0)
*/
if (lineWidth !== 0) {
numberOfLines += 1;
}
lineWidth = wordWidth + spaceWidth;
continue;
}
lineWidth += wordWidth + spaceWidth;
}
return numberOfLines;
};
const wordWidths={}为{[word:string]:number};
常量xmlsx=常量xmlsn=”http://www.w3.org/2000/svg";
const svg=document.createElements(xmlsn,“svg”);
const text=document.createElements(xmlsn,“text”);
const spaceText=document.createElements(xmlsn,“text”);
appendChild(文本);
appendChild(spaceText);
document.body.appendChild(svg);
//将样式对象(如{backgroundColor:“red”})转换为HTML的“backgroundColor:red;”字符串
constyleString=(对象:any)=>{
返回Object.keys(Object.reduce)(上一个,当前)=>{
返回`${(上一个+=curr
.分割(/(?=[A-Z])/)
.加入(“—”)
.toLowerCase())}:${object[curr]};`;
}, "");
};
const getWordWidth=(字符:字符串,样式:任意)=>{
const cachedWidth=字宽[字符];
if(cachedWidth)返回cachedWidth;
让宽度;
//边缘情况:裸空间(字符代码32)不占用任何空间,因此我们需要
//以不同的方式处理。用两个字母把它包起来,然后减去它们
//总宽度的两个字母。
如果(字符==“”){
const textNode=document.createTextNode(“t”);
appendChild(textNode);
setAttribute(“style”,styleString(style));
宽度=spaceText.getBoundingClientRect().width;
宽度-=2*getWordWidth(“t”,样式);
字宽[“”]=宽度;
removeChild(textNode);
}否则{
const textNode=document.createTextNode(字符);
appendChild(textNode);
setAttribute(“style”,styleString(style));
宽度=text.getBoundingClientRect().width;
字宽[字符]=宽度;
text.removeChild(textNode);
}
返回宽度;
};
const getNumberOfLines=(文本:字符串,最大宽度:数字,样式:任意)=>{
设numberOfLines=1;
//在我的用例中,我修剪所有空白,不允许一行有多个空格
//它还简化了这个逻辑。不过,目前这个逻辑还不能处理
//新线
const words=text.replace(/\s+/g,“”).trim().split(“”);
const spaceWidth=getWordWidth(“,样式);
设线宽=0;
有限公司