【问题标题】:replace innerHTML in contenteditable div替换 contenteditable div 中的 innerHTML
【发布时间】:2011-08-01 13:03:03
【问题描述】:

我需要在 contenteditable div 中实现数字高亮(将来我会添加更复杂的规则)。问题是当我使用 javascript 替换插入新内容时,DOM 更改和 contenteditable div 失去焦点。我需要的是专注于当前位置的插入符号的 div,这样用户就可以毫无问题地输入,而我的功能简单地突出显示数字。谷歌搜索我认为Rangy 库是最好的解决方案。我有以下代码:

function formatText() {
         
              var savedSel = rangy.saveSelection();
              el = document.getElementById('pad');
              el.innerHTML = el.innerHTML.replace(/(<([^>]+)>)/ig,"");
              el.innerHTML = el.innerHTML.replace(/([0-9])/ig,"<font color='red'>$1</font>");
              rangy.restoreSelection(savedSel);
          }

<div contenteditable="true" id="pad" onkeyup="formatText();"></div>

问题是在函数结束工作重心回到 div 之后,但插入符号始终指向 div begin,我可以在任何地方键入,除了 div begin。还有Rangy warning: Module SaveRestore: Marker element has been removed. Cannot restore selection. 之后的console.log 类型 请帮我实现这个功能。我对另一个解决方案持开放态度,而不仅仅是 rangy 库。谢谢!

http://jsfiddle.net/2rTA5/ 这是 jsfiddle,但它不能正常工作(当我在我的 div 中输入数字时没有任何反应),不知道可能是我(第一次通过 jsfiddle 发布代码)或资源不支持 contenteditable。 UPD* 我在 stackoverflow 上读到了类似的问题,但解决方案不适合我的情况:(

【问题讨论】:

  • 你的问题很难理解,或许你可以在jsfiddle.net放个demo
  • 你指的是插入符号位置吗?
  • 我将我的代码发布到 js fiddle,请参阅我的帖子。据我所知,这就是 restoreSelection 所做的。图书馆记住插入符号的位置并恢复它。

标签: javascript html range contenteditable rangy


【解决方案1】:

问题在于 Rangy 的保存/恢复选择模块通过将不可见的标记元素插入到选择边界所在的 DOM 中来工作,然后您的代码会去除所有 HTML 标记,包括 Rangy 的标记元素(如错误消息所示)。你有两个选择:

  1. 使用 DOM 遍历解决方案为数字着色而不是 innerHTML。这将更可靠,但涉及更多。
  2. 实现基于字符索引的替代选择保存和恢复。这通常很脆弱,但在这种情况下会做你想做的事。

更新

我已经为 Rangy(上面的选项 2)敲了一个基于字符索引的选择保存/恢复。这有点粗糙,但它确实适用于这种情况。它通过遍历文本节点来工作。我可能会以某种形式将它添加到 Rangy 中。 (2012 年 6 月 5 日更新:I've now implemented this, in a more reliable way, for Rangy.

jsFiddle:http://jsfiddle.net/2rTA5/2/

代码:

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;
        }
    }
}

function formatText() {
    var el = document.getElementById('pad');
    var savedSel = saveSelection(el);
    el.innerHTML = el.innerHTML.replace(/(<([^>]+)>)/ig,"");
    el.innerHTML = el.innerHTML.replace(/([0-9])/ig,"<font color='red'>$1</font>");

    // Restore the original selection
    restoreSelection(el, savedSel);
}

【讨论】:

  • 哇!非常感谢蒂姆。一个小但重要的问题,在按下输入插入符号后,开始而不是新行。这对我很重要。用户需要输入多行文本。如何获得这个功能?
  • @Mikhail:我明天会调查一下。
  • @Mikhail:按 Enter 的问题是您的代码会删除浏览器为换行符插入的任何 &lt;br&gt;&lt;div&gt; 元素。如果处理这对您很重要,您可能最好使用 DOM 遍历方法来为数字着色。
  • 对不起我的愚蠢,但是你能描述更多这种遍历方式,或者给我一个链接,非常感谢你所有的工作和你的图书馆。
  • @Mikhail:我会使用递归函数来检查容器元素中的每个文本节点是否有数字。诚然,它有点涉及,但具有保留现有元素的好处。以下是一些您可能能够适应的示例:stackoverflow.com/questions/4040495/…stackoverflow.com/questions/2798142/…
【解决方案2】:

我要感谢蒂姆在这里与我们分享的功能,这对于我正在从事的项目非常重要。我将他的函数嵌入了一个小的 jQuery 插件,可以在这里访问:https://jsfiddle.net/sh5tboL8/

$.fn.get_selection_start = function(){
    var result = this.get(0).selectionStart;
    if (typeof(result) == 'undefined') result = this.get_selection_range().selection_start;
    return result;
}

$.fn.get_selection_end = function(){
    var result = this.get(0).selectionEnd;
    if (typeof(result) == 'undefined') result = this.get_selection_range().selection_end;
    return result;
}

$.fn_get_selected_text = function(){
    var value = this.get(0).value;
    if (typeof(value) == 'undefined'){
        var result = this.get_selection_range().selected_text;
    }else{
        var result = value.substring(this.selectionStart, this.selectionEnd);
    }
    return result;
}

$.fn.get_selection_range = function(){

    var range = window.getSelection().getRangeAt(0);
    var cloned_range = range.cloneRange();
    cloned_range.selectNodeContents(this.get(0));
    cloned_range.setEnd(range.startContainer, range.startOffset);
    var selection_start = cloned_range.toString().length;
    var selected_text = range.toString();
    var selection_end = selection_start + selected_text.length;
    var result = {
        selection_start: selection_start,
        selection_end: selection_end,
        selected_text: selected_text
    }
    return result;
}

$.fn.set_selection = function(selection_start, selection_end){
    var target_element = this.get(0);
    selection_start = selection_start || 0;
    if (typeof(target_element.selectionStart) == 'undefined'){
        if (typeof(selection_end) == 'undefined') selection_end = target_element.innerHTML.length;

        var character_index = 0;
        var range = document.createRange();
        range.setStart(target_element, 0);
        range.collapse(true);
        var node_stack = [target_element];
        var node = null;
        var start_found = false;
        var stop = false;

        while (!stop && (node = node_stack.pop())) {
            if (node.nodeType == 3){
                var next_character_index = character_index + node.length;
                if (!start_found && selection_start >= character_index && selection_start <= next_character_index){
                    range.setStart(node, selection_start - character_index);
                    start_found = true;
                }

                if (start_found && selection_end >= character_index && selection_end <= next_character_index){
                    range.setEnd(node, selection_end - character_index);
                    stop = true;
                }
                character_index = next_character_index;
            }else{
                var child_counter = node.childNodes.length;
                while (child_counter --){
                    node_stack.push(node.childNodes[child_counter]);
                }
            }
        }

        var selection = window.getSelection();
        selection.removeAllRanges();
        selection.addRange(range);
    }else{
        if (typeof(selection_end) == 'undefined') selection_end = target_element.value.length;
        target_element.focus();
        target_element.selectionStart = selection_start;
        target_element.selectionEnd = selection_end;
    }
}

插件只做我需要它做的事情,获取选定的文本,并设置自定义文本选择。它也适用于文本框和 contentEditable div。

【讨论】:

    猜你喜欢
    • 2011-11-16
    • 1970-01-01
    • 2014-02-04
    • 2011-04-29
    • 2018-07-09
    • 2012-06-13
    • 2011-03-18
    • 1970-01-01
    • 2011-05-16
    相关资源
    最近更新 更多