ContentEditable कैरेट इंडेक्स स्थिति प्राप्त करें


119

मैं एक contentEditableतत्व में कर्सर या कैरेट इंडेक्स स्थिति को सेट करने के तरीके पर बहुत सारे अच्छे, क्रॉसबोवर एवरर्स ढूंढ रहा हूं , लेकिन कोई भी नहीं कि कैसे जीईटी प्राप्त करें या इसका इंडेक्स ढूंढें ...

मैं क्या करना चाहता हूं, इस div के भीतर कैरेट का सूचकांक पता है keyup

इसलिए, जब उपयोगकर्ता पाठ टाइप कर रहा है, तो मैं किसी भी बिंदु पर contentEditableतत्व के भीतर इसके कर्सर के सूचकांक को जान सकता हूं ।

संपादित करें: मैं div सामग्री (पाठ) के भीतर INDEX की तलाश कर रहा हूं , न कि कर्सर समन्वय का।

<div id="contentBox" contentEditable="true"></div>

$('#contentbox').keyup(function() { 
    // ... ? 
});

पाठ में इसकी स्थिति को देखें। फिर, उस स्थिति से पहले '@' के अंतिम विचलन को देखें। तो बस कुछ टेक्स्ट लॉजिक।
बर्टवन

इसके अलावा, मैं <diV> के भीतर अन्य टैग की अनुमति देने की योजना नहीं बना रहा हूं, केवल पाठ
बर्टवान

ठीक है, हाँ मैं कर रहा हूँ <div> के भीतर अन्य टैग की जरूरत के लिए जा रहा। इसमें <a> टैग होंगे, लेकिन कोई घोंसला नहीं होगा ...
बर्टवन

@ बर्टवन: अगर कैरेट के अंदर एक <a>तत्व है <div>, तो आप क्या ऑफसेट चाहते हैं? अंदर पाठ के भीतर ऑफसेट <a>?
टिम डाउन

यह कभी भी <a> तत्व के अंदर नहीं होना चाहिए। <a> तत्व को HTML प्रदान किया जाना चाहिए, इसलिए उपयोगकर्ता वास्तव में कैरेट को वहां नहीं रख सकता है।
बर्टवन

जवाबों:


121

निम्नलिखित कोड मानता है:

  • संपादन योग्य <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>


9
अगर वहाँ कोई अन्य टैग नहीं है तो यह काम नहीं करेगा। प्रश्न: अगर कैरेट के अंदर एक <a>तत्व है <div>, तो आप क्या ऑफसेट चाहते हैं? अंदर पाठ के भीतर ऑफसेट <a>?
टिम डाउन

3
@ रीचर्ड: खैर, keyupइसके लिए गलत घटना होने की संभावना है लेकिन मूल प्रश्न में इसका उपयोग किया गया था। getCaretPosition()अपनी सीमाओं के भीतर ही ठीक है।
टिम डाउन

3
वह JSFIDDLE डेमो विफल हो जाता है अगर मैं एंटर दबाता हूं और एक नई लाइन पर जाता हूं। स्थिति 0. दिखाएगा
giorgio79

5
@ जियोर्जियो79: हां, क्योंकि लाइन ब्रेक एक <br>या <div>तत्व उत्पन्न करता है, जो उत्तर में उल्लिखित पहली धारणा का उल्लंघन करता है। यदि आपको थोड़ा और सामान्य समाधान चाहिए, तो आप stackoverflow.com/a/4812022/96100
टिम डाउन

2
क्या ऐसा करने के लिए कुछ भी है ताकि इसमें लाइन नंबर शामिल हो?
अदिति

28

कुछ झुर्रियाँ जिन्हें मैं अन्य उत्तरों में संबोधित नहीं करता हूँ:

  1. तत्व में कई स्तर के बच्चे नोड्स हो सकते हैं (जैसे चाइल्ड नोड्स जिनमें चाइल्ड नोड्स होते हैं, जिनमें चाइल्ड नोड्स होते हैं ...)
  2. एक चयन में अलग-अलग आरंभ और अंत वाले पद शामिल हो सकते हैं (जैसे कई वर्ण चुने गए हैं)
  3. कैरेट स्टार्ट / एंड वाले नोड में तत्व या उसके सीधे बच्चे नहीं हो सकते हैं

तत्व के पाठ मान के लिए ऑफ़सेट के रूप में आरंभ और अंत की स्थिति प्राप्त करने का एक तरीका है:

// 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
इसे सही उत्तर के रूप में चुना जाना चाहिए। यह पाठ के अंदर टैग के साथ काम करता है (स्वीकृत प्रतिक्रिया नहीं करता है)
hamboy75

17

$("#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>


3
यह दुर्भाग्य से काम करना बंद कर देता है जैसे ही आप एंट्री मारते हैं और दूसरी लाइन पर शुरू करते हैं (यह 0 पर फिर से शुरू होता है - शायद सीआर / एलएफ से गिनती)।
इयान

यदि आपके पास कुछ बोल्ड और / या इटैलिक शब्द हैं तो यह ठीक से काम नहीं करता है।
user2824371

14

इसे इस्तेमाल करे:

Caret.js टेक्स्ट फ़ील्ड से कार्यवाहक पोस्ट और ऑफ़सेट प्राप्त करें

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

डेमो: http://ichord.github.com/Caret.js


यह प्रिय है। सामग्री के contenteditable liनाम को बदलने के लिए एक बटन पर क्लिक करने पर कैरेट को समाप्त करने के लिए मुझे इस व्यवहार की आवश्यकता थी li
एकिनवरी २६'१

@AndroidDev मैं Caret.js का लेखक नहीं हूं, लेकिन क्या आपने माना है कि सभी प्रमुख ब्राउज़रों के लिए कैरेट की स्थिति कुछ पंक्तियों की तुलना में अधिक जटिल है? क्या आप जानते हैं या एक गैर-फूला हुआ विकल्प बनाया है जिसे आप हमारे साथ साझा कर सकते हैं?
adelriosantiago 17

8

किंडा पार्टी में देर से आते हैं, लेकिन मामले में कोई और संघर्ष कर रहा है। पिछले दो दिनों से मैंने जो कुछ भी खोजा है, उनमें से कोई भी ऐसा नहीं है जो काम करता है, लेकिन मैं एक संक्षिप्त और सुरुचिपूर्ण समाधान के साथ आया हूं जो हमेशा काम करेगा चाहे आपके पास कितने भी नेस्टेड टैग हों:

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अधिक जानकारी के लिए एपीआई की जाँच करें । चीयर्स! :)


1
अगर मेरे पास टैग या किसी भी बच्चे के एचटीएमएल तत्व <div contenteditable> some text here <i>italic text here</i> some other text here <b>bold text here</b> end of text </div> से पहले हर जगह कर्सर iहै div, तो कर्सर की स्थिति 0. से शुरू हो रही है। क्या इस पुनरारंभ गिनती से बचने का कोई तरीका है?
vam

अजीब। मुझे Chrome में वह व्यवहार नहीं मिल रहा है। आप कौन सा ब्राउज़र उपयोग कर रहे हैं?
16

2
ऐसा लगता है कि चयन। सभी ब्राउज़रों पर समर्थन नहीं किया जा सकता है। developer.mozilla.org/en-US/docs/Web/API/Selection
क्रिस सुलिवन

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

यह वास्तव में मेरे लिए काम करता है, मैंने ऊपर दिए गए सभी लोगों की कोशिश की है, उन्होंने नहीं किया।
स्टूडियन

धन्यवाद, लेकिन यह नई लाइन पर {x: 0, y: 0} भी लौटाता है।
हिचकाज़ान

यह पिक्सेल स्थिति देता है, चरित्र ऑफसेट नहीं
4esn0k

धन्यवाद, मैं कैरेट से पिक्सेल स्थिति को पुनः प्राप्त करने के लिए देख रहा था और यह ठीक काम कर रहा है।
समेश

6

window.getSelection - बनाम - 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())

इन दो मामलों पर मैं लक्ष्य सूचकांक जोड़कर ब्रेक लाइनों के लिए देखभाल करता हूं


4
//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)
    }
});

ध्यान दें: रेंज ऑब्जेक्ट अपने स्वयं को एक चर में संग्रहीत किया जा सकता है, और किसी भी समय फिर से चुना जा सकता है जब तक कि कंटेंटेड डिव की सामग्री नहीं बदलती।

IE 8 और निम्न का संदर्भ: http://msdn.microsoft.com/en-us/library/ms535872(VS.85).aspx

मानकों (अन्य सभी) ब्राउज़रों के लिए संदर्भ: https://developer.mozilla.org/en/DOM/range (इसके मोज़िला डॉक्स, लेकिन कोड क्रोम, सफारी, ओपेरा और ie9 में भी काम करता है)


1
धन्यवाद, लेकिन मैं वास्तव में div सामग्री में कैरेट स्थिति का 'सूचकांक' कैसे प्राप्त कर सकता हूं?
बर्टवन

ठीक है, यह कॉलिंग की तरह दिखता है .baseOffset on .getSelection () चाल करता है। तो यह, आपके उत्तर के साथ, मेरे प्रश्न का उत्तर देता है। धन्यवाद!
बर्टवन

2
दुर्भाग्य से .baseOffset केवल वेबकिट में काम करता है (मुझे लगता है)। यह आपको केवल कैरेट के imediate माता-पिता से ऑफ़सेट देता है (यदि आपके पास <div> <div> के अंदर टैग है तो यह <b> के प्रारंभ से ऑफ़सेट देगा, <div> के प्रारंभ से नहीं मानक आधारित पर्वतमाला चयन के मूल नोड से ऑफसेट प्राप्त करने के लिए range.endOffset range.startOffset range.endContainer और range.startContainer का उपयोग कर सकते हैं , और नोड स्वयं (इसमें पाठ नोड्स शामिल हैं)। IE इसमें range.offsetLeft प्रदान करता है जो कि है। पिक्सल में बाईं ओर से ऑफसेट , और इतना बेकार।
निको बर्न्स

रेंज ऑब्जेक्ट को अपने स्वयं को स्टोर करना और window.getSelection ()। Addrange (रेंज) का उपयोग करना सबसे अच्छा है; <- मानक और range.select (); <- उसी स्थान पर कर्सर को पुनः स्थिति के लिए IE। range.insertNode (nodetoinsert); <- मानक और range.pasteHTML (htmlcode); <- IE कर्सर पर टेक्स्ट या html सम्मिलित करने के लिए।
निको बर्न्स

Rangeवस्तु अधिकांश ब्राउज़र और द्वारा दिया TextRangeवस्तु आईई द्वारा दिया, बहुत अलग बातें हैं तो मुझे यकीन है कि इस जवाब को हल करती है ज्यादा नहीं हूँ।
टिम डाउन

3

के रूप में यह मुझे हमेशा के लिए नई window.getSelection एपीआई का उपयोग कर यह पता लगाने के लिए ले लिया कि मैं पोस्टीरिटी के लिए साझा करने जा रहा हूं। ध्यान दें कि MDN का सुझाव है कि 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 है जो कीप पर फायर करता है। हालाँकि, ध्यान दें कि तेजी से दिशात्मक कुंजी प्रेस, साथ ही तेजी से विलोपन घटनाओं को छोड़ दें।


मेरे लिये कार्य करता है! बहुत बहुत धन्यवाद।
dmodo

इससे पाठ का चयन संभव नहीं है क्योंकि यह ढह गया है। संभावित परिदृश्य: हर की-अप घटना पर मूल्यांकन करने की आवश्यकता
hschmieder

0

एक सीधा आगे रास्ता है, कि यह संतोषी div के सभी chidren के माध्यम से iterates जब तक यह endContainer हिट। फिर मैं अंत कंटेनर ऑफसेट जोड़ता हूं और हमारे पास चरित्र सूचकांक है। किसी भी संख्या में घोंसले के साथ काम करना चाहिए। पुनरावृत्ति का उपयोग करता है।

नोट: समर्थन के लिए यानी के लिए एक पाली भरने की आवश्यकता है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;
    }
  }
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.