javascript - 等宽字符的精确宽度
问题描述
我正在使用带有语法突出显示的代码编辑器,我需要知道等宽字符的精确宽度。我使用这个值来计算一个字符在一行上的位置,我需要知道它以便放置各种 GUI 元素,例如文本光标(可以有多个)、选择矩形、警告工具提示等。到目前为止我一直在使用以下功能:
function getCharacterWidth(char, fontFamily, fontSize) {
let span = document.createElement("span");
span.style.fontFamily = fontFamily;
span.style.fontSize = fontSize;
span.style.position = "absolute";
span.style.visibility = "hidden";
span.style.width = "auto";
span.style.whiteSpace = "nowrap";
span.style.padding = "0";
span.style.margin = "0";
span.style.letterSpacing = "0px";
span.style.wordSpacing = "0px";
span.innerText = char;
document.body.appendChild(span);
let width = span.getBoundingClientRect().width;
span.remove();
return width;
}
它一直运行良好,但后来我注意到 Google Chrome 出现问题。当我的文本编辑器渲染包含数千个字符的大行时,由于舍入问题,字符位置没有被正确计算。似乎在谷歌浏览器上,width
返回getBoundingClientRect()
的精度最多为小数点后 5 位,这对我的用例来说并不理想。在 Firefox 上,精度似乎要高得多,达到小数点后 15 位,这就是为什么我在那里从来没有遇到过这个问题。
经过一番挖掘,我听说了根据包含数千个字符的跨度宽度来计算字符宽度的想法(https://stackoverflow.com/a/56379770/2197150)。所以,在我原来的函数中,我替换span.innerText = char
为span.innerText = char.repeat(10000)
并返回了width / 10000
. 它有所帮助,但是当我处理大行时,计算仍然明显不正确。
所以我在这里。如何在其他浏览器中像 Firefox 一样以高精度计算字符的宽度?
解决方案
我想出了一个对我的用例运行良好的解决方案。这个想法是根据我的编辑器的最大行来计算字符的宽度。我的线元素看起来像这样:
<div class="line"><span class="keyword">let</span><span class="space"> </span><span class="identifier">foo</span></div>
如果我们想根据该行计算字符的宽度,我们可以这样做:
let lineElement = document.querySelector(".line");
let lineWidth = lineElement.getBoundingClientRect().width;
let charWidth = lineWidth / lineElement.textContent.length;
我跟踪我在编辑器中渲染的最大行。每当我渲染一条新的最大线时,我都会charWidth
根据该新线进行更新。
这使我可以通过简单的乘法计算一行内任何给定列的字符位置column * charWidth
。到目前为止,它一直运行良好,即使对于超过 100000 个字符的行也是如此。
然而,我必须做的最后一件事是处理更大的行。Google Chrome 上似乎有一个错误,当您尝试使用巨大的文本节点渲染一行时,例如<span>many many characters...</span>
,它不会为您提供正确的元素宽度(请参阅Chrome 上大元素的不准确宽度)。为了克服这个问题,每当我必须渲染一个巨大的文本时,我都会将它分成多个跨度,例如<span>many many </span><span>characters...</span>
.
现在我可以毫无问题地渲染 500000 个字符长的行,字体大小为 48。除此之外,事情又开始变得奇怪了,计算错误和其他奇怪的浏览器行为。所以我决定设置一个在一行中呈现 500000 个字符的硬性限制。第 500000 个字符以外的所有内容都对用户隐藏。
推荐阅读
- f# - 可空匿名记录
- reactjs - 未经授权的页面重定向电子中的相关性
- sql - 未找到列:1054 'on 子句'中的未知列 '2' - InnerJoin 查询无法正常工作
- react-native - 方形图像的 React Native 使用百分比
- objective-c - 在 macOS Objective-C 应用程序中,我需要以编程方式重新启动应用程序。这可行,但重新启动的应用程序有 1Gb 的内存泄漏。有什么线索吗?
- sql - 在 SSRS 报告 .rdl 中四舍五入到最接近的 0.5
- c# - 订阅作为参数传递的动作
- java - 当他想在android studio中注销用户时如何注销
- kotlin - 这个扩展属性如何将字符串中每个单词的首字母大写?
- java - 如何通过 JMX 连接到 AMQ7,以编程方式使用 MBean 获取代理指标?