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

当一个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实体的结尾 )

这与基于标准的浏览器兼容,但可能会在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的选择。

这完美的作品,除非你想要选择恢复当用户点击分区也(这是有点不够用,因为通常你希望光标去你所点击的位置,但代码包括为了完整性)

为了实现这一点, onclickonmousedown事件被取消事件的函数cancelEvent()取消。 cancelEvent()函数也运行restoreSelection()函数,因为当点击事件被取消时,div不会收到焦点,因此除非这个函数运行,否则什么都不会被选中。

变量isInFocus存储它是否处于焦点状态,并在焦点上变为“false” onblur和“true”。 这样,只有当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> 

更新

我写了一个跨浏览器范围和选择库Rangy ,它包含了我在下面发布的代码的改进版本。 您可以使用选择保存和恢复模块这个特定的问题,虽然我会试图使用像@Nico伯恩斯的答案,如果你没有做任何其他选择在你的项目,并不需要大量的图书馆。

以前的答案

您可以使用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/

请注意,可以在浏览器中进行选择,以便焦点节点处于选择的开始位置,点击向右或向左的光标键可以将插入符号移动到相对于选择开始的位置。 我不认为有可能在恢复选择时复制它,所以焦点节点总是在选择的最后。

我很快就会写出来。

我有一个相关的情况,我特别需要将光标位置设置为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末尾留下一个光标。 你可以调整这个解决方案来插入任何你想要的跨度,从而把光标放在特定的位置。

用法很简单:

 $('#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://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> 

玩过之后,我已经修改了上面的无瑕疵的答案,并将其作为一个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); }); }; 

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