代码之家  ›  专栏  ›  技术社区  ›  Bertvan

在contentEditable div中获取插入符号位置

  •  92
  • Bertvan  · 技术社区  · 15 年前

    我发现了很多很好的,crossbrowser anwers,关于如何在 contentEditable

    我想做的是知道这个div中插入符号的索引,on keyup

    所以,当用户输入文本时,我可以随时知道其光标在 元素。

    编辑:我在找 在div内容(文本)中,不是光标坐标。

    <div id="contentBox" contentEditable="true"></div>
    
    $('#contentbox').keyup(function() { 
        // ... ? 
    });
    
    10 回复  |  直到 6 年前
        1
  •  115
  •   Tim Down    6 年前

    以下代码假定:

    • <div> 没有其他节点
    • 可编辑div没有CSS white-space 属性设置为 pre

    如果需要更通用的方法来处理嵌套元素的内容,请尝试以下答案:

    https://stackoverflow.com/a/4812022/96100

    代码:

    function getCaretPosition(editableDiv) {
      var caretPos = 0,
        sel, range;
      if (window.getSelection) {
        sel = window.getSelection();
        if (sel.rangeCount) {
          range = sel.getRangeAt(0);
          if (range.commonAncestorContainer.parentNode == editableDiv) {
            caretPos = range.endOffset;
          }
        }
      } else if (document.selection && document.selection.createRange) {
        range = document.selection.createRange();
        if (range.parentElement() == editableDiv) {
          var tempEl = document.createElement("span");
          editableDiv.insertBefore(tempEl, editableDiv.firstChild);
          var tempRange = range.duplicate();
          tempRange.moveToElementText(tempEl);
          tempRange.setEndPoint("EndToEnd", range);
          caretPos = tempRange.text.length;
        }
      }
      return caretPos;
    }
    #caretposition {
      font-weight: bold;
    }
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
    <div id="contentbox" contenteditable="true">Click me and move cursor with keys or mouse</div>
    <div id="caretposition">0</div>
    <script>
      var update = function() {
        $('#caretposition').html(getCaretPosition(this));
      };
      $('#contentbox').on("mousedown mouseup keydown keyup", update);
    </script>
        2
  •  26
  •   mwag    6 年前

    一些我在其他答案中看不到的皱纹:

    1. 元素可以包含多个级别的子节点(例如,具有子节点的子节点具有子节点…)
    2. 选择可以由不同的开始和结束位置组成(例如,选择了多个字符)

    有一种方法可以将开始和结束位置作为元素textContent值的偏移:

    // node_walk: walk the element tree, stop when func(node) returns false
    function node_walk(node, func) {
      var result = func(node);
      for(node = node.firstChild; result !== false && node; node = node.nextSibling)
        result = node_walk(node, func);
      return result;
    };
    
    // getCaretPosition: return [start, end] as offsets to elem.textContent that
    //   correspond to the selected portion of text
    //   (if start == end, caret is at given position and no text is selected)
    function getCaretPosition(elem) {
      var sel = window.getSelection();
      var cum_length = [0, 0];
    
      if(sel.anchorNode == elem)
        cum_length = [sel.anchorOffset, sel.extentOffset];
      else {
        var nodes_to_find = [sel.anchorNode, sel.extentNode];
        if(!elem.contains(sel.anchorNode) || !elem.contains(sel.extentNode))
          return undefined;
        else {
          var found = [0,0];
          var i;
          node_walk(elem, function(node) {
            for(i = 0; i < 2; i++) {
              if(node == nodes_to_find[i]) {
                found[i] = true;
                if(found[i == 0 ? 1 : 0])
                  return false; // all done
              }
            }
    
            if(node.textContent && !node.firstChild) {
              for(i = 0; i < 2; i++) {
                if(!found[i])
                  cum_length[i] += node.textContent.length;
              }
            }
          });
          cum_length[0] += sel.anchorOffset;
          cum_length[1] += sel.extentOffset;
        }
      }
      if(cum_length[0] <= cum_length[1])
        return cum_length;
      return [cum_length[1], cum_length[0]];
    }
    
        3
  •  17
  •   h8n    9 年前

    $("#editable").on('keydown keyup mousedown mouseup',function(e){
    		   
           if($(window.getSelection().anchorNode).is($(this))){
        	  $('#position').html('0')
           }else{
             $('#position').html(window.getSelection().anchorOffset);
           }
     });
    body{
      padding:40px;
    }
    #editable{
      height:50px;
      width:400px;
      border:1px solid #000;
    }
    #editable p{
      margin:0;
      padding:0;
    }
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.1/jquery.min.js"></script>
    <div contenteditable="true" id="editable">move the cursor to see position</div>
    <div>
    position : <span id="position"></span>
    </div>
        4
  •  13
  •   J.Y Han    11 年前

    插入符号.js 从文本字段获取插入符号位置和偏移量

    https://github.com/ichord/Caret.js

    演示: http://ichord.github.com/Caret.js

        5
  •  7
  •   vsync    5 年前

    function cursor_position() {
        var sel = document.getSelection();
        sel.modify("extend", "backward", "paragraphboundary");
        var pos = sel.toString().length;
        if(sel.anchorNode != undefined) sel.collapseToEnd();
    
        return pos;
    }
    
    // Demo:
    var elm = document.querySelector('[contenteditable]');
    elm.addEventListener('click', printCaretPosition)
    elm.addEventListener('keydown', printCaretPosition)
    
    function printCaretPosition(){
      console.log( cursor_position(), 'length:', this.textContent.trim().length )
    }
    <div contenteditable>some text here <i>italic text here</i> some other text here <b>bold text here</b> end of text</div>

    它会一直选择到段落的开头,然后计算字符串的长度以获得当前位置,然后撤消选择以将光标返回到当前位置。如果要对整个文档(多个段落)执行此操作,请更改 paragraphboundary documentboundary more details . 干杯!:)

        6
  •  6
  •   Tom    9 年前
    function getCaretPosition() {
        var x = 0;
        var y = 0;
        var sel = window.getSelection();
        if(sel.rangeCount) {
            var range = sel.getRangeAt(0).cloneRange();
            if(range.getClientRects()) {
            range.collapse(true);
            var rect = range.getClientRects()[0];
            if(rect) {
                y = rect.top;
                x = rect.left;
            }
            }
        }
        return {
            x: x,
            y: y
        };
    }
    
        7
  •  5
  •   vsync    5 年前

    window.getSelection - vs - document.selection

    这个对我有用:

    function getCaretCharOffset(element) {
      var caretOffset = 0;
    
      if (window.getSelection) {
        var range = window.getSelection().getRangeAt(0);
        var preCaretRange = range.cloneRange();
        preCaretRange.selectNodeContents(element);
        preCaretRange.setEnd(range.endContainer, range.endOffset);
        caretOffset = preCaretRange.toString().length;
      } 
    
      else if (document.selection && document.selection.type != "Control") {
        var textRange = document.selection.createRange();
        var preCaretTextRange = document.body.createTextRange();
        preCaretTextRange.moveToElementText(element);
        preCaretTextRange.setEndPoint("EndToEnd", textRange);
        caretOffset = preCaretTextRange.text.length;
      }
    
      return caretOffset;
    }
    
    
    // Demo:
    var elm = document.querySelector('[contenteditable]');
    elm.addEventListener('click', printCaretPosition)
    elm.addEventListener('keydown', printCaretPosition)
    
    function printCaretPosition(){
      console.log( getCaretCharOffset(elm), 'length:', this.textContent.trim().length )
    }
    <div contenteditable>some text here <i>italic text here</i> some other text here <b>bold text here</b> end of text</div>

    调用行取决于事件类型,对于关键事件,请使用以下命令:

    getCaretCharOffsetInDiv(e.target) + ($(window.getSelection().getRangeAt(0).startContainer.parentNode).index());
    

    getCaretCharOffsetInDiv(e.target.parentElement) + ($(e.target).index())
    

    在这两种情况下,我通过添加目标索引来处理换行

        8
  •  4
  •   Nico Burns    15 年前
    //global savedrange variable to store text range in
    var savedrange = null;
    
    function getSelection()
    {
        var savedRange;
        if(window.getSelection && window.getSelection().rangeCount > 0) //FF,Chrome,Opera,Safari,IE9+
        {
            savedRange = window.getSelection().getRangeAt(0).cloneRange();
        }
        else if(document.selection)//IE 8 and lower
        { 
            savedRange = document.selection.createRange();
        }
        return savedRange;
    }
    
    $('#contentbox').keyup(function() { 
        var currentRange = getSelection();
        if(window.getSelection)
        {
            //do stuff with standards based object
        }
        else if(document.selection)
        { 
            //do stuff with microsoft object (ie8 and lower)
        }
    });
    

    注意:range对象本身可以存储在变量中,并且可以随时重新选择,除非contenteditable div的内容发生更改。

    IE 8和更低版本的参考: http://msdn.microsoft.com/en-us/library/ms535872(VS.85).aspx

    https://developer.mozilla.org/en/DOM/range (它是mozilla文档,但代码也可以在chrome、safari、opera和ie9中工作)

        9
  •  3
  •   Chris Sullivan    5 年前

    因为我花了很长时间才发现 window.getSelection

    const getSelectionCaretAndLine = () => {
        // our editable div
        const editable = document.getElementById('editable');
    
        // collapse selection to end
        window.getSelection().collapseToEnd();
    
        const sel = window.getSelection();
        const range = sel.getRangeAt(0);
    
        // get anchor node if startContainer parent is editable
        let selectedNode = editable === range.startContainer.parentNode
          ? sel.anchorNode 
          : range.startContainer.parentNode;
    
        if (!selectedNode) {
            return {
                caret: -1,
                line: -1,
            };
        }
    
        // select to top of editable
        range.setStart(editable.firstChild, 0);
    
        // do not use 'this' sel anymore since the selection has changed
        const content = window.getSelection().toString();
        const text = JSON.stringify(content);
        const lines = (text.match(/\\n/g) || []).length + 1;
    
        // clear selection
        window.getSelection().collapseToEnd();
    
        // minus 2 because of strange text formatting
        return {
            caret: text.length - 2, 
            line: lines,
        }
    } 
    

    这是一个 jsfiddle 钥匙上的火。但是请注意,快速方向键按下以及快速删除似乎是跳过事件。

        10
  •  0
  •   alockwood05    6 年前

    一种直截了当的方法,它遍历contenteditable div的所有子对象,直到它到达endContainer。然后我加上结束容器偏移量,我们得到了字符索引。应该可以使用任意数量的嵌套。使用递归。

    poly fill 为ie提供支持 Element.closest('div[contenteditable]')

    https://codepen.io/alockwood05/pen/vMpdmZ

    function caretPositionIndex() {
        const range = window.getSelection().getRangeAt(0);
        const { endContainer, endOffset } = range;
    
        // get contenteditableDiv from our endContainer node
        let contenteditableDiv;
        const contenteditableSelector = "div[contenteditable]";
        switch (endContainer.nodeType) {
          case Node.TEXT_NODE:
            contenteditableDiv = endContainer.parentElement.closest(contenteditableSelector);
            break;
          case Node.ELEMENT_NODE:
            contenteditableDiv = endContainer.closest(contenteditableSelector);
            break;
        }
        if (!contenteditableDiv) return '';
    
    
        const countBeforeEnd = countUntilEndContainer(contenteditableDiv, endContainer);
        if (countBeforeEnd.error ) return null;
        return countBeforeEnd.count + endOffset;
    
        function countUntilEndContainer(parent, endNode, countingState = {count: 0}) {
          for (let node of parent.childNodes) {
            if (countingState.done) break;
            if (node === endNode) {
              countingState.done = true;
              return countingState;
            }
            if (node.nodeType === Node.TEXT_NODE) {
              countingState.count += node.length;
            } else if (node.nodeType === Node.ELEMENT_NODE) {
              countUntilEndContainer(node, endNode, countingState);
            } else {
              countingState.error = true;
            }
          }
          return countingState;
        }
      }