首页 文章

在contentEditable <div>上设置光标位置

提问于
浏览
136

我正在寻找一个明确的跨浏览器解决方案,当contentEditable ='on'<div>重新获得焦点时,将光标/插入位置设置为最后的已知位置 . 看来内容可编辑div的默认功能是每次单击时将插入符/光标移动到div中文本的开头,这是不合需要的 .

我相信当他们离开div的焦点时我必须在变量中存储当前光标位置,然后当他们再次聚焦时重新设置它,但是我无法放在一起,或者找不到工作代码示例 .

如果有人有任何想法,工作代码片段或样本我很乐意看到它们 .

我还没有任何代码,但这是我所拥有的:

<script type="text/javascript">
// jQuery
$(document).ready(function() {
   $('#area').focus(function() { .. }  // focus I would imagine I need.
}
</script>
<div id="area" contentEditable="true"></div>

PS . 我已经尝试过这个资源,但它似乎不适用于<div> . 也许只适用于textarea(How to move cursor to end of contenteditable entity

8 回答

  • 19

    这与基于标准的浏览器兼容,但在IE中可能会失败 . 我提供它作为起点 . IE不支持DOM Range .

    var editable = document.getElementById('editable'),
        selection, range;
    
    // Populates selection and range variables
    var captureSelection = function(e) {
        // Don't capture selection outside editable region
        var isOrContainsAnchor = false,
            isOrContainsFocus = false,
            sel = window.getSelection(),
            parentAnchor = sel.anchorNode,
            parentFocus = sel.focusNode;
    
        while(parentAnchor && parentAnchor != document.documentElement) {
            if(parentAnchor == editable) {
                isOrContainsAnchor = true;
            }
            parentAnchor = parentAnchor.parentNode;
        }
    
        while(parentFocus && parentFocus != document.documentElement) {
            if(parentFocus == editable) {
                isOrContainsFocus = true;
            }
            parentFocus = parentFocus.parentNode;
        }
    
        if(!isOrContainsAnchor || !isOrContainsFocus) {
            return;
        }
    
        selection = window.getSelection();
    
        // Get range (standards)
        if(selection.getRangeAt !== undefined) {
            range = selection.getRangeAt(0);
    
        // Get range (Safari 2)
        } else if(
            document.createRange &&
            selection.anchorNode &&
            selection.anchorOffset &&
            selection.focusNode &&
            selection.focusOffset
        ) {
            range = document.createRange();
            range.setStart(selection.anchorNode, selection.anchorOffset);
            range.setEnd(selection.focusNode, selection.focusOffset);
        } else {
            // Failure here, not handled by the rest of the script.
            // Probably IE or some older browser
        }
    };
    
    // Recalculate selection while typing
    editable.onkeyup = captureSelection;
    
    // Recalculate selection after clicking/drag-selecting
    editable.onmousedown = function(e) {
        editable.className = editable.className + ' selecting';
    };
    document.onmouseup = function(e) {
        if(editable.className.match(/\sselecting(\s|$)/)) {
            editable.className = editable.className.replace(/ selecting(\s|$)/, '');
            captureSelection();
        }
    };
    
    editable.onblur = function(e) {
        var cursorStart = document.createElement('span'),
            collapsed = !!range.collapsed;
    
        cursorStart.id = 'cursorStart';
        cursorStart.appendChild(document.createTextNode('—'));
    
        // Insert beginning cursor marker
        range.insertNode(cursorStart);
    
        // Insert end cursor marker if any text is selected
        if(!collapsed) {
            var cursorEnd = document.createElement('span');
            cursorEnd.id = 'cursorEnd';
            range.collapse();
            range.insertNode(cursorEnd);
        }
    };
    
    // Add callbacks to afterFocus to be called after cursor is replaced
    // if you like, this would be useful for styling buttons and so on
    var afterFocus = [];
    editable.onfocus = function(e) {
        // Slight delay will avoid the initial selection
        // (at start or of contents depending on browser) being mistaken
        setTimeout(function() {
            var cursorStart = document.getElementById('cursorStart'),
                cursorEnd = document.getElementById('cursorEnd');
    
            // Don't do anything if user is creating a new selection
            if(editable.className.match(/\sselecting(\s|$)/)) {
                if(cursorStart) {
                    cursorStart.parentNode.removeChild(cursorStart);
                }
                if(cursorEnd) {
                    cursorEnd.parentNode.removeChild(cursorEnd);
                }
            } else if(cursorStart) {
                captureSelection();
                var range = document.createRange();
    
                if(cursorEnd) {
                    range.setStartAfter(cursorStart);
                    range.setEndBefore(cursorEnd);
    
                    // Delete cursor markers
                    cursorStart.parentNode.removeChild(cursorStart);
                    cursorEnd.parentNode.removeChild(cursorEnd);
    
                    // Select range
                    selection.removeAllRanges();
                    selection.addRange(range);
                } else {
                    range.selectNode(cursorStart);
    
                    // Select range
                    selection.removeAllRanges();
                    selection.addRange(range);
    
                    // Delete cursor marker
                    document.execCommand('delete', false, null);
                }
            }
    
            // Call callbacks here
            for(var i = 0; i < afterFocus.length; i++) {
                afterFocus[i]();
            }
            afterFocus = [];
    
            // Register selection again
            captureSelection();
        }, 10);
    };
    
  • 95

    This solution works in all major browsers:

    saveSelection() 附加到div的 onmouseuponkeyup 事件,并将选择保存到变量 savedRange .

    restoreSelection() 附加到div的 onfocus 事件,并重新选择 savedRange 中保存的选择 .

    这非常有效,除非您希望在用户单击div时恢复选择(这与通常您希望光标移动到您单击的位置但包含完整性的代码一样有点不直观)

    为实现此目的, onclickonmousedown 事件由函数 cancelEvent() 取消,该函数是用于取消事件的跨浏览器函数 . cancelEvent() 函数还运行 restoreSelection() 函数,因为当click事件被取消时,div不会获得焦点,因此除非运行此函数,否则根本不会选择任何内容 .

    变量 isInFocus 存储它是否处于焦点并且更改为"false" onblur 和"true" onfocus . 这允许仅在div未处于焦点时取消单击事件(否则您根本无法更改选择) .

    如果您希望在通过单击聚焦div时更改选择,而不是恢复选择 onclick (并且仅当使用 document.getElementById("area").focus(); 或类似程序以编程方式给予元素时,只需删除 onclickonmousedown 事件. onblur 在这些情况下,也可以安全地删除事件和 onDivBlur()cancelEvent() 函数 .

    如果要快速测试,如果直接放入html页面的主体,此代码应该有效:

    <div id="area" style="width:300px;height:300px;" onblur="onDivBlur();" onmousedown="return cancelEvent(event);" onclick="return cancelEvent(event);" contentEditable="true" onmouseup="saveSelection();" onkeyup="saveSelection();" onfocus="restoreSelection();"></div>
    <script type="text/javascript">
    var savedRange,isInFocus;
    function saveSelection()
    {
        if(window.getSelection)//non IE Browsers
        {
            savedRange = window.getSelection().getRangeAt(0);
        }
        else if(document.selection)//IE
        { 
            savedRange = document.selection.createRange();  
        } 
    }
    
    function restoreSelection()
    {
        isInFocus = true;
        document.getElementById("area").focus();
        if (savedRange != null) {
            if (window.getSelection)//non IE and there is already a selection
            {
                var s = window.getSelection();
                if (s.rangeCount > 0) 
                    s.removeAllRanges();
                s.addRange(savedRange);
            }
            else if (document.createRange)//non IE and no selection
            {
                window.getSelection().addRange(savedRange);
            }
            else if (document.selection)//IE
            {
                savedRange.select();
            }
        }
    }
    //this part onwards is only needed if you want to restore selection onclick
    var isInFocus = false;
    function onDivBlur()
    {
        isInFocus = false;
    }
    
    function cancelEvent(e)
    {
        if (isInFocus == false && savedRange != null) {
            if (e && e.preventDefault) {
                //alert("FF");
                e.stopPropagation(); // DOM style (return false doesn't always work in FF)
                e.preventDefault();
            }
            else {
                window.event.cancelBubble = true;//IE stopPropagation
            }
            restoreSelection();
            return false; // false = IE style
        }
    }
    </script>
    
  • 56

    Update

    我编写了一个名为Rangy的跨浏览器范围和选择库,其中包含我在下面发布的代码的改进版本 . 你可以使用selection save and restore module来解决这个问题,尽管如果你需要大量的库,我会想要使用像_989881这样的东西 .

    Previous answer

    您可以使用IERange(http://code.google.com/p/ierange/)将IE的TextRange转换为类似DOM范围的内容,并将其与eyelidlessness的起点结合使用 . 就个人而言,我只会使用IERange中执行Range < - > TextRange转换的算法,而不是使用整个事物 . IE的选择对象没有focusNode和anchorNode属性,但您应该只能使用从选择中获取的Range / TextRange .
    我可能会把一些东西放在一起做这件事,如果我这样做,我会在这里回复 . 编辑:我已经创建了一个执行此操作的脚本演示 . 它适用于我迄今为止尝试过的所有内容,除了Opera 9中的一个错误,我还没来得及查看 . 它适用的浏览器是IE 5.5,6和7,Chrome 2,Firefox 2,3和3.5,以及Safari 4,所有这些都在Windows上 . http://www.timdown.co.uk/code/selections/请注意,选择可以在浏览器中向后进行,以便焦点节点位于选择的开始处,点击右或左光标键会将插入符号移动到相对于选择开始的位置 . 我不认为在恢复选择时可以复制它,因此焦点节点始终位于选择的末尾 .
    我很快就会写完这篇文章 .

  • 7

    我有一个相关的情况,我特别需要将光标位置设置为一个contenteditable div的END . 我不想使用像Rangy这样的完整的图书馆,而且很多解决方案都太重量级了 .

    最后,我想出了这个简单的jQuery函数,将克拉位置设置为一个可信的div的末尾:

    $.fn.focusEnd = function() {
        $(this).focus();
        var tmp = $('<span />').appendTo($(this)),
            node = tmp.get(0),
            range = null,
            sel = null;
    
        if (document.selection) {
            range = document.body.createTextRange();
            range.moveToElementText(node);
            range.select();
        } else if (window.getSelection) {
            range = document.createRange();
            range.selectNode(node);
            sel = window.getSelection();
            sel.removeAllRanges();
            sel.addRange(range);
        }
        tmp.remove();
        return this;
    }
    

    理论很简单:在可编辑的末尾附加一个 Span ,选择它,然后删除 Span - 让我们在div的末尾添加一个光标 . 您可以调整此解决方案以在任何位置插入 Span ,从而将光标放在特定位置 .

    用法很简单:

    $('#editable').focusEnd();
    

    而已!

  • 0

    我带了尼科Burns的回答是使用jQuery做的:

    • Generic:每个 div contentEditable="true"

    • 更短

    你需要jQuery 1.6或更高版本:

    savedRanges = new Object();
    $('div[contenteditable="true"]').focus(function(){
        var s = window.getSelection();
        var t = $('div[contenteditable="true"]').index(this);
        if (typeof(savedRanges[t]) === "undefined"){
            savedRanges[t]= new Range();
        } else if(s.rangeCount > 0) {
            s.removeAllRanges();
            s.addRange(savedRanges[t]);
        }
    }).bind("mouseup keyup",function(){
        var t = $('div[contenteditable="true"]').index(this);
        savedRanges[t] = window.getSelection().getRangeAt(0);
    }).on("mousedown click",function(e){
        if(!$(this).is(":focus")){
            e.stopPropagation();
            e.preventDefault();
            $(this).focus();
        }
    });
    
    savedRanges = new Object();
    $('div[contenteditable="true"]').focus(function(){
        var s = window.getSelection();
        var t = $('div[contenteditable="true"]').index(this);
        if (typeof(savedRanges[t]) === "undefined"){
            savedRanges[t]= new Range();
        } else if(s.rangeCount > 0) {
            s.removeAllRanges();
            s.addRange(savedRanges[t]);
        }
    }).bind("mouseup keyup",function(){
        var t = $('div[contenteditable="true"]').index(this);
        savedRanges[t] = window.getSelection().getRangeAt(0);
    }).on("mousedown click",function(e){
        if(!$(this).is(":focus")){
            e.stopPropagation();
            e.preventDefault();
            $(this).focus();
        }
    });
    
    div[contenteditable] {
        padding: 1em;
        font-family: Arial;
        outline: 1px solid rgba(0,0,0,0.5);
    }
    
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <div contentEditable="true"></div>
    <div contentEditable="true"></div>
    <div contentEditable="true"></div>
    
  • 15

    在玩完之后我修改了上面的eyelidlessness回答并使其成为一个jQuery插件,所以你可以做其中一个:

    var html = "The quick brown fox";
    $div.html(html);
    
    // Select at the text "quick":
    $div.setContentEditableSelection(4, 5);
    
    // Select at the beginning of the contenteditable div:
    $div.setContentEditableSelection(0);
    
    // Select at the end of the contenteditable div:
    $div.setContentEditableSelection(html.length);
    

    请原谅长代码,但它可以帮助某人:

    $.fn.setContentEditableSelection = function(position, length) {
        if (typeof(length) == "undefined") {
            length = 0;
        }
    
        return this.each(function() {
            var $this = $(this);
            var editable = this;
            var selection;
            var range;
    
            var html = $this.html();
            html = html.substring(0, position) +
                '<a id="cursorStart"></a>' +
                html.substring(position, position + length) +
                '<a id="cursorEnd"></a>' +
                html.substring(position + length, html.length);
            console.log(html);
            $this.html(html);
    
            // Populates selection and range variables
            var captureSelection = function(e) {
                // Don't capture selection outside editable region
                var isOrContainsAnchor = false,
                    isOrContainsFocus = false,
                    sel = window.getSelection(),
                    parentAnchor = sel.anchorNode,
                    parentFocus = sel.focusNode;
    
                while (parentAnchor && parentAnchor != document.documentElement) {
                    if (parentAnchor == editable) {
                        isOrContainsAnchor = true;
                    }
                    parentAnchor = parentAnchor.parentNode;
                }
    
                while (parentFocus && parentFocus != document.documentElement) {
                    if (parentFocus == editable) {
                        isOrContainsFocus = true;
                    }
                    parentFocus = parentFocus.parentNode;
                }
    
                if (!isOrContainsAnchor || !isOrContainsFocus) {
                    return;
                }
    
                selection = window.getSelection();
    
                // Get range (standards)
                if (selection.getRangeAt !== undefined) {
                    range = selection.getRangeAt(0);
    
                    // Get range (Safari 2)
                } else if (
                    document.createRange &&
                    selection.anchorNode &&
                    selection.anchorOffset &&
                    selection.focusNode &&
                    selection.focusOffset
                ) {
                    range = document.createRange();
                    range.setStart(selection.anchorNode, selection.anchorOffset);
                    range.setEnd(selection.focusNode, selection.focusOffset);
                } else {
                    // Failure here, not handled by the rest of the script.
                    // Probably IE or some older browser
                }
            };
    
            // Slight delay will avoid the initial selection
            // (at start or of contents depending on browser) being mistaken
            setTimeout(function() {
                var cursorStart = document.getElementById('cursorStart');
                var cursorEnd = document.getElementById('cursorEnd');
    
                // Don't do anything if user is creating a new selection
                if (editable.className.match(/\sselecting(\s|$)/)) {
                    if (cursorStart) {
                        cursorStart.parentNode.removeChild(cursorStart);
                    }
                    if (cursorEnd) {
                        cursorEnd.parentNode.removeChild(cursorEnd);
                    }
                } else if (cursorStart) {
                    captureSelection();
                    range = document.createRange();
    
                    if (cursorEnd) {
                        range.setStartAfter(cursorStart);
                        range.setEndBefore(cursorEnd);
    
                        // Delete cursor markers
                        cursorStart.parentNode.removeChild(cursorStart);
                        cursorEnd.parentNode.removeChild(cursorEnd);
    
                        // Select range
                        selection.removeAllRanges();
                        selection.addRange(range);
                    } else {
                        range.selectNode(cursorStart);
    
                        // Select range
                        selection.removeAllRanges();
                        selection.addRange(range);
    
                        // Delete cursor marker
                        document.execCommand('delete', false, null);
                    }
                }
    
                // Register selection again
                captureSelection();
            }, 10);
        });
    };
    
  • 1

    您可以利用现代浏览器支持的selectNodeContents .

    var el = document.getElementById('idOfYoursContentEditable');
    var selection = window.getSelection();
    var range = document.createRange();
    selection.removeAllRanges();
    range.selectNodeContents(el);
    range.collapse(false);
    selection.addRange(range);
    el.focus();
    
  • 4

    在Firefox中,您可能在子节点中具有div的文本( o_div.childNodes[0]

    var range = document.createRange();
    
    range.setStart(o_div.childNodes[0],last_caret_pos);
    range.setEnd(o_div.childNodes[0],last_caret_pos);
    range.collapse(false);
    
    var sel = window.getSelection(); 
    sel.removeAllRanges();
    sel.addRange(range);
    

相关问题