You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

不同缩放级别下HTML换行位置计算异常问题求助

修复Monaco/VSCode高级换行功能Bug

我正在修复Monaco/VSCode的高级换行功能Bug。该功能通过渲染不可见DOM节点,计算浏览器自动换行位置,所得结果用于各类计算。

原有方案未考虑decorators(装饰器)——这类装饰器可实现文本着色、加粗、字号修改等效果。我修改了用于计算的不可见DOM节点渲染逻辑,使其包含装饰样式。

原有HTML结构

<div class="wrapper"
  ><div id="line" style="width: 171px"
    ><span
      >I need to find the character positions where this line is broken.</span
  ></div>
</div>

修改后的HTML结构

<div class="wrapper"
  ><div id="line" style="width: 171px"
    ><span
      ><span style="font-size:calc(var(--editor-font-size)*1));">I </span
      ><span style="font-size:calc(var(--editor-font-size)*2));">need</span
      ><span style="font-size:calc(var(--editor-font-size)*1));"> to </span
      ><span style="font-size:calc(var(--editor-font-size)*0.5));">find</span
      ><span style="font-size:calc(var(--editor-font-size)*1));"> the character positions where this line is broken.</span
    ></span
  ></div>
</div>

我需要计算更新后HTML的自动换行位置,结果应为换行处的字符位置数组。现有公式在常规场景下有效,仅最简示例存在差一问题,但这并非核心问题。

核心问题

该方案在特定缩放级别(浏览器或系统级缩放)下失效,例如浏览器缩放至175%时,包含单词“need”的<span>元素比line元素顶部低约0.57px。实际场景中内容动态、字号多变,该DOM节点仅用于换行计算且不可见。

设想的三种解决方案

  • 增大阈值,但阈值本身非理想方案,需考虑极小字号、多变缩放级别;
  • 实现所有span元素顶部对齐,这是首选方案;
  • 寻找替代的换行计算公式。

相关代码实现

JavaScript代码

const line = document.getElementById('line')
const lineContent = line.textContent
console.log('lineContent', lineContent)
const breaks = readLineBreaks(document.createRange(), line, lineContent)
console.log(breaks)
if (breaks) {
  let lastIndex = 0
  for (const index of breaks) {
    console.log(lineContent.slice(lastIndex, index))
    lastIndex = index
  }
}

/**
 * @param {Range} range
 * @param {HTMLDivElement} lineDomNode
 * @param {string} lineContent
 */
function readLineBreaks(range, lineDomNode, lineContent) {
  if (lineContent.length <= 1) {
    return null
  }

  /** @type {number[]} */
  const breakOffsets = []
  let lineOffset = 0
  /** @type {number | undefined} */
  let previousTop

  try {
    for (const wrapper of lineDomNode.children) {
      for (const child of wrapper.children) {
        const textNode = child.firstChild
        const length = textNode?.textContent?.length
        if (length) {
          discoverBreaks(textNode, 0, length)
        }
      }
    }
  } catch (err) {
    console.error(err)
    return null
  }

  if (breakOffsets.length === 0) {
    return null
  }

  breakOffsets.push(lineContent.length)
  return breakOffsets

  /**
   * @param {ChildNode} node
   * @param {number} low
   * @param {number} high
   */
  function discoverBreaks(node, low, high) {
    if (low === high) {
      return
    }

    range.setStart(node, low)
    range.setEnd(node, high)

    const chunkSize = high - low
    const rects = range.getClientRects()
    if (rects.length === 0) {
      lineOffset += chunkSize
    } else if (rects.length === 1) {
      const rect = rects[0]
      // console.log(rect)
      let top = rect.top
      const threshold = 0.5

      if (previousTop !== undefined && Math.abs(previousTop - top) > threshold) {
        breakOffsets.push(lineOffset)
      }

      previousTop = top
      lineOffset += chunkSize
    } else {
      const middle = low + ((chunkSize / 2) | 0)

      discoverBreaks(node, low, middle)
      discoverBreaks(node, middle, high)
    }
  }
}

CSS代码

.wrapper {
  font-weight: normal;
  font-size: 14px;
  --editor-font-size: 14px;
  position: absolute;
  background: red;
  font-family: sans-serif;
}

#line > span > span {
  vertical-align: top;
}

HTML代码

<!doctype html>
<html lang="en">
  <head>
    <meta name="color-scheme" content="light dark" />
  </head>
  <body>
    <div class="wrapper"
      ><div id="line" style="width: 171px"
        ><span
          ><span style="font-size:calc(var(--editor-font-size)*1));">I </span
          ><span style="font-size:calc(var(--editor-font-size)*2));">need</span
          ><span style="font-size:calc(var(--editor-font-size)*1));"> to </span
          ><span style="font-size:calc(var(--editor-font-size)*0.5));">find</span
          ><span style="font-size:calc(var(--editor-font-size)*1));"> the character positions where this line is broken.</span
        ></span
      ></div>
    </div>
  </body>
</html>

内容的提问来源于stack exchange,提问作者Remco Haszing

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.02 00:17:26