我刚刚注意到div contenteditable
在Firefox中报告了1个换行符,而在Firefox中则是2个换行符。
这是错误还是我错过了什么?
在以下示例中,只需键入:
Hello
World
在contenteditable
中。
使用innerText
抓取值将其报告为:
Hello
World
const textarea = document.querySelector('#textarea')
textarea.addEventListener('keydown', e => {
console.log(textarea.innerText)
})
#textarea {
width: 200px;
height: 75px;
outline: 1px solid black;
}
<div id="textarea" contenteditable="true"></div>
答案 0 :(得分:2)
2020年8月27日更新
我认为答案可能不是使用innerText
,而是使用textContent
与CSS指令white-space: pre-wrap;
请参见this answer。
正如我在评论中所说,截至今天(2020年7月26日),这种execCommand
解决方案似乎没有任何作用。
看着这个问题,我发现如果我在可编辑的DIV中有此内容:
asdfkadsfaklshdfkajsa
sdfaalkj
Pinkerton detective agency
sdfaldskfhalks
...,然后在该行的第一行末尾添加换行符,在Firefox中,我得到以下innerHTML
:
<div>asdfkadsfaklshdfkajsa</div><div><br></div><br>sdfaalkj <br><br>Pinkerton detective agency<br><br>sdfaldskfhalks<br>
...并计算\n
上的换行符(innerText
),据说有9条换行符。在Chrome(不会发生此问题的地方)中,我得到以下innerHTML
:
asdfkadsfaklshdfkajsa<div><br><br>sdfaalkj <br><br>Pinkerton detective agency<br><br>sdfaldskfhalks<br></div>
,据说innerText
中有 8 个换行符。我们可以看到,DIV标签的使用是完全和完全不同的,的确是荒谬的。对于Firefox所宣称的将这种处理方式与其他浏览器保持一致的目标而言,如此之多!
可能的解决方案
解决方案是干扰生成的HTML,并且在您的文本中故意包含DIV元素的情况下当然不适用。但是,对于处理最原始的纯文本(DIV元素实际上根本没有位置),此解决方案似乎有效(当然,此代码仅适用于Firefox):
if( comp.innerHTML.toLowerCase().includes( '<div>' )){
// spurious double new BR happens under certain circs
if( comp.innerHTML.includes( '<div><br><\/div><div><br><\/div>' )){
comp.innerHTML = comp.innerHTML.replace(/<div><br><\/div><div><br><\/div>/i, '<br>' );
// see below: must be decremented
startOffset -= 1;
}
// simple new BR
comp.innerHTML = comp.innerHTML.replace(/<div><br><\/div>/i, '<br>' );
// console.log( comp.innerHTML )
comp.innerHTML = comp.innerHTML.replace(/<\/div>/gi, '<br>' );
// console.log( comp.innerHTML )
comp.innerHTML = comp.innerHTML.replace(/<div>/gi, '' );
console.log( comp.innerHTML )
}
我第一次尝试时遇到上述错误。我之所以每次都包含console.log
是为了让您可以看到正在发生的事情。实际上,您必须在删除<BR>
之前,先用</DIV>
替换<DIV>
,:如果删除<DIV>
,这也会自动删除结束</DIV>
标签。但是,如果只删除结束标记,则开始标记会保留!
...之后,在FF中,您有一个innerText
,其中包含8个换行符,事情有点像您希望/期望的那样。
不幸的是,事实并非如此简单:有时反复按Enter键 会导致将两个<DIV><BR></DIV>
插入到一个应该插入的地方。但是,如果您实际单击其他位置,然后将插入符号返回到DIV,则在您下次单击Enter时可以正常使用。
要检测浏览器,请参阅here。
您还有另一个问题:设置innerHTML
会将光标移到内容的开头。这并非无关紧要:请记住,作为DOM结构,您现在拥有的是几个文本节点,这些文本节点散布着<BR>
节点。请参阅下面的建议解决方案,该解决方案出现即可工作。
还要注意,如果您有一个突变观察者在监听文档中的更改,则像这样插入新行会触发大量异步突变事件。实际上,事件数量似乎与DIV中BR节点的数量有关。即使变异观察者作为对第一个此类事件的响应而断开连接,您仍然会得到这些。就我而言,上面的代码是每次触发的,但是它是无害的,因为一旦您按上述方式进行处理,后续调用就不会再对其进行更改(即,已从innerHTML中删除了所有DIV)。
适用于插入符号获取/设置的解决方案
如我所说,设置innerHTML
会将插入号设置为文本的开头。不好。我们还知道(或假设)此文本包含新行(在HTML中使用<BR>
),但未进行其他标记,因此我们可以利用:DIV的childNodes应该只是#text节点和BR节点。
第一个实验似乎表明我们可以使用这些功能。首先,在应用更改之前先获得插入符的位置,将每个BR计数为1个字符:
function calculateTotalOffset(node, offset) {
let total = offset;
let currNode = node;
while (currNode !== comp ) {
if( currNode.tagName === 'BR'){
total += 1;
}
if(currNode.previousSibling) {
total += currNode.previousSibling.textContent.length;
currNode = currNode.previousSibling;
} else {
currNode = currNode.parentElement;
}
}
return total;
}
const sel = window.getSelection();
const range = sel.getRangeAt( 0 );
let startOffset = calculateTotalOffset(range.startContainer, range.startOffset);
...一旦更改了innerHTML
,就设置了插入符号:
function applyTotalOffset( headNode, offset ){
let remainingOffset = offset;
for( let node of headNode.childNodes ){
if( node.nodeName === '#text' ){
if( node.length > remainingOffset ){
range.setStart( node, remainingOffset );
return;
}
else {
remainingOffset -= node.length;
}
}
else if( node.nodeName === 'BR' ){
if( remainingOffset === 0 ){
range.setStart( node, 0 );
return;
}
else {
remainingOffset -= 1;
}
}
else {
throw new Error( 'not plain text: ' + headNode.innerHTML );
}
}
}
// add 1 to returned offset because the cursor should advance by 1, after adding new line
applyTotalOffset( comp, startOffset + 1 );
答案 1 :(得分:1)
这似乎与Firefox的innerText实现有关。
contenteditable
中的新行在历史上一直缺乏关于精确实现的共识。 Gecko插入了<br>
,IE和Opera的Presto用<p>
标签包裹了这一行,Webkit和Blink用<div>
标签包裹了这一行。借助旧的使用<br>
标签的实现,Firefox的innerText
可以很好地工作,正是您的问题想要的那样。
但是,出于一致性的考虑,当今的现代浏览器已达成普遍共识。 MDN专门针对Firefox:
从Firefox 60开始,Firefox将进行更新,以将单独的行包装在
<div>
元素中,以匹配Chrome,现代Opera,Edge和Safari的行为。
现代浏览器插入空白行时,它们是通过将<br>
包裹在<div>
标签中来实现的。似乎Firefox的innerText
实现将其解释为两行。
解决方法非常简单。 execCommand
方法(非官方的W3草案,但我们需要Firefox支持),您可以使用定义的命令来操作contenteditable
的内容。其中一种命令是defaultParagraphSeparator
,which allows,您可以指定使用<div>
还是<p>
作为contenteditable
中行的容器。
Firefox-和仅 Firefox-还通过<br>
实现了对defaultParagraphSeparator
的支持。使用此命令,contenteditable的行为将类似于FF60之前的行为,即插入换行符而不是在容器内包装换行。
因此,您所需要做的就是:
document.execCommand("defaultParagraphSeparator", false, "br");
在您的JavaScript中。然后,所有版本的Firefox都将使用<br>
代替contenteditable
中新行的容器,从而使innerText
正确地解释新行。而且除Firefox以外的所有浏览器都将忽略它。