【问题标题】:Set cursor position on contentEditable <div>在 contentEditable <div> 上设置光标位置
【发布时间】:2010-11-13 23:31:18
【问题描述】:

当 contentEditable='on'

重新获得焦点时,我正在寻求一个明确的跨浏览器解决方案,将光标/插入符号位置设置为最后一个已知位置。似乎内容可编辑 div 的默认功能是每次单击时将插入符号/光标移动到 div 中文本的开头,这是不可取的。

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

如果有人有任何想法、工作代码 sn-ps 或示例,我很乐意看到它们。

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

<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。我已经尝试过这个资源,但它似乎不适用于

。也许只适用于 textarea (How to move cursor to end of contenteditable entity)

【问题讨论】:

  • 我不知道 contentEditable 在非 IE 浏览器中工作过 o_o
  • 是的 aditya。
  • aditya, Safari 2+, Firefox 3+ 我想。
  • 尝试在 div 上设置 tabindex="0"。这应该使它在大多数浏览器中都具有焦点。

标签: javascript jquery html contenteditable cursor-position


【解决方案1】:

这与基于标准的浏览器兼容,但在 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 的解决方案,因为我知道它在所有浏览器中都兼容,并且往往运行良好。非常感谢您的努力。
  • 你知道吗,忘记我最后的回答,在进一步检查了你和 Nico 的之后,你的不是我在描述中要求的,而是我更喜欢并且会意识到我需要的。您的正确设置将焦点激活回
    时单击的光标位置,就像常规文本框一样。将焦点恢复到最后一点不足以创建一个用户友好的输入字段。我会给你积分。
  • 效果很好!这是上述解决方案的一个jsfiddle:jsfiddle.net/s5xAr/3
  • 感谢您发布真正的 JavaScript,尽管 OP 失败并想使用框架。
  • cursorStart.appendChild(document.createTextNode('\u0002')); 是我们认为的合理替代品。对于 - 字符。感谢您的代码
【解决方案2】:

更新

我编写了一个名为Rangy 的跨浏览器范围和选择库,其中包含我在下面发布的代码的改进版本。对于这个特定问题,您可以使用selection save and restore module,尽管如果您没有对项目中的选择做任何其他事情并且不需要大量的库,我会很想使用@Nico Burns's answer 之类的东西。

上一个答案

您可以使用IERange (http://code.google.com/p/ierange/) 将IE 的TextRange 转换为DOM Range 之类的东西,并与eyelidlessness 的起点之类的东西结合使用。就我个人而言,我只会使用来自 IERange 的算法来进行 Range TextRange 转换,而不是使用整个东西。并且 IE 的 selection 对象没有 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/

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

我会在不久的将来完整地写出来。

【讨论】:

    【解决方案3】:

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

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

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

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

    为了实现这一点,onclickonmousedown 事件被函数cancelEvent() 取消,这是一个用于取消事件的跨浏览器函数。 cancelEvent() 函数也运行 restoreSelection() 函数,因为当点击事件被取消时,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>
    

    【讨论】:

    • 谢谢你这确实有效!在 IE、Chrome 和 FF 最新测试。很抱歉超级延迟回复=)
    • 不会if (window.getSelection)...只测试浏览器是否支持getSelection,而不是是否有选择?
    • @Sandy 是的。这部分代码决定是使用标准的getSelection api 还是旧版本IE 使用的遗留document.selection api。如果没有选择,后面的getRangeAt (0) 调用将返回null,这在还原函数中进行了检查。
    • @NicoBurns 对,但我正在查看第二个条件块 (else if (document.createRange)) 中的代码。仅当window.getSelection 不存在但使用window.getSelection 时才会调用它
    • @NicoBurns 此外,我认为您不会找到带有 window.getSelection 而不是 document.createRange 的浏览器 - 这意味着永远不会使用第二个块...
    【解决方案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);
    

    【讨论】:

      【解决方案5】:

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

      就是这样!

      【讨论】:

      【解决方案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();
          }
      });
      

      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>

      【讨论】:

      • @salivan 我知道更新它已经很晚了,但我认为它现在可以工作了。基本上我添加了一个新条件并从使用元素的 id 更改为元素的索引,它应该始终存在:)
      【解决方案7】:

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

      【讨论】:

        【解决方案8】:

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

        【讨论】:

        猜你喜欢
        相关资源
        最近更新 更多
        热门标签