Modify text in a contenteditable div without reset

2019-04-06 20:50发布

问题:

I am trying to replace any instances of /any thing in here/ with <b>/any thing in here/</b> on the fly, as changes are made in a contenteditable div.

My current implementation works, but at every keypress the caret is moved to the beginning of div making the implementation unusable. Is there some way to keep the caret position while replacing the div's contents?

$('.writer').on('keyup', function(e) {
     $(this).html($(this).html().replace(/\/(.*)\//g, '<b>\/$1\/<\/b>'));
});

回答1:

try it demo

    $('#writer').on('keyup', function(e) {
        var range = window.getSelection().getRangeAt(0);
        var end_node = range.endContainer;
        var end = range.endOffset;
        if(end_node != this){
            var text_nodes = get_text_nodes_in(this);
            for (var i = 0; i < text_nodes.length; ++i) {
                if(text_nodes[i] == end_node){
                    break;
                }
                end += text_nodes[i].length;
            }
        }
        var html = $(this).html();
        if(/\&nbsp;$/.test(html) && $(this).text().length == end){
            end = end - 1;
            set_range(end,end,this);
            return;
        }
        var filter = html.replace(/(<b>)?\/([^<\/]*)(<\/b>)?/g, '\/$2');
        console.log(filter);
        filter = filter.replace(/(<b>)?([^<\/]*)\/(<\/b>)?/g, '$2\/');
        console.log(filter);
        filter = filter.replace(/(<b>)?\/([^<\/]*)\/(<\/b>)?/g, '<b>\/$2\/<\/b>');
        console.log(filter);
        if(!/\&nbsp;$/.test($(this).html())){
            filter += '&nbsp;';
        }
        $(this).html(filter);
        set_range(end,end,this);

    });

    $('#writer').on('mouseup', function(e) {
        if(!/\&nbsp;$/.test($(this).html())){
            return;
        }
        var range = window.getSelection().getRangeAt(0);
        var end = range.endOffset;
        var end_node = range.endContainer;
        if(end_node != this){
            var text_nodes = get_text_nodes_in(this);
            for (var i = 0; i < text_nodes.length; ++i) {
                if(text_nodes[i] == end_node){
                    break;
                }
                end += text_nodes[i].length;
            }
        }
        if($(this).text().length == end){
            end = end - 1;
            set_range(end,end,this);
        }
    });

    function get_text_nodes_in(node) {
        var text_nodes = [];
        if (node.nodeType === 3) {
            text_nodes.push(node);
        } else {
            var children = node.childNodes;
            for (var i = 0, len = children.length; i < len; ++i) {
                var text_node
                text_nodes.push.apply(text_nodes, get_text_nodes_in(children[i]));
            }
        }
        return text_nodes;
    }

    function set_range(start, end, element) {
        var range = document.createRange();
        range.selectNodeContents(element);
        var text_nodes = get_text_nodes_in(element);
        var foundStart = false;
        var char_count = 0, end_char_count;

        for (var i = 0, text_node; text_node = text_nodes[i++]; ) {
            end_char_count = char_count + text_node.length;
            if (!foundStart && start >= char_count && (start < end_char_count || (start === end_char_count && i < text_nodes.length))) {
                range.setStart(text_node, start - char_count);
                foundStart = true;
            }
            if (foundStart && end <= end_char_count) {
                range.setEnd(text_node, end - char_count);
                break;
            }
            char_count = end_char_count;
        }

        var selection = window.getSelection();
        selection.removeAllRanges();
        selection.addRange(range);
    }