在可编辑的div中插入光标处的文本

78

如果您想在光标处插入HTML,请参见https://dev59.com/CGw15IYBdhLWcg3wT6H2。 - Kes115
7个回答

162

以下函数将在插入符位置插入文本并删除现有的选择内容。它适用于所有主流桌面浏览器:

function insertTextAtCaret(text) {
    var sel, range;
    if (window.getSelection) {
        sel = window.getSelection();
        if (sel.getRangeAt && sel.rangeCount) {
            range = sel.getRangeAt(0);
            range.deleteContents();
            range.insertNode( document.createTextNode(text) );
        }
    } else if (document.selection && document.selection.createRange) {
        document.selection.createRange().text = text;
    }
}

更新

根据评论,这里有一些保存和恢复选择的代码。在显示上下文菜单之前,您应该将saveSelection的返回值存储在一个变量中,然后将该变量传递给restoreSelection,以便在隐藏上下文菜单并在插入文本之前恢复选择。

function saveSelection() {
    if (window.getSelection) {
        sel = window.getSelection();
        if (sel.getRangeAt && sel.rangeCount) {
            return sel.getRangeAt(0);
        }
    } else if (document.selection && document.selection.createRange) {
        return document.selection.createRange();
    }
    return null;
}

function restoreSelection(range) {
    if (range) {
        if (window.getSelection) {
            sel = window.getSelection();
            sel.removeAllRanges();
            sel.addRange(range);
        } else if (document.selection && range.select) {
            range.select();
        }
    }
}

1
感谢您的帮助,我正在使用这个作为在线代码编辑器的一部分。每当用户在对象名称后键入“.”时,就会弹出一个上下文菜单,其中包含其方法列表(智能感知/代码完成)。当用户单击方法名称时,需要将文本粘贴到代码区域div中的点之后。但是到目前为止,“方法名称”被粘贴到上下文菜单中而不是代码区域中。 - user314362
2
好的。在这种情况下,我建议在显示上下文菜单之前,存储表示选择的范围/文本范围的副本,然后在隐藏上下文菜单但在插入文本之前从中恢复选择。 - Tim Down
1
@mikel:不完全是这样:你只需要使用第一个分支的简化版本。function insertTextAtCursor(text) { var range, sel = rangy.getSelection(); if (sel.rangeCount) { range = sel.getRangeAt(0); range.insertNode( document.createTextNode(text) ); } } - Tim Down
3
我已经更新了你的JSFiddle代码,使插入文本后光标立即移动到该位置:http://jsfiddle.net/ww3Rk/1/。在IE 9中似乎没有问题。 - Tim Down
1
@Fuxian:我预料到这一点,但我习惯于选择API,它对可编辑和不可编辑文本都是相同的。如果你担心这个问题,那么在插入文本之前,你可以检查包含选择内容的元素是否可编辑。如果需要,我可以提供代码。 - Tim Down
显示剩余6条评论

23
  1. Get a Selection Object with window.getSelection().
  2. Use Selection.getRangeAt(0).insertNode() to add a textnode.
  3. If necessary, move the cursor position behind the added text with Selection.modify(). (Not standardized, but this feature is supported in Firefox, Chrome and Safari)

    function insertTextAtCursor(text)
    {
        let selection = window.getSelection();
        let range = selection.getRangeAt(0);
        range.deleteContents();
        let node = document.createTextNode(text);
        range.insertNode(node);
    
        for(let position = 0; position != text.length; position++)
        {
            selection.modify("move", "right", "character");
        };
    }
    

1
这个解决方案的行为比被接受的答案更好,+1。 - goblin GONE
3
如果我正确阅读 https://developer.mozilla.org/en-US/docs/Web/API/Selection/collapseToEnd 的文档,那么可以在不使用 for() 循环的情况下使用 selection.collapseToEnd() 来插入/替换用户正在输入的内容。 - Munneson
@Munneson:你试过了吗?这仅适用于当前选择的内容。插入的节点未被选中,它会显示在所选内容的右侧。 - Han Seoul-Oh
工作过,但唯一的问题是它使用了一个for循环。 - raj
非常好。在 Remirror 工作过。 - lwdthe1
插入文本后,选中插入的文本,只需移动一次即可取消选择:selection.modify("move", "right", "character"); - Dee

11

更新:自2020年以来,解决方案已被废弃(尽管它仍然可以使用

// <div contenteditable id="myeditable">
// const editable = document.getElementById('myeditable')
// editable.focus()
// document.execCommand('insertHTML', false, '<b>B</b>anana')
document.execCommand('insertText', false, 'banana')

2
关于execCommand,来自MDN的说明:该功能已经过时。虽然它在某些浏览器中仍然可以使用,但由于它可能随时被删除,因此不建议使用它。请尽量避免使用。 - Jens
2
我真的希望当废弃execCommand时,浏览器能够考虑提出一种优雅的API来与可编辑内容进行交互。但事实并非如此。 - Frenchcooc
喜欢这个解决方案。 - raj

4
我使用下面的代码在聊天消息中插入图标。
<div class="chat-msg-text" id="chat_message_text" contenteditable="true"></div>

<script>
var lastCaretPos = 0;
var parentNode;
var range;
var selection;

$(function(){
    $('#chat_message_text').focus();

    $('#chat_message_text').on('keyup mouseup',function (e){
        selection = window.getSelection();
        range = selection.getRangeAt(0);
        parentNode = range.commonAncestorContainer.parentNode;
    });
})

function insertTextAtCursor(text) { 

    if($(parentNode).parents().is('#chat_message_text') || $(parentNode).is('#chat_message_text') )
    {
        var span = document.createElement('span');              
        span.innerHTML=text;

        range.deleteContents();        
        range.insertNode(span);  
        //cursor at the last with this
        range.collapse(false);
        selection.removeAllRanges();
        selection.addRange(range);

    }
    else
    {
        msg_text = $("#chat_message_text").html()
        $("#chat_message_text").html(text+msg_text).focus()                 
    }
}

</script>

enter image description here


2

如果你正在使用富文本编辑器(如DraftJs),但没有访问其API的权限(例如从扩展程序进行修改),以下是我发现的解决方案:

  • 分派beforeinput事件,这是推荐的方式,大多数编辑器都支持
target.dispatchEvent(new InputEvent("beforeinput", {
    inputType: "insertText",
    data: text,
    bubbles: true,
    cancelable: true
}))
  • 触发 paste 事件
const data = new DataTransfer();
data.setData(
    'text/plain',
    text
);
target.dispatchEvent(new ClipboardEvent("paste", {
    dataType: "text/plain",
    data: text,
    bubbles: true,
    clipboardData: data,
    cancelable: true
}));

这是最后一个使用了两种不同方法的例子:

如果你想替换所有现有的文本,你必须先选择它。

function selectTargetText(target) {
    const selection = window.getSelection();
    const range = document.createRange();
    range.selectNodeContents(target);
    selection.removeAllRanges();
    selection.addRange(range);
}

selectTargetText(target)

// wait for selection before dispatching the `beforeinput` event
document.addEventListener("selectionchange",()=>{
    target.dispatchEvent(new InputEvent("beforeinput", {
        inputType: "insertText",
        data: text,
        bubbles: true,
        cancelable: true
    }))
},{once: true})

你好,能否分享一下这种方法的一个可行示例?非常感谢您的时间。 - Zoren Konte
我的回答是针对富文本编辑器的,因为它们会监听这些事件。在我给出的那些示例中,你只需要替换 targettext,例如 document.getElementById('myId')"sometext"target 必须是富文本编辑器内部的任何 HTML 元素。 - Madacol

1
可以使用以下代码来处理粘贴纯文本。
const editorEle = document.getElementById('editor');

// Handle the `paste` event
editorEle.addEventListener('paste', function (e) {
    // Prevent the default action
    e.preventDefault();

    // Get the copied text from the clipboard
    const text = e.clipboardData
        ? (e.originalEvent || e).clipboardData.getData('text/plain')
        : // For IE
        window.clipboardData
        ? window.clipboardData.getData('Text')
        : '';

    if (document.queryCommandSupported('insertText')) {
        document.execCommand('insertText', false, text);
    } else {
        // Insert text at the current position of caret
        const range = document.getSelection().getRangeAt(0);
        range.deleteContents();

        const textNode = document.createTextNode(text);
        range.insertNode(textNode);
        range.selectNodeContents(textNode);
        range.collapse(false);

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

-5

使用jQuery更简单的方法:

复制整个div的内容

var oldhtml = $('#elementID').html();

var tobejoined = '<span>hii</span>';

//具有新HTML的元素将是

$('#elementID').html(oldhtml+tobejoined);

简单!


3
未解决最初的问题,它在html内容之后添加了文本。 - Tobi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接