问题描述

当一个 contentEditable = ‘on'< div> 时,我已经确定了一个 cross-browser 解决方案来设置光标/插入符位置到最后一个已知位置。重新获得关注。出现内容可编辑 div 的默认功能是每次单击时将插入符号/光标移动到 div 中文本的开头,这是不希望的。

我相信当他们离开 div 的焦点时,我必须存储一个变量当前的光标位置,然后 re-set 这个当他们有焦点在里面,但是我没有能够放在一起,或者找到一个工作的代码示例然而。

如果有人有任何想法,工作代码片段或样本,我将很高兴看到它们。

我真的没有任何代码,但这里是我做的:

<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)

最佳解决方案

这与 standards-based 浏览器兼容,但 IE 可能会失败。我提供它作为起点。 IE 不支持 DOM 范围。

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

次佳解决方案

该解决方案适用于所有主流浏览器:

saveSelection()连接到 div 的 onmouseuponkeyup 事件,并将选择保存到变量 savedRange

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

这样做完美,除非您希望在用户单击 div 时恢复选择 (通常您希望光标到达您点击的位置,但包含的代码完整性),这是非常不寻常的)

为了达到这个目的,onclickonmousedown 事件被 cancelEvent()功能取消,cancelEvent()是一种跨浏览器功能来取消事件。 cancelEvent()功能也运行 restoreSelection()功能,因为当单击事件被取消时,div 不会接收到焦点,因此除非运行此功能,否则不会选择任何内容。

变量 isInFocus 存储是否对焦,并更改为”false” onblur 和”true” onfocus 。这样,只有当 div 不在焦点时才能取消点击事件 (否则根本无法更改选择) 。

如果您希望在 div 被焦点聚焦时进行选择,而不是恢复选择 onclick(并且只有当使用 document.getElementById("area").focus(); 或类似程序使用 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>

第三种解决方案

更新

我编写了一个 cross-browser 系列和选择库,名为 Rangy,其中包含了我在下面发布的代码的改进版本。您可以将 selection save and restore module 用于此特定问题,尽管如果您在项目中没有选择任何其他选项,并且不需要大量的库,我也会尝试使用像 @Nico Burns’s answer 这样的东西。

以前的答案

您可以使用 IERange(http://code.google.com/p/ierange/) 将 IE 的 TextRange 转换为 DOM 范围,并将其与眼睑的起点结合使用。就我个人而言,我将只使用来自 IERange 的算法进行 Range< – > TextRange 转换而不是使用整个事情。而 IE 的选择对象没有 focusNode 和 anchorNode 属性,但是您应该只能使用从选择中获取的 Range /TextRange 。

我可以把东西放在一起去做,如果我做的话会发贴到这里。

编辑:

我已经创建了一个脚本的演示,这样做。除了 Opera 9 中的一个 bug,我还没有时间研究之外,它在我尝试过的所有内容中起作用。它的工作原理是 IE 5.5,6 和 7,Chrome 2,Firefox 2,3 和 3.5 以及 Safari 4,都在 Windows 上。

http://www.timdown.co.uk/code/selections/

请注意,可以在浏览器中进行选择,以使焦点节点处于选择开始,并且击中右光标键或左光标键将将插入符号移动到相对于选择开始的位置。我不认为在还原选择时可以复制它,所以焦点节点始终处于选择结束。

我会在稍后写完这个。

第四种方案

我有一个相关的情况,我特别需要将光标位置设置为可内容 div 的 END 。我不想使用像 Rangy 这样一个完整的 Library ,许多解决方案太重了。

最后,我想出了这个简单的 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 的末尾留下一个光标。您可以调整此解决方案以将任何位置插入到任何位置,从而将光标放在特定位置。

用法很简单:

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

而已!

第五种方案

我拿了 Nico Burns 的答案,并使用了 jQuery:

  • 通用型:适用于每个 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://googleajax.admincdn.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div contentEditable="true"></div>
<div contentEditable="true"></div>
<div contentEditable="true"></div>

第六种方案

玩过之后,我修改了上面的 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);
    });
};

参考文献

注:本文内容整合自 Google/Baidu/Bing 辅助翻译的英文资料结果。如果您对结果不满意,可以加入我们改善翻译效果:薇晓朵技术论坛