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

IT技术 javascript jquery html contenteditable cursor-position
2021-02-06 05:05:56

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

附注。我已经尝试过这个资源,但它似乎不适用于 <div>。也许仅适用于 textarea(如何将光标移动到 contenteditable 实体的末尾

6个回答

此解决方案适用于所有主要浏览器:

saveSelection()附加到divonmouseuponkeyup事件并将选择保存到变量savedRange

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

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

为了实现这一点onclickonmousedown事件被取消的函数cancelEvent()是一个跨浏览器的函数来取消事件。cancelEvent()函数还运行该restoreSelection()函数,因为当单击事件被取消时,div 不会获得焦点,因此除非运行此函数,否则根本不会选择任何内容。

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

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

如果您想快速测试此代码,如果将其直接放入 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>
@NicoBurns 是对的,但第二个条件块 ( else if (document.createRange)) 中的代码是我正在查看的。它只会在window.getSelection不存在时被调用,但会使用window.getSelection
2021-03-12 05:05:56
@NicoBurns 此外,我认为你不会找到一个浏览器,window.getSelection但不会document.createRange- 这意味着永远不会使用第二个块......
2021-03-15 05:05:56
谢谢,这确实有效!最新在 IE、Chrome 和 FF 中测试。抱歉回复超级延迟 =)
2021-03-16 05:05:56
@Sandy 是的。这部分代码决定是使用标准getSelectionapi还是document.selection旧版本IE使用的遗留api。如果没有选择,后面的getRangeAt (0)调用将返回null,这是在恢复函数中检查的。
2021-03-21 05:05:56
不会if (window.getSelection)...只测试浏览器getSelection是否支持,不测试是否有选择?
2021-04-07 05:05:56

这与基于标准的浏览器兼容,但在 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);
};
感谢您发布真正的 JavaScript,即使 OP 已经过时并想使用框架。
2021-03-20 05:05:56
你知道吗,忘记我最后的回应,在进一步检查你和 Nico 的之后,你的不是我在我的描述中要求的,而是我更喜欢的,并且会意识到我需要。您正确设置了在将焦点激活回 <div> 时单击位置的光标位置,就像常规文本框一样。将焦点恢复到最后一点不足以制作用户友好的输入字段。我会给你加分的。
2021-03-28 05:05:56
谢谢你的解决方案,我尝试了你的解决方案,我有点着急,但是在将其连接起来后,它只将“-”位置放在最后一个焦点(这似乎是一个调试标记?)焦点,当我点击返回时它似乎没有恢复光标/插入符号(至少不是在 Chrome 中,我会尝试 FF),它只是转到 div 的末尾。所以我会接受 Nico 的解决方案,因为我知道它在所有浏览器中都兼容,并且往往运行良好。非常感谢你的努力。
2021-04-02 05:05:56
效果很好!这是上述解决方案的jsfiddlejsfiddle.net/s5xAr/3
2021-04-02 05:05:56
cursorStart.appendChild(document.createTextNode('\u0002'));是我们认为合理的替代品。对于 — 字符。谢谢你的代码
2021-04-06 05:05:56

更新

我编写了一个名为Rangy的跨浏览器范围和选择库,其中包含我在下面发布的代码的改进版本。您可以使用选择保存和恢复模块为这个特定的问题,但我会尝试使用类似@Nico伯恩斯的回答,如果你不这样做,在你的项目中选择别的,不需要大头的图书馆。

上一个答案

您可以使用 IERange ( http://code.google.com/p/ierange/ ) 将 IE 的 TextRange 转换为 DOM Range 之类的内容,并将其与 Eyelidless 的起点等内容结合使用。就我个人而言,我只会使用 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/

请注意,可以在浏览器中向后进行选择,以便焦点节点位于选择的开始处,并且按向右或向左光标键会将插入符号移动到相对于选择开始的位置。我认为在恢复选择时无法复制这一点,因此焦点节点始终位于选择的末尾。

我很快就会把它完整地写下来。

我有一个相关的情况,我特别需要将光标位置设置为 contenteditable 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();

而已!

您不需要插入<span>,这会顺便破坏浏览器的内置撤消堆栈。参见stackoverflow.com/a/4238971/96100
2021-03-27 05:05:56

我接受了 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 更改为元素的索引,它应该始终存在:)
2021-03-23 05:05:56