【问题标题】:Rangy loses caret position on backspace in contenteditable divRangy 在 contenteditable div 的退格键上失去插入符号位置
【发布时间】:2012-05-11 20:02:36
【问题描述】:

我研究了所有 Rangy Q&A 好几天,但无法适应这种情况。

我有以下内容可编辑

<div id="area" style="width:100%;height:2em;" 
contentEditable="true";
onkeyup="formatText();"
></div>

调用一个函数,每次用户输入内容时,它都会解析内容并格式化特定的标记。

 function formatText() {

    var el = document.getElementById('area');
    var savedSel = saveSelection(el); // calls Rangy function

    var tokenColor;

        // removes html tags before passing the expression to the parser
    var userInput = document.getElementById('area').innerHTML.replace(/(<([^>]+)>)/g,"").replace(/&amp;/g, "").replace(/&gt;/g, ">").replace(/&lt;/g, "<").replace(/<span[^>]*>+<\/span>/, "");


    var i, newHTML=[];

    tokenType=[]; // [NUMBER,+,(,NUMBER,..]
    tokenArray=[]; // [3,+,(5,...]

    var resultOutput =  parse(userInput); // parser also fills tokenType and tokenArray

    for (i=0; i<tokenArray.length-1; i++){

        newHTML += "<span style='color: " + tokenColor + " '>" +  tokenArray[i] + "</span>";

         } // newHTML looks like <span style='color: red'>3</span><span style='color: black'>+</span> etc.


    el.innerHTML = newHTML; // replaces content of <div> with formatted text    

    restoreSelection(el, savedSel);  // calls Rangy function to restore cursor position
}

我使用了作者在本论坛其他帖子中提出的以下基于 Rangy 的函数:

    function saveSelection(containerEl) {
    var charIndex = 0, start = 0, end = 0, foundStart = false, stop = {};
    var sel = rangy.getSelection(), range;

    function traverseTextNodes(node, range) {
        if (node.nodeType == 3) {
            if (!foundStart && node == range.startContainer) {
                start = charIndex + range.startOffset;
                foundStart = true;
            }

            if (foundStart && node == range.endContainer) {
                end = charIndex + range.endOffset;
                throw stop;
            }
            charIndex += node.length;


        }  else {
            for (var i = 0, len = node.childNodes.length; i < len; ++i) {
                traverseTextNodes(node.childNodes[i], range);
            }   
        }
    }

    if (sel.rangeCount) {
        try {
            traverseTextNodes(containerEl, sel.getRangeAt(0));
        } catch (ex) {
            if (ex != stop) {
                throw ex;
            }
        }
    }

    return {
        start: start,
        end: end
    };
}



function restoreSelection(containerEl, savedSel) {
    var charIndex = 0, range = rangy.createRange(), foundStart = false, stop = {};
    range.collapseToPoint(containerEl, 0);

    function traverseTextNodes(node) {
        if (node.nodeType == 3) {
            var nextCharIndex = charIndex + node.length;
            if (!foundStart && savedSel.start >= charIndex && savedSel.start <= nextCharIndex) {
                range.setStart(node, savedSel.start - charIndex);
                foundStart = true;
            }
            if (foundStart && savedSel.end >= charIndex && savedSel.end <= nextCharIndex) {
                range.setEnd(node, savedSel.end - charIndex);
                throw stop;
            }
            charIndex = nextCharIndex;
        }   

        else {
            for (var i = 0, len = node.childNodes.length; i < len; ++i) {
                traverseTextNodes(node.childNodes[i]);
            }
        }
    }

    try {
        traverseTextNodes(containerEl);
    } catch (ex) {
        if (ex == stop) {
            rangy.getSelection().setSingleRange(range);   
        } else {
            throw ex;
        }
    }
}

在我尝试删除一个字符之前,一切正常。此时光标会跳到div的开头。

知道为什么会发生这种情况吗?

非常感谢。

【问题讨论】:

  • 您销毁并用innerHTML替换了div的全部内容,因此被选中的节点不再存在。您应该手动删除不需要的节点,而不是使用 innerHTML,而让其他节点保持原样。
  • @GGG:第二个代码块对选择进行基于字符索引的保存和恢复,所以只要可见文本在innerHTML替换后保持不变,它应该可以工作。
  • @TimDown 我现在明白你的意思了,我之前只是略过那部分。我仍然对这项工作持怀疑态度。一些正则表达式看起来会创建损坏的标记。 '&lt;span&gt;&lt;/span&gt;'.replace(/(&lt;([^&gt;]+)&gt;)/, '') // "&lt;/span&gt;"
  • @GGG:我将以下链接用于正则表达式pagecolumn.com/tool/all_about_html_tags.htm。您能否建议一个更好的选择来删除所有没有内容的跨度标签?这也应该删除像 这样的空跨度和带有隐藏字符的跨度。如果 javascript 用特殊的隐藏字符替换 DEL,可能在按下删除时会创建一些奇怪的跨度?

标签: javascript dom contenteditable rangy


【解决方案1】:

在 Tim Down 的帮助下,我已经解决了这个问题(感谢 Tim!)。

他最近在 Rangy 中添加了新的 TextRange 模块,它运行良好。该模块具有基于页面上可见文本中的字符索引的选择保存/恢复,因此不受 innerHTML 更改的影响。你可以在这里找到演示:

http://rangy.googlecode.com/svn/trunk/demos/textrange.html

文档(初步):http://code.google.com/p/rangy/wiki/TextRangeModule

所以基本上代码应该是:

    document.getElementById("area").onkeyup = function() {
    var sel = rangy.getSelection();
    var savedSel = sel.saveCharacterRanges(this);    

    var userInput = this.textContent || this.innerText;

    var userInputLength = userInput.length;  
    var newHTML = [];

    for (var i=0; i<userInputLength; i++) {
        newHTML[i] = "<span style='color: red'>" + userInput.charAt(i)  + "</span>";  
    }

    this.innerHTML =  newHTML.join("");

    sel.restoreCharacterRanges(this, savedSel);
};
​

希望这会有所帮助。

【讨论】:

    【解决方案2】:

    在我看来,它好像可以工作。解决方案可能很简单,只需在恢复选择之前调用 contenteditable &lt;div&gt;focus() 方法即可。

    【讨论】:

    • 嗨,蒂姆,根据您的建议,我添加了以下内容:$('#area').focus();恢复选择(el,savedSel); } 但仍然无法按删除。它删除最后一个字符并在 div 的开头跳转。它在您键入时完美运行,所以很奇怪。似乎在删除时会生成一个空的 块,并且第一个 Range.StartContainer.nodeName="SPAN".Could 这就是原因吗?
    • 更新:我认为这与另一个问题有关:当按下 SHIFT + 右箭头时,它会正确突出显示前进的字符。当您尝试使用向左箭头时,它会突出显示一个字符,然后将光标向左移动,然后突出显示另一个字符,依此类推。我认为有些逻辑不喜欢向后的操作,例如删除或向后突出显示。谢谢。
    猜你喜欢
    • 2011-10-12
    • 1970-01-01
    • 2021-08-28
    • 1970-01-01
    • 1970-01-01
    • 2012-01-19
    • 2014-07-29
    • 2012-05-11
    • 2011-06-17
    相关资源
    最近更新 更多