不同缩放级别下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
相关产品推荐
相关产品推荐

