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


142

我正在寻找一种确定的跨浏览器解决方案,当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(如何将光标移动到contenteditable实体的末尾


我不知道contentEditable在非IE浏览器中使用过o_o
aditya,2009年

10
是的,它确实有aditya。
GONeale

5
我认为aditya,Safari 2以上版本,Firefox 3以上版本。
眼睑滑落

尝试在div上设置tabindex =“ 0”。这应该使其在大多数浏览器中都具有针对性。
Tokimon 2010年

Answers:


58

这与基于标准的浏览器兼容,但在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);
};

谢谢,我尝试了您的解决方案,我有点着急,但是在将其连接好之后,它仅将“-”位置放置在最后一个焦点上(这似乎是调试标记?),那是我们失败的时候焦点,当我单击时似乎无法恢复光标/尖号(至少不是在Chrome中,我会尝试FF),它只是跳到div的末尾。因此,我将接受Nico的解决方案,因为我知道该解决方案在所有浏览器中都兼容,并且可以很好地工作。非常感谢您的努力。
GONeale 2010年

3
在进一步检查了您和Nico的信息后,您是否知道忘记了我的最后答复,这不是我在描述中要求的,而是我更喜欢并且会意识到我需要的。您可以像正常文本框一样,将激活焦点时的单击光标位置正确地设置回<div>。将焦点恢复到最后一点不足以创建一个用户友好的输入字段。我会给你积分。
GONeale

9
很棒!这是上述解决方案的jsfiddle
vaughan

4
感谢OP发布真正的JavaScript,即使OP退出并希望使用框架。
约翰

cursorStart.appendChild(document.createTextNode('\u0002'));是我们认为的合理替代品。为-字符。感谢您的代码
twobob

97

该解决方案可在所有主要浏览器中使用:

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

restoreSelection()附加到onfocusdiv事件并重新选择保存在中的选择savedRange

除非您希望在用户单击div时也恢复选择(除非您通常希望光标移到您单击的位置,但包括了完整性的代码),否则这样做并不完美。

为了实现这一点,通过事件可以取消onclickonmousedown事件,该函数cancelEvent()是一个跨浏览器函数,用于取消事件。该cancelEvent()函数还会运行该restoreSelection()函数,因为取消click事件后,div不会获得焦点,因此除非运行该函数,否则根本不会选择任何内容。

该变量isInFocus存储它是否处于焦点,并更改为“ false” onblur和“ true” onfocus。这样,仅当div不在焦点上时,才能取消单击事件(否则您将根本无法更改选择)。

如果您希望在单击时将div聚焦时更改选择,而不是恢复选择onclick(并且仅当使用document.getElementById("area").focus();或类似方式以编程方式将焦点聚焦到元素时,只需删除onclickand onmousedown事件。event onbluronDivBlur()and 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>

1
谢谢,这实际上有效!经过IE,Chrome和FF最新测试。很抱歉
收到

不会if (window.getSelection)...只有当浏览器支持测试getSelection,不在于是否有选择?
桑迪·吉福德

@桑迪是的。这部分代码决定是使用标准getSelectionapi还是document.selection使用旧版IE使用的旧版api。如果没有选择,将在恢复功能中检查是否选择了以后的getRangeAt (0)调用null
尼克·伯恩斯

@NicoBurns正确,但是第二个条件块(else if (document.createRange))中的代码正是我正在查看的。如果它只能被称为window.getSelection是不存在的,但用途window.getSelection
桑迪·吉福德

@NicoBurns此外,我认为您不会找到具有window.getSelection但没有的浏览器document.createRange-意味着将永远不会使用第二个块……
Sandy Gifford

19

更新资料

我已经编写了一个跨浏览器范围和选择库,称为Rangy,其中包含了我在下面发布的代码的改进版本。您可以将选择保存和还原模块用于该特定问题,但是如果您不对项目中的选择做其他任何事情并且不需要大量的内容,尽管我很想使用@Nico Burns的答案,图书馆。

上一个答案

您可以使用IERange(http://code.google.com/p/ierange/)将IE的TextRange转换为类似DOM Range的东西,并将其与类似无眼点的起点结合使用。我个人只使用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/

请注意,可以在浏览器中向后进行选择,以便焦点节点位于选择的开始,并且单击向右或向左光标键会将插入号移动到相对于选择开始的位置。我认为在还原选择时不可能复制此内容,因此焦点节点始终位于选择的末尾。

我很快会写完整。


15

我有一个相关的情况,我特别需要将光标位置设置为一个内容可编辑div的END。我不想使用像Rangy这样的功能强大的库,而且许多解决方案都过于繁重。

最后,我想出了这个简单的jQuery函数,将克拉位置设置为contenteditable 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;
}

理论很简单:在可编辑内容的末尾附加一个跨度,选择它,然后删除该跨度-在div的末尾留下一个光标。您可以调整此解决方案以在需要的任何位置插入跨度,从而将光标置于特定位置。

用法很简单:

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

而已!


3
您不需要插入<span>,这会偶然破坏浏览器的内置撤消堆栈。请参阅stackoverflow.com/a/4238971/96100
Tim Down

6

我接受了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();
    }
});


@salivan我知道更新它已经晚了,但是我认为它现在可以工作。基本上,我添加了一个新条件,从使用元素的id更改为元素的索引,它应该始终存在:)
Gatsbimantico 2015年

4

玩转之后,我已经修改了上面的“双眼失明”答案,并使其成为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);
    });
};

3

您可以利用现代浏览器支持的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();

是否可以修改此代码以使最终用户仍然能够将插入符号移动到他们想要的任何位置?
Zabs

是。您应该在范围对象上使用setStart和setEnd方法。developer.mozilla.org/en-US/docs/Web/API/Range/setStart
zoonman,

0

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

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);
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.