gpt4 book ai didi

javascript - 如何在不向DOM呈现任何内容的情况下计算文本高度?

转载 作者:行者123 更新时间:2023-12-01 14:44:46 26 4
gpt4 key购买 nike

我正在利用一个虚拟列表(react-virtualized),其中我的列表项的高度是必需的,并且高度可能会很大。由于变化很大,我给库提供的任何高度估计值都不好。

通常的高度计算方法如下:

const containerStyle = {
display: "inline-block",
position: "absolute",
visibility: "hidden",
zIndex: -1,
};

export const measureText = (text) => {
const container = document.createElement("div");
container.style = containerStyle;

container.appendChild(text);

document.body.appendChild(container);

const height = container.clientHeight;
const width = container.clientWidth;

container.parentNode.removeChild(container);
return { height, width };
};

不幸的是,当您处理大小不一的物品的超大型列表时,这不起作用。尽管可能会利用缓存,但是当您一开始就需要知道总高度(所有项目的总高度)时,即使这样也无法很好地解决问题。

通常使用的第二个解决方案是通过HTML canvas的 measureText。性能类似于上述DOM操作。

就我而言,我知道以下几点:
  • 容器宽度
  • 字体
  • 字体大小
  • 所有填充
  • 所有边距
  • 任何和所有其他样式,例如线条高度

  • 我正在寻找的是一种数学解决方案,可以计算高度(或 极大紧密估计),这样我就不必依赖任何DOM操作,只要我愿意,我都可以获取高度。

    我想它像这样:

    const measureText = (text, options) => {
    const { width, font, fontSize, padding, margins, borders, lineHeight } = options;

    // Assume this magical function exists
    // This all depends on width, stying and font information
    const numberOfLines = calculateLines(text, options);

    const contentHeight = numberOfLines * lineHeight;

    const borderHeight = borders.width * 2 // (this is all pseudo-code... but somehow get the pixel thickness.

    const marginsHeight = margins.top + margins.bottom
    const paddingHeight = padding.top + padding.bottom

    return marginsHeight + paddingHeight + borderHeight + contentHeight;
    }

    在上面,我们缺少了 calculateLines函数,这似乎是首当其冲的。一个人将如何在这一方面前进?我需要做一些预处理以确定字符宽度吗?既然我知道我使用的字体,这应该不是一个太大的问题,对吗?

    是否存在浏览器问题?计算在每个浏览器上可能会有什么不同?

    还有其他参数要考虑吗?例如,如果用户具有一些系统设置,可以为其放大文本(可访问性),那么浏览器是否通过任何可用数据告诉我这一点?

    我知道向DOM渲染是最简单的方法,但是我愿意将精力投入到公式化解决方案中,即使这意味着每次我更改边距等时,我都需要确保对函数的输入进行更新。

    更新:这可能有助于查找字符宽度: Static character width map calibrated via SVG bounding box。以下是更多信息: Demo and details。积分转到 Toph

    更新2:通过使用 monospaced typefaces,宽度计算变得更加简化,因为您只需要测量一个字符的宽度。令人惊讶的是,列表上有一些非常不错且受欢迎的字体,例如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;
    };

    最初,我是逐个字符地执行此操作的,但是由于字距调整以及它们如何影响字母组,逐字逐句比较准确。同样重要的是要注意,尽管使用了样式,但必须在 maxWidth参数中考虑填充。 CSS填充对SVG文本元素没有任何影响。它很好地处理了宽度调整样式 letter-spacing(这不是完美的,我不确定为什么)。

    至于国际化,它似乎和英语一样好,除了我进入中文时。我不懂中文,但似乎遵循不同的规则来换行,但这并不说明这些规则。

    不幸的是,就像我之前说的那样,我已经注意到,这是不时地一次出现的。尽管这种情况很少见,但并不理想。我试图找出是什么原因导致了微小的差异。

    我正在使用的测试数据是随机生成的,范围是4到80行(我一次生成100条)。

    更新4:我认为我没有任何负面结果了。更改是微妙的,但很重要:您需要使用 getNumberOfLines(text, width, styles)而不是 getNumberOfLines(text, Math.floor(width), styles),并确保 Math.floor(width)也是DOM中使用的宽度。浏览器不一致并且以不同方式处理小数像素。如果我们将宽度强制为整数,则不必担心。

    最佳答案

    我找到了Measure text算法,该算法可以近似字符串的宽度而不接触DOM。

    我对其进行了一些修改以计算行数(卡住的位置)。

    You can calculate the number of lines like below:



    /**
    * @param text : <string> - The text to be rendered.
    * @param containerWidth : <number> - Width of the container where dom will be rendered.
    * @param fontSize : <number> - Font size of DOM text
    **/

    function calculateLines(text, containerWidth, fontSize = 14) {
    let lines = 1; // Initiating number of lines with 1

    // widths & avg value based on `Helvetica` font.
    const widths = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.278125,0.278125,0.35625,0.55625,0.55625,0.890625,0.6671875,0.1921875,0.334375,0.334375,0.390625,0.584375,0.278125,0.334375,0.278125,0.303125,0.55625,0.55625,0.55625,0.55625,0.55625,0.55625,0.55625,0.55625,0.55625,0.55625,0.278125,0.278125,0.5859375,0.584375,0.5859375,0.55625,1.015625,0.6671875,0.6671875,0.7234375,0.7234375,0.6671875,0.6109375,0.778125,0.7234375,0.278125,0.5,0.6671875,0.55625,0.834375,0.7234375,0.778125,0.6671875,0.778125,0.7234375,0.6671875,0.6109375,0.7234375,0.6671875,0.9453125,0.6671875,0.6671875,0.6109375,0.278125,0.35625,0.278125,0.478125,0.55625,0.334375,0.55625,0.55625,0.5,0.55625,0.55625,0.278125,0.55625,0.55625,0.2234375,0.2421875,0.5,0.2234375,0.834375,0.55625,0.55625,0.55625,0.55625,0.334375,0.5,0.278125,0.55625,0.5,0.7234375,0.5,0.5,0.5,0.35625,0.2609375,0.3546875,0.590625]
    const avg = 0.5293256578947368

    text.split('')
    .map(c => c.charCodeAt(0) < widths.length ? widths[c.charCodeAt(0)] : avg)
    .reduce((cur, acc) => {
    if((acc + cur) * fontSize > containerWidth) {
    lines ++;
    cur = acc;
    }
    return acc + cur;
    });

    return lines;
    }


    Note

    I used Helvetica as font-family, you can get the value of widths & avg from Measure text according to font-family you have.

    关于javascript - 如何在不向DOM呈现任何内容的情况下计算文本高度?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62400367/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com