Get contentEditable caret index position

后端 未结 10 693
暗喜
暗喜 2020-11-22 05:48

I\'m finding tons of good, crossbrowser anwers on how to SET the cursor or caret index position in a contentEditable element, but none on how to GET or find its

相关标签:
10条回答
  • 2020-11-22 06:30
    //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)
        }
    });
    

    Note: the range object its self can be stored in a variable, and can be re-selected at any time unless the contents of the contenteditable div change.

    Reference for IE 8 and lower: http://msdn.microsoft.com/en-us/library/ms535872(VS.85).aspx

    Reference for standards (all other) browsers: https://developer.mozilla.org/en/DOM/range (its the mozilla docs, but code works in chrome, safari, opera and ie9 too)

    0 讨论(0)
  • 2020-11-22 06:33

    As this took me forever to figure out using the new window.getSelection API I am going to share for posterity. Note that MDN suggests there is wider support for window.getSelection, however, your mileage may vary.

    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,
        }
    } 
    

    Here is a jsfiddle that fires on keyup. Note however, that rapid directional key presses, as well as rapid deletion seems to be skip events.

    0 讨论(0)
  • 2020-11-22 06:35

    A few wrinkles that I don't see being addressed in other answers:

    1. the element can contain multiple levels of child nodes (e.g. child nodes that have child nodes that have child nodes...)
    2. a selection can consist of different start and end positions (e.g. multiple chars are selected)
    3. the node containing a Caret start/end may not be either the element or its direct children

    Here's a way to get start and end positions as offsets to the element's textContent value:

    // 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]];
    }
    
    0 讨论(0)
  • 2020-11-22 06:38

    window.getSelection - vs - document.selection

    This one works for me:

    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>

    The calling line depends on event type, for key event use this:

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

    for mouse event use this:

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

    on these two cases I take care for break lines by adding the target index

    0 讨论(0)
提交回复
热议问题