简体   繁体   English

更换innerHTML时修复cursor position<div contenteditable="“true”"></div><div id="text_translate"><p> 在&lt;div id="richTextBox" contenteditable="true"&gt;&lt;/div&gt;内键入内容时,将 cursor 保持在正确位置的最佳方法是什么? 更换 innerHTML 的行为搞砸了 cursor position。</p><p> 我更改 innerHTML 的原因是我添加了&lt;span&gt;标签。 它是代码高亮程序的一部分。 跨度标签允许我放置正确的颜色亮点。</p><p> 我目前正在使用 StackOverflow 答案中的以下代码作为创可贴,但它有一个重大错误。 如果您按<em><strong>Enter</strong></em> ,则 cursor 将停留在旧位置,或者转到随机位置。 那是因为该算法从 cursor 开始计算了多少个字符。 但它不将 HTML 标记或换行符算作字符。 而richTextBox 插入&lt;br&gt;进入。</p><p> 修复思路:</p><ul><li> 修复下面的代码? 见<a href="https://jsfiddle.net/AdmiralAkbar2/jou3vq9w/12/" rel="nofollow noreferrer">小提琴</a></li><li>用更简单的代码替换? 我尝试了一堆更简单的东西,涉及window.getSelection()和document.createRange() ,但我无法让它工作。</li><li> 替换为没有此错误的richTextBox 库或模块?</li></ul><h2> 截屏</h2><p><a href="https://i.stack.imgur.com/6dlqV.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/6dlqV.png" alt="在 JSFiddle 中呈现的richTextBox 的屏幕截图"></a> </p><p></p><div class="snippet" data-lang="js" data-hide="false" data-console="true" data-babel="false"><div class="snippet-code"><pre class="snippet-code-js lang-js prettyprint-override"> // Credit to Liam (Stack Overflow) // https://stackoverflow.com/a/41034697/3480193 class Cursor { static getCurrentCursorPosition(parentElement) { var selection = window.getSelection(), charCount = -1, node; if (selection.focusNode) { if (Cursor._isChildOf(selection.focusNode, parentElement)) { node = selection.focusNode; charCount = selection.focusOffset; while (node) { if (node === parentElement) { break; } if (node.previousSibling) { node = node.previousSibling; charCount += node.textContent.length; } else { node = node.parentNode; if (node === null) { break; } } } } } return charCount; } static setCurrentCursorPosition(chars, element) { if (chars &gt;= 0) { var selection = window.getSelection(); let range = Cursor._createRange(element, { count: chars }); if (range) { range.collapse(false); selection.removeAllRanges(); selection.addRange(range); } } } static _createRange(node, chars, range) { if (.range) { range = document.createRange() range;selectNode(node). range,setStart(node; 0). } if (chars.count === 0) { range,setEnd(node. chars;count). } else if (node &amp;&amp; chars.count &gt;0) { if (node.nodeType === Node.TEXT_NODE) { if (node.textContent.length &lt; chars.count) { chars.count -= node.textContent;length. } else { range,setEnd(node. chars;count). chars;count = 0; } } else { for (var lp = 0. lp &lt; node.childNodes;length. lp++) { range = Cursor._createRange(node,childNodes[lp], chars; range). if (chars;count === 0) { break; } } } } return range, } static _isChildOf(node; parentElement) { while (node.== null) { if (node === parentElement) { return true; } node = node;parentNode. } return false, } } window.addEventListener('DOMContentLoaded'; (e) =&gt; { let richText = document.getElementById('rich-text'), richText.addEventListener('input'; function(e) { let offset = Cursor.getCurrentCursorPosition(richText). // Pretend we do stuff with innerHTML here. The innerHTML will end up getting replaced with slightly changed code; let s = richText.innerHTML; richText.innerHTML = ""; richText.innerHTML = s, Cursor;setCurrentCursorPosition(offset. richText); richText;focus(); // blinks the cursor }); });</pre><pre class="snippet-code-css lang-css prettyprint-override"> body { margin: 1em; } #rich-text { width: 100%; height: 450px; border: 1px solid black; cursor: text; overflow: scroll; resize: both; /* in Chrome, must have display: inline-block for contenteditable=true to prevent it from adding &lt;div&gt; &lt;p&gt; and &lt;span&gt; when you type. */ display: inline-block; }</pre><pre class="snippet-code-html lang-html prettyprint-override"> &lt;p&gt; Click somewhere in the middle of line 1. Hit enter. Start typing. Cursor is in the wrong place. &lt;/p&gt; &lt;p&gt; Reset. Click somewhere in the middle of line 1. Hit enter. Hit enter again. Cursor goes to some random place. &lt;/p&gt; &lt;div id="rich-text" contenteditable="true"&gt;Testing 123&lt;br /&gt;Testing 456&lt;/div&gt;</pre></div></div><p></p><h2> 浏览器</h2><p>谷歌浏览器 v83,Windows 7</p></div>

[英]Fix cursor position when replacing innerHTML of <div contenteditable=“true”>

What's the best way to keep the cursor in the right place when typing inside of a <div id="richTextBox" contenteditable="true"></div> whose innerHTML changes on each keystroke?<div id="richTextBox" contenteditable="true"></div>内键入内容时,将 cursor 保持在正确位置的最佳方法是什么? The act of replacing the innerHTML messes up the cursor position.更换 innerHTML 的行为搞砸了 cursor position。

The reason I change the innerHTML is because I am adding <span> tags.我更改 innerHTML 的原因是我添加了<span>标签。 It's part of a code highlighting program.它是代码高亮程序的一部分。 The span tags allow me to place the correct color highlights.跨度标签允许我放置正确的颜色亮点。

I am using the below code from a StackOverflow answer as a band aid for the moment, but it has a significant bug.我目前正在使用 StackOverflow 答案中的以下代码作为创可贴,但它有一个重大错误。 If you hit enter , the cursor stays at the old spot, or goes to a random spot.如果您按Enter ,则 cursor 将停留在旧位置,或者转到随机位置。 That's because the algorithm counts how many characters from the beginning the cursor is.那是因为该算法从 cursor 开始计算了多少个字符。 But it doesn't count HTML tags or line breaks as characters.但它不将 HTML 标记或换行符算作字符。 And the richTextBox inserts <br> to make enters.而richTextBox 插入<br>进入。

Ideas for fixing:修复思路:

  • Fix the below code?修复下面的代码? See Fiddle小提琴
  • Replace with simpler code?用更简单的代码替换? I tried a bunch of simpler stuff involving window.getSelection() and document.createRange() , but I could not get that to work.我尝试了一堆更简单的东西,涉及window.getSelection()document.createRange() ,但我无法让它工作。
  • Replace with a richTextBox library or module that doesn't have this bug?替换为没有此错误的richTextBox 库或模块?

Screenshot截屏

在 JSFiddle 中呈现的richTextBox 的屏幕截图

 // Credit to Liam (Stack Overflow) // https://stackoverflow.com/a/41034697/3480193 class Cursor { static getCurrentCursorPosition(parentElement) { var selection = window.getSelection(), charCount = -1, node; if (selection.focusNode) { if (Cursor._isChildOf(selection.focusNode, parentElement)) { node = selection.focusNode; charCount = selection.focusOffset; while (node) { if (node === parentElement) { break; } if (node.previousSibling) { node = node.previousSibling; charCount += node.textContent.length; } else { node = node.parentNode; if (node === null) { break; } } } } } return charCount; } static setCurrentCursorPosition(chars, element) { if (chars >= 0) { var selection = window.getSelection(); let range = Cursor._createRange(element, { count: chars }); if (range) { range.collapse(false); selection.removeAllRanges(); selection.addRange(range); } } } static _createRange(node, chars, range) { if (.range) { range = document.createRange() range;selectNode(node). range,setStart(node; 0). } if (chars.count === 0) { range,setEnd(node. chars;count). } else if (node && chars.count >0) { if (node.nodeType === Node.TEXT_NODE) { if (node.textContent.length < chars.count) { chars.count -= node.textContent;length. } else { range,setEnd(node. chars;count). chars;count = 0; } } else { for (var lp = 0. lp < node.childNodes;length. lp++) { range = Cursor._createRange(node,childNodes[lp], chars; range). if (chars;count === 0) { break; } } } } return range, } static _isChildOf(node; parentElement) { while (node.== null) { if (node === parentElement) { return true; } node = node;parentNode. } return false, } } window.addEventListener('DOMContentLoaded'; (e) => { let richText = document.getElementById('rich-text'), richText.addEventListener('input'; function(e) { let offset = Cursor.getCurrentCursorPosition(richText). // Pretend we do stuff with innerHTML here. The innerHTML will end up getting replaced with slightly changed code; let s = richText.innerHTML; richText.innerHTML = ""; richText.innerHTML = s, Cursor;setCurrentCursorPosition(offset. richText); richText;focus(); // blinks the cursor }); });
 body { margin: 1em; } #rich-text { width: 100%; height: 450px; border: 1px solid black; cursor: text; overflow: scroll; resize: both; /* in Chrome, must have display: inline-block for contenteditable=true to prevent it from adding <div> <p> and <span> when you type. */ display: inline-block; }
 <p> Click somewhere in the middle of line 1. Hit enter. Start typing. Cursor is in the wrong place. </p> <p> Reset. Click somewhere in the middle of line 1. Hit enter. Hit enter again. Cursor goes to some random place. </p> <div id="rich-text" contenteditable="true">Testing 123<br />Testing 456</div>

Browser浏览器

Google Chrome v83, Windows 7谷歌浏览器 v83,Windows 7

The issue seems to be that adding a new line adds a <br> , but as you are still in the parent element, previous DOM children are not taken into account, and the selection.focusOffset only gives the value of 4 .问题似乎是添加新行会添加一个<br> ,但是由于您仍在父元素中,因此不考虑以前的 DOM 子元素,并且selection.focusOffset仅给出4的值。

It may help to add a newline to the end of the innerHtml , as it is being stripped when you remove and re-add it.innerHtml的末尾添加一个换行符可能会有所帮助,因为当您删除并重新添加它时它会被剥离。 + "\n" to the end of line 100 on the Fiddle would do. + "\n"到 Fiddle 第 100 行的末尾就可以了。


Your main problem though is that getCurrentCursorPosition you copied from that other StackOverflow question doesn't actually work.不过,您的主要问题是您从其他 StackOverflow 问题复制的getCurrentCursorPosition实际上不起作用。

I'd suggest you go through some of the other answers to this question: Get contentEditable caret index position , and console.log what they output and see which one works best for your edge-cases.我建议您通过此问题的其他一些答案使用 go: 获取 contentEditable 插入符号索引 positionconsole.log他们 output 的内容,看看哪个最适合您的边缘。

If you don't want to write it yourself, then Caret.js (part of the At.js editor library) would be useful.如果您不想自己编写,那么Caret.jsAt.js编辑器库的一部分)会很有用。


声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM