rto*_*tom 9 html javascript css
我正在处理一个contentEditable span
想要将position: absolute
元素放置在与光标同一行的位置。当文本因不适合而被换行时,就会出现问题 - 换行行的第一个和最后一个位置有奇怪的行为。
对于它们两者,当我位于第二行中的第一个位置时,偏移y
量getBoundingClientRect()
等于第一行的偏移量,但是如果我在第二行上进一步移动一个位置,则y offset
正确匹配第二行。
在下面的代码片段中,显示了 Firefox 的此行为。对于 Chrome 来说,它似乎工作得很好,尽管在我的完整实现中它也有不精确的行为,但我能够为 chrome 解决它。然而,对于 Firefox,第一行的最后一个位置等于offset
第一行,第二行的第一个位置等于offset
等于第一行,之后就可以正常工作了。
在示例中,转到第一行的最后一个位置,并注意CURRENT_TOP
控制台中的值显示16
。如果你向右移动一个地方,光标已经在下一行,它仍然显示16
。如果你再向右移动一个,它会说36
const textEl = document.getElementById("myText")
textEl.addEventListener("keyup", (event) => {
const domSelection = window.getSelection();
if (domSelection && domSelection.isCollapsed && domSelection.anchorNode) {
let offsetNewLine = 0;
const domRange = domSelection.getRangeAt(0);
const rect = domRange.getBoundingClientRect();
const rects = domRange.getClientRects();
const newRange = document.createRange();
const newRangeNextOffset = domSelection.anchorNode.textContent.length < domSelection.anchorOffset + 1 ? domSelection.anchorOffset : domSelection.anchorOffset + 1
newRange.setStart(domSelection.anchorNode, newRangeNextOffset);
newRange.setEnd(domSelection.anchorNode, newRangeNextOffset);
const nextCharacterRect = newRange.getBoundingClientRect();
console.log(`CURRENT_TOP: ${rect.y}, NEXT_CHAR_TOP: ${nextCharacterRect.y}`);
}
})
Run Code Online (Sandbox Code Playgroud)
.text-container {
width: 500px;
display: inline-block;
border: 1px solid black;
line-height: 20px;
padding: 5px;
}
Run Code Online (Sandbox Code Playgroud)
<span id="myText" class="text-container" contentEditable="true">Go on the last position in the first row and come it to first position in the second row</span>
Run Code Online (Sandbox Code Playgroud)
先诊断,后治疗。
\n之所以会出现这种奇怪的行为,是因为 Chrome 和 Firefox 对待换行符的方式似乎有所不同。在 Chrome 和 Firefox 中执行以下代码片段。唯一的区别是我添加了
\nanchorOffset: ${domSelection.anchorOffset}\n
Run Code Online (Sandbox Code Playgroud)\n到控制台输出。我们将在下面讨论结果。
\nanchorOffset: ${domSelection.anchorOffset}\n
Run Code Online (Sandbox Code Playgroud)\r\nconst textEl = document.getElementById("myText")\n\ntextEl.addEventListener("keyup", (event) => {\n const domSelection = window.getSelection();\n if (domSelection && domSelection.isCollapsed && domSelection.anchorNode) {\n let offsetNewLine = 0;\n\n let domRange = domSelection.getRangeAt(0);\n let rect = domRange.getBoundingClientRect();\n const rects = domRange.getClientRects();\n const newRange = document.createRange();\n const newRangeNextOffset = domSelection.anchorNode.textContent.length < domSelection.anchorOffset + 1 ? domSelection.anchorOffset : domSelection.anchorOffset + 1\n\n newRange.setStart(domSelection.anchorNode, newRangeNextOffset);\n newRange.setEnd(domSelection.anchorNode, newRangeNextOffset);\n let nextCharacterRect = newRange.getBoundingClientRect();\n\n console.log(`anchorOffset: ${domSelection.anchorOffset}, CURRENT_TOP: ${rect.y}, NEXT_CHAR_TOP: ${nextCharacterRect.y}`);\n }\n})
Run Code Online (Sandbox Code Playgroud)\r\n.text-container {\n width: 500px;\n display: inline-block;\n border: 1px solid black;\n line-height: 20px;\n padding: 5px;\n}
Run Code Online (Sandbox Code Playgroud)\r\n浏览器在不同的位置换行,但这不是重点。首先查看Chrome中的输出。请注意,插入符号直接跳到下一行,实际存在的空格已转换为换行符(NL),看起来是经典的回车加换行(CR+LF)形式。所以当 NL Chrome 看到光标后,就像人眼一样,已经在第 2 行了。
\n第 1 行最后一个非空格 | 换行符 | 第 2 行第一个非空格 |
---|---|---|
\'t\' 位于偏移量 61 处 | 偏移量 62 处的 NL | \'p\' 位于偏移量 63 处 |
现在是火狐。插入符号跟随空格,然后跳到下一行。空间 (SP) 已被保留。然而插入的换行符还没有在偏移量计算中。此外,它仍然被视为第 1 行的一部分,即人眼看到光标在第 2 行,但 Firefox 看到的是第 1 行。为什么呢?
\n因此,Firefox在第 1 行末尾迭代两次(SP 然后 NL),但仅增加偏移量一次(SP 和 NL 一起),并且还没有真正移动到第 2 行。所有这些都让这里的事情变得如此混乱。
\n第 1 行最后一个非空格 | 换行符 | 第 2 行第一个非空格 |
---|---|---|
\'n\' 位于偏移量 73 处 | SP和NL,均位于偏移 74 | \'t\' 位于偏移量 75 处 |
我目前能想到的唯一方法是检测浏览器并引入特定于 Firefox 的解决方法,因此要检查 Firefox,例如
\nconst isFirefox = typeof InstallTrigger !== \'undefined\';\n
Run Code Online (Sandbox Code Playgroud)\n经过测试,在 Firefox 111 上仍然可以工作。
\n因此,我们可以通过指示我们是否处于 Firefox 换行符中来解决这个问题。让我们首先添加一些全局变量:
\n// whether we\'re in a (Firefox-)NL\nlet isNewline = false;\n// whether we\'re in Firefox\nconst isFirefox = typeof InstallTrigger !== \'undefined\';\n
Run Code Online (Sandbox Code Playgroud)\n请注意,如果需要,也可以用于isNewline
其他浏览器。接下来,我们将 Firefox 特定的换行添加到keyup
处理程序中:
/*\n* Check whether we\'re in Firefox and on the edge of a line.\n* At need easily extendable for other browsers.\n*/\nif(isFirefox && rect.y < nextCharacterRect.y)\n{\n // caret is after the SP, i.e. we\'re in the NL-sequence\n if(isNewline)\n {\n /*\n * Hop straight to the next line by\n * de facto enforcing a LF+CR.\n */\n domRange = newRange;\n domSelection.getRangeAt(0);\n rect = domRange.getBoundingClientRect();\n\n // end of Firefox\' NL-sequence\n isNewline = false;\n }\n // begin of Firefox\' NL-sequence, i.e. we hit the SP\n else\n isNewline = true;\n}\n
Run Code Online (Sandbox Code Playgroud)\n这可以例如通过用于微调的选择方向检测来扩展。
\n让我们将所有内容放在下面的代码片段中。请注意,domRange
分别。rect
变成let
而不是const
.
<span id="myText" class="text-container" contentEditable="true">Go on the last position in the first row and come it to first position in the second row</span>
Run Code Online (Sandbox Code Playgroud)\r\nconst isFirefox = typeof InstallTrigger !== \'undefined\';\n
Run Code Online (Sandbox Code Playgroud)\r\n// whether we\'re in a (Firefox-)NL\nlet isNewline = false;\n// whether we\'re in Firefox\nconst isFirefox = typeof InstallTrigger !== \'undefined\';\n
Run Code Online (Sandbox Code Playgroud)\r\n可能有一个更优雅、更复杂的解决方案,但目前它可以完成任务。本质上,我们通过在 Chrome 中强制执行一种换行符 \xc3\xa0 来修改 Firefox 的换行行为LF+CR
。唯一剩下的区别是在实际换行之前行尾的额外空格,即在 Firefox 中我们仍然需要按两次按键才能进入下一行,而不是像 Chrome 中那样按一次按键。但这在这里无关紧要。否则,两个浏览器的行为现在是等效的。此外,如有必要,此解决方法可以轻松适用于其他浏览器。
使用换行符变量的最后灵感来自@herrstrietzel的一篇文章,其中还讨论了考虑选择方向和鼠标交互的方法。
\n