कैसे संतुष्ट तत्व (div) में कैरेट (कर्सर) स्थिति सेट करने के लिए?


190

मेरे पास उदाहरण के रूप में यह सरल HTML है:

<div id="editable" contenteditable="true">
  text text text<br>
  text text text<br>
  text text text<br>
</div>
<button id="button">focus</button>

मैं साधारण चीज चाहता हूं - जब मैं बटन पर क्लिक करता हूं, तो मैं संपादन योग्य div में कैरेट (कर्सर) को विशिष्ट स्थान पर रखना चाहता हूं। वेब पर खोज करने से, मेरे पास यह जेएस बटन क्लिक से जुड़ा हुआ है, लेकिन यह काम नहीं करता है (एफएफ, क्रोम):

var range = document.createRange();
var myDiv = document.getElementById("editable");
range.setStart(myDiv, 5);
range.setEnd(myDiv, 5);

क्या इस तरह से मैन्युअल रूप से कैरेट स्थिति सेट करना संभव है?

जवाबों:


258

अधिकांश ब्राउज़रों में, आपको ऑब्जेक्ट Rangeऔर Selectionऑब्जेक्ट की आवश्यकता होती है । आप प्रत्येक नोड और उस नोड के भीतर एक ऑफसेट के रूप में चयन सीमाओं को निर्दिष्ट करते हैं। उदाहरण के लिए, कैरेट को दूसरी पंक्ति के पाठ के पांचवें वर्ण में सेट करने के लिए, आप निम्नलिखित कार्य करेंगे:

var el = document.getElementById("editable");
var range = document.createRange();
var sel = window.getSelection();
range.setStart(el.childNodes[2], 5);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);

IE <9 पूरी तरह से अलग तरह से काम करता है। यदि आपको इन ब्राउज़रों का समर्थन करने की आवश्यकता है, तो आपको अलग कोड की आवश्यकता होगी।

jsField उदाहरण: http://jsfiddle.net/timdown/vXnCM/


2
आपका समाधान पूरी तरह से काम करता है। बहुत बहुत धन्यवाद। क्या ऐसा मौका है जिसे "टेक्स्ट संदर्भ" में काम करने के लिए बनाया जा सकता है - इसका मतलब है कि स्थिति # 5 एक स्क्रीन पर पांचवां अक्षर होगा और एक कोड में पांचवां अक्षर नहीं होगा?
फ्रोडिक

3
@ फ़्रोडिक: आप setSelectionRange()मेरे द्वारा लिखे गए उत्तर से फ़ंक्शन का उपयोग कर सकते हैं: stackoverflow.com/questions/6240139/… । जैसा कि मैंने उत्तर में उल्लेख किया है, विभिन्न चीजें हैं जो इसे सही ढंग से / लगातार नहीं संभालेंगी लेकिन यह काफी अच्छी हो सकती हैं।
टिम डाउन

7
कैसे इस बारे में एक स्पैन टैग के अंदर कैरेट सेट करें: << div id = "editable" contenteditable = "true"> test1 <br> test2 <br> <span> </ span> </ div>
मेड अकरम Z

1
@MalcolmOcean: Barf, क्योंकि IE <9 के पास कोई नहीं है document.createRange (या नहीं है window.getSelection, लेकिन यह अभी तक नहीं मिलेगा)।
टिम डाउन

1
@undroid: मैक पर फ़ायरफ़ॉक्स 38.0.5 में jsfiddle मेरे लिए ठीक काम करता है।
टिम डाउन

62

संतोषप्रद कर्सर पोजिशनिंग पर आपको मिलने वाले अधिकांश उत्तर इस बात में काफी सरल हैं कि वे केवल सादे वेनिला पाठ के साथ इनपुट के लिए पूर्ति करते हैं। एक बार जब आप html तत्वों का उपयोग कंटेनर में करते हैं, तो दर्ज किया गया पाठ नोड्स में विभाजित हो जाता है और एक पेड़ की संरचना में उदारतापूर्वक वितरित होता है।

कर्सर स्थिति सेट करने के लिए मेरे पास यह फ़ंक्शन है जो आपूर्ति किए गए नोड के भीतर सभी चाइल्ड टेक्स्ट नोड्स को गोल करता है और प्रारंभिक नोड की शुरुआत से वर्णक्रम चरित्र में एक सीमा निर्धारित करता है :

function createRange(node, chars, range) {
    if (!range) {
        range = document.createRange()
        range.selectNode(node);
        range.setStart(node, 0);
    }

    if (chars.count === 0) {
        range.setEnd(node, chars.count);
    } else if (node && chars.count >0) {
        if (node.nodeType === Node.TEXT_NODE) {
            if (node.textContent.length < chars.count) {
                chars.count -= node.textContent.length;
            } else {
                range.setEnd(node, chars.count);
                chars.count = 0;
            }
        } else {
           for (var lp = 0; lp < node.childNodes.length; lp++) {
                range = createRange(node.childNodes[lp], chars, range);

                if (chars.count === 0) {
                    break;
                }
            }
        }
    } 

    return range;
};

मैं तो इस समारोह के साथ दिनचर्या कहते हैं:

function setCurrentCursorPosition(chars) {
    if (chars >= 0) {
        var selection = window.getSelection();

        range = createRange(document.getElementById("test").parentNode, { count: chars });

        if (range) {
            range.collapse(false);
            selection.removeAllRanges();
            selection.addRange(range);
        }
    }
};

Range.collapse (false) कर्सर को रेंज के अंत में सेट करता है। मैंने इसे क्रोम, IE, मोज़िला और ओपेरा के नवीनतम संस्करणों के साथ परीक्षण किया है और वे सभी ठीक काम करते हैं।

पुनश्च। अगर किसी को दिलचस्पी है तो मुझे इस कोड का उपयोग करके वर्तमान कर्सर स्थिति प्राप्त होगी:

function isChildOf(node, parentId) {
    while (node !== null) {
        if (node.id === parentId) {
            return true;
        }
        node = node.parentNode;
    }

    return false;
};

function getCurrentCursorPosition(parentId) {
    var selection = window.getSelection(),
        charCount = -1,
        node;

    if (selection.focusNode) {
        if (isChildOf(selection.focusNode, parentId)) {
            node = selection.focusNode; 
            charCount = selection.focusOffset;

            while (node) {
                if (node.id === parentId) {
                    break;
                }

                if (node.previousSibling) {
                    node = node.previousSibling;
                    charCount += node.textContent.length;
                } else {
                     node = node.parentNode;
                     if (node === null) {
                         break
                     }
                }
           }
      }
   }

    return charCount;
};

कोड सेट फ़ंक्शन के विपरीत करता है - इसे वर्तमान window.getSelection () मिलता है। फ़ोकस नोड और फ़ोकसऑफ़सेट और पीछे की ओर गिने सभी पाठ वर्णों का सामना करता है जब तक कि यह कंटेनर आईडी की आईडी के साथ एक मूल नोड को हिट नहीं करता है। .ChildOf फ़ंक्शन रनिंग करने से पहले जांचता है कि वास्तव में आपूर्ति नोड वास्तव में आपूर्ति किए गए parentId का बच्चा है ।

कोड को बिना किसी बदलाव के सीधे काम करना चाहिए, लेकिन मैंने अभी इसे एक jQuery प्लगइन से लिया है जिसे मैंने विकसित किया है, इसलिए इस जोड़े को हैक किया है - मुझे बताएं कि क्या कुछ भी काम नहीं करता है!


1
क्या आप इस काम की कृपया प्रदान कर सकते हैं? मैं कैसे मैं के रूप में इस काम करता है यकीन है कि क्या नहीं कर रहा हूँ यह पता लगाने के लिए संघर्ष कर रहा हूँ node.idऔर parentIdएक उदाहरण के बिना से संबंधित हैं। धन्यवाद :)
बेंडिहोसन

4
@ बेंडीहॉसन - इस jsfiddle.net/nrx9yvw9/5 को आज़माएं - किसी कारणवश इस उदाहरण में सामग्री संपादन योग्य div कुछ वर्णों में जोड़ रहा है और पाठ के प्रारंभ में एक गाड़ी वापस आती है (यह jsfiddle स्वयं हो सकता है क्योंकि यह इसे नहीं करता है ; मेरे asp.net सर्वर पर भी ऐसा ही करें)।
लियाम

@ बेंडीहॉसन - संतोषपूर्ण div के भीतर html तत्व प्रत्येक HTML तत्व के लिए एक नोड के साथ एक पेड़ की संरचना में टूट जाते हैं। GetCurrentCursorPosition वर्तमान चयन स्थिति को प्राप्त करता है और पेड़ की गिनती करता है कि कितने सादे पाठ वर्ण हैं। Node.id html एलिमेंट आईडी है, जबकि parentId html एलिमेंट आईडी को संदर्भित करता है, इसे वापस गिनना बंद करना चाहिए
Liam

1
यह मेरे टूडू सूची में एक को लिखने के लिए है जो कि मेरे यूआई कोड से पूरी तरह से अलग है - मैं इसे पोस्ट करूँगा जब मेरे पास एक सेकंड होगा।
लियाम

1
अपने विभिन्न समाधानों को जल्दी से परखने में सक्षम होने के लिए, क्या आप अपने जवाब को रन करने योग्य कोड स्निपेट में संपादित कर सकते हैं? पहले ही, आपका बहुत धन्यवाद।
बसज

3

यदि आप jQuery का उपयोग नहीं करना चाहते हैं तो आप इस दृष्टिकोण की कोशिश कर सकते हैं:

public setCaretPosition() {
    const editableDiv = document.getElementById('contenteditablediv');
    const lastLine = this.input.nativeElement.innerHTML.replace(/.*?(<br>)/g, '');
    const selection = window.getSelection();
    selection.collapse(editableDiv.childNodes[editableDiv.childNodes.length - 1], lastLine.length);
}

editableDivआप संपादन योग्य तत्व हैं, इसके लिए सेट करना न भूलें id। फिर आपको अपने innerHTMLतत्व से प्राप्त करने और सभी ब्रेक लाइनों को काटने की आवश्यकता है। और बस अगले तर्कों के साथ पतन निर्धारित किया है।


3
  const el = document.getElementById("editable");
  el.focus()
  let char = 1, sel; // character at which to place caret

  if (document.selection) {
    sel = document.selection.createRange();
    sel.moveStart('character', char);
    sel.select();
  }
  else {
    sel = window.getSelection();
    sel.collapse(el.lastChild, char);
  }

3

function set_mouse() {
  var as = document.getElementById("editable");
  el = as.childNodes[1].childNodes[0]; //goal is to get ('we') id to write (object Text) because it work only in object text
  var range = document.createRange();
  var sel = window.getSelection();
  range.setStart(el, 1);
  range.collapse(true);
  sel.removeAllRanges();
  sel.addRange(range);

  document.getElementById("we").innerHTML = el; // see out put of we id
}
<div id="editable" contenteditable="true">dddddddddddddddddddddddddddd
  <p>dd</p>psss
  <p>dd</p>
  <p>dd</p>
  <p>text text text</p>
</div>
<p id='we'></p>
<button onclick="set_mouse()">focus</button>

जब आपके पास अग्रिम तत्व जैसे (पी) (स्पैन) इत्यादि हों तो उचित स्थिति में यह बहुत कठिन सेट कैरट है। लक्ष्य प्राप्त करना है (ऑब्जेक्ट टेक्स्ट):

<div id="editable" contenteditable="true">dddddddddddddddddddddddddddd<p>dd</p>psss<p>dd</p>
    <p>dd</p>
    <p>text text text</p>
</div>
<p id='we'></p>
<button onclick="set_mouse()">focus</button>
<script>

    function set_mouse() {
        var as = document.getElementById("editable");
        el = as.childNodes[1].childNodes[0];//goal is to get ('we') id to write (object Text) because it work only in object text
        var range = document.createRange();
        var sel = window.getSelection();
        range.setStart(el, 1);
        range.collapse(true);
        sel.removeAllRanges();
        sel.addRange(range);

        document.getElementById("we").innerHTML = el;// see out put of we id
    }
</script>

1
अपने उत्तर को तेज़ी से परखने में सक्षम होने के लिए, क्या आप अपने जवाब को एक रन करने योग्य कोड स्निपेट में संपादित कर सकते हैं? पहले ही, आपका बहुत धन्यवाद।
बसज

1

मैं एक सिंटैक्स हाइलाइटर (और बेसिक कोड एडिटर) लिख रहा हूं, और मुझे यह जानना आवश्यक है कि कैसे एक सिंगल कोट्स को ऑटो-टाइप किया जाए और कैरट को वापस ले लिया जाए (जैसे आजकल बहुत सारे कोड एडिटर हैं)।

इस समाधान, एमडीएन डॉक्स से बहुत मदद करने के लिए, और बहुत सारे मूवर्स कंसोल के लिए धन्यवाद, मेरे समाधान का एक टुकड़ा।

//onKeyPress event

if (evt.key === "\"") {
    let sel = window.getSelection();
    let offset = sel.focusOffset;
    let focus = sel.focusNode;

    focus.textContent += "\""; //setting div's innerText directly creates new
    //nodes, which invalidate our selections, so we modify the focusNode directly

    let range = document.createRange();
    range.selectNode(focus);
    range.setStart(focus, offset);

    range.collapse(true);
    sel.removeAllRanges();
    sel.addRange(range);
}

//end onKeyPress event

यह एक संतुष्ट दिव्य तत्व में है

मैं इसे एक धन्यवाद के रूप में यहां छोड़ता हूं, यह महसूस करते हुए कि पहले से ही एक स्वीकृत उत्तर है।


1

मैंने @ लियाम के उत्तर को रद्द कर दिया। मैंने इसे स्टैटिक विधियों के साथ एक कक्षा में रखा, मैंने इसके कार्यों को एक #id के बजाय एक तत्व प्राप्त किया, और कुछ अन्य छोटे ट्वीक्स।

यह कोड विशेष रूप से आपके द्वारा बनाए जा रहे रिच टेक्स्ट बॉक्स में कर्सर को ठीक करने के लिए अच्छा है <div contenteditable="true">। नीचे दिए गए कोड पर पहुंचने से पहले मैं कई दिनों तक इस पर अटका रहा।

संपादित करें: उसका उत्तर और इस उत्तर में बग दर्ज करने से संबंधित बग है। चूँकि एंटर एक कैरेक्टर के रूप में नहीं गिना जाता है, एंटर करने के बाद कर्सर की स्थिति गड़बड़ हो जाती है। यदि मैं कोड को ठीक करने में सक्षम हूं, तो मैं अपना जवाब अपडेट करूंगा।

edit2: अपने आप को बहुत सारे सिरदर्द से बचाएं और सुनिश्चित करें कि आपका <div contenteditable=true>है display: inline-block। जब आप एंटर दबाते हैं तो <div>इसके बजाय क्रोम से संबंधित कुछ बग्स को ठीक करता है <br>

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

let richText = document.getElementById('rich-text');
let offset = Cursor.getCurrentCursorPosition(richText);
// do stuff to the innerHTML, such as adding/removing <span> tags
Cursor.setCurrentCursorPosition(offset, richText);
richText.focus();

कोड

// Credit to Liam (Stack Overflow)
// https://stackoverflow.com/a/41034697/3480193
class Cursor {
    static getCurrentCursorPosition(parentElement) {
        var selection = window.getSelection(),
            charCount = -1,
            node;
        
        if (selection.focusNode) {
            if (Cursor._isChildOf(selection.focusNode, parentElement)) {
                node = selection.focusNode; 
                charCount = selection.focusOffset;
                
                while (node) {
                    if (node === parentElement) {
                        break;
                    }

                    if (node.previousSibling) {
                        node = node.previousSibling;
                        charCount += node.textContent.length;
                    } else {
                        node = node.parentNode;
                        if (node === null) {
                            break;
                        }
                    }
                }
            }
        }
        
        return charCount;
    }
    
    static setCurrentCursorPosition(chars, element) {
        if (chars >= 0) {
            var selection = window.getSelection();
            
            let range = Cursor._createRange(element, { count: chars });

            if (range) {
                range.collapse(false);
                selection.removeAllRanges();
                selection.addRange(range);
            }
        }
    }
    
    static _createRange(node, chars, range) {
        if (!range) {
            range = document.createRange()
            range.selectNode(node);
            range.setStart(node, 0);
        }

        if (chars.count === 0) {
            range.setEnd(node, chars.count);
        } else if (node && chars.count >0) {
            if (node.nodeType === Node.TEXT_NODE) {
                if (node.textContent.length < chars.count) {
                    chars.count -= node.textContent.length;
                } else {
                    range.setEnd(node, chars.count);
                    chars.count = 0;
                }
            } else {
                for (var lp = 0; lp < node.childNodes.length; lp++) {
                    range = Cursor._createRange(node.childNodes[lp], chars, range);

                    if (chars.count === 0) {
                    break;
                    }
                }
            }
        } 

        return range;
    }
    
    static _isChildOf(node, parentElement) {
        while (node !== null) {
            if (node === parentElement) {
                return true;
            }
            node = node.parentNode;
        }

        return false;
    }
}

0

मुझे लगता है कि सामग्री के अनुकूल तत्व में कुछ स्थिति पर ध्यान केंद्रित करना सरल नहीं है। मैंने इसके लिए अपना कोड लिखा। यह नोड ट्री को बायपास करता है कि कितने वर्ण बचे हैं और आवश्यक तत्व में कैरेट सेट करता है। मैंने इस कोड का अधिक परीक्षण नहीं किया।

//Set offset in current contenteditable field (for start by default or for with forEnd=true)
function setCurSelectionOffset(offset, forEnd = false) {
    const sel = window.getSelection();
    if (sel.rangeCount !== 1 || !document.activeElement) return;

    const firstRange = sel.getRangeAt(0);

    if (offset > 0) {
        bypassChildNodes(document.activeElement, offset);
    }else{
        if (forEnd)
            firstRange.setEnd(document.activeElement, 0);
        else
            firstRange.setStart(document.activeElement, 0);
    }



    //Bypass in depth
    function bypassChildNodes(el, leftOffset) {
        const childNodes = el.childNodes;

        for (let i = 0; i < childNodes.length && leftOffset; i++) {
            const childNode = childNodes[i];

            if (childNode.nodeType === 3) {
                const curLen = childNode.textContent.length;

                if (curLen >= leftOffset) {
                    if (forEnd)
                        firstRange.setEnd(childNode, leftOffset);
                    else
                        firstRange.setStart(childNode, leftOffset);
                    return 0;
                }else{
                    leftOffset -= curLen;
                }
            }else
            if (childNode.nodeType === 1) {
                leftOffset = bypassChildNodes(childNode, leftOffset);
            }
        }

        return leftOffset;
    }
}

मैंने वर्तमान करंट पोजीशन पाने के लिए कोड भी लिखा (परीक्षण नहीं किया):

//Get offset in current contenteditable field (start offset by default or end offset with calcEnd=true)
function getCurSelectionOffset(calcEnd = false) {
    const sel = window.getSelection();
    if (sel.rangeCount !== 1 || !document.activeElement) return 0;

    const firstRange     = sel.getRangeAt(0),
          startContainer = calcEnd ? firstRange.endContainer : firstRange.startContainer,
          startOffset    = calcEnd ? firstRange.endOffset    : firstRange.startOffset;
    let needStop = false;

    return bypassChildNodes(document.activeElement);



    //Bypass in depth
    function bypassChildNodes(el) {
        const childNodes = el.childNodes;
        let ans = 0;

        if (el === startContainer) {
            if (startContainer.nodeType === 3) {
                ans = startOffset;
            }else
            if (startContainer.nodeType === 1) {
                for (let i = 0; i < startOffset; i++) {
                    const childNode = childNodes[i];

                    ans += childNode.nodeType === 3 ? childNode.textContent.length :
                           childNode.nodeType === 1 ? childNode.innerText.length :
                           0;
                }
            }

            needStop = true;
        }else{
            for (let i = 0; i < childNodes.length && !needStop; i++) {
                const childNode = childNodes[i];
                ans += bypassChildNodes(childNode);
            }
        }

        return ans;
    }
}

आपको श्रेणी.startOffset और range.endOffset में टेक्स्ट नोड्स (नोड टाइप === 3) के लिए वर्ण ऑफसेट और तत्व नोड्स के लिए चाइल्ड नोड ऑफसेट (नोडटाइप === 1) के बारे में पता होना चाहिए। range.startContainer और range.endContainer पेड़ में किसी भी स्तर के किसी भी तत्व नोड को संदर्भित कर सकते हैं (बेशक वे पाठ नोड्स को भी संदर्भित कर सकते हैं)।


0

टिम डाउन के उत्तर के आधार पर, लेकिन यह अंतिम ज्ञात "अच्छा" पाठ पंक्ति के लिए जाँच करता है। यह कर्सर को सबसे अंत में रखता है।

इसके अलावा, मैं DOM में निरपेक्ष अंतिम "अच्छे" टेक्स्ट नोड को खोजने के लिए लगातार प्रत्येक अंतिम बच्चे की पुनरावृत्ति / पुनरावृत्ति की जांच भी कर सकता था।

function onClickHandler() {
  setCaret(document.getElementById("editable"));
}

function setCaret(el) {
  let range = document.createRange(),
      sel = window.getSelection(),
      lastKnownIndex = -1;
  for (let i = 0; i < el.childNodes.length; i++) {
    if (isTextNodeAndContentNoEmpty(el.childNodes[i])) {
      lastKnownIndex = i;
    }
  }
  if (lastKnownIndex === -1) {
    throw new Error('Could not find valid text content');
  }
  let row = el.childNodes[lastKnownIndex],
      col = row.textContent.length;
  range.setStart(row, col);
  range.collapse(true);
  sel.removeAllRanges();
  sel.addRange(range);
  el.focus();
}

function isTextNodeAndContentNoEmpty(node) {
  return node.nodeType == Node.TEXT_NODE && node.textContent.trim().length > 0
}
<div id="editable" contenteditable="true">
  text text text<br>text text text<br>text text text<br>
</div>
<button id="button" onclick="onClickHandler()">focus</button>


0

मैंने इसे अपने साधारण पाठ संपादक के लिए बनाया है।

अन्य तरीकों से अंतर:

  • उच्च प्रदर्शन
  • सभी स्थानों के साथ काम करता है

प्रयोग

// get current selection
const [start, end] = getSelectionOffset(container)

// change container html
container.innerHTML = newHtml

// restore selection
setSelectionOffset(container, start, end)

// use this instead innerText for get text with keep all spaces
const innerText = getInnerText(container)
const textBeforeCaret = innerText.substring(0, start)
const textAfterCaret = innerText.substring(start)

selection.ts

/** return true if node found */
function searchNode(
    container: Node,
    startNode: Node,
    predicate: (node: Node) => boolean,
    excludeSibling?: boolean,
): boolean {
    if (predicate(startNode as Text)) {
        return true
    }

    for (let i = 0, len = startNode.childNodes.length; i < len; i++) {
        if (searchNode(startNode, startNode.childNodes[i], predicate, true)) {
            return true
        }
    }

    if (!excludeSibling) {
        let parentNode = startNode
        while (parentNode && parentNode !== container) {
            let nextSibling = parentNode.nextSibling
            while (nextSibling) {
                if (searchNode(container, nextSibling, predicate, true)) {
                    return true
                }
                nextSibling = nextSibling.nextSibling
            }
            parentNode = parentNode.parentNode
        }
    }

    return false
}

function createRange(container: Node, start: number, end: number): Range {
    let startNode
    searchNode(container, container, node => {
        if (node.nodeType === Node.TEXT_NODE) {
            const dataLength = (node as Text).data.length
            if (start <= dataLength) {
                startNode = node
                return true
            }
            start -= dataLength
            end -= dataLength
            return false
        }
    })

    let endNode
    if (startNode) {
        searchNode(container, startNode, node => {
            if (node.nodeType === Node.TEXT_NODE) {
                const dataLength = (node as Text).data.length
                if (end <= dataLength) {
                    endNode = node
                    return true
                }
                end -= dataLength
                return false
            }
        })
    }

    const range = document.createRange()
    if (startNode) {
        if (start < startNode.data.length) {
            range.setStart(startNode, start)
        } else {
            range.setStartAfter(startNode)
        }
    } else {
        if (start === 0) {
            range.setStart(container, 0)
        } else {
            range.setStartAfter(container)
        }
    }

    if (endNode) {
        if (end < endNode.data.length) {
            range.setEnd(endNode, end)
        } else {
            range.setEndAfter(endNode)
        }
    } else {
        if (end === 0) {
            range.setEnd(container, 0)
        } else {
            range.setEndAfter(container)
        }
    }

    return range
}

export function setSelectionOffset(node: Node, start: number, end: number) {
    const range = createRange(node, start, end)
    const selection = window.getSelection()
    selection.removeAllRanges()
    selection.addRange(range)
}

function hasChild(container: Node, node: Node): boolean {
    while (node) {
        if (node === container) {
            return true
        }
        node = node.parentNode
    }

    return false
}

function getAbsoluteOffset(container: Node, offset: number) {
    if (container.nodeType === Node.TEXT_NODE) {
        return offset
    }

    let absoluteOffset = 0
    for (let i = 0, len = Math.min(container.childNodes.length, offset); i < len; i++) {
        const childNode = container.childNodes[i]
        searchNode(childNode, childNode, node => {
            if (node.nodeType === Node.TEXT_NODE) {
                absoluteOffset += (node as Text).data.length
            }
            return false
        })
    }

    return absoluteOffset
}

export function getSelectionOffset(container: Node): [number, number] {
    let start = 0
    let end = 0

    const selection = window.getSelection()
    for (let i = 0, len = selection.rangeCount; i < len; i++) {
        const range = selection.getRangeAt(i)
        if (range.intersectsNode(container)) {
            const startNode = range.startContainer
            searchNode(container, container, node => {
                if (startNode === node) {
                    start += getAbsoluteOffset(node, range.startOffset)
                    return true
                }

                const dataLength = node.nodeType === Node.TEXT_NODE
                    ? (node as Text).data.length
                    : 0

                start += dataLength
                end += dataLength

                return false
            })

            const endNode = range.endContainer
            searchNode(container, startNode, node => {
                if (endNode === node) {
                    end += getAbsoluteOffset(node, range.endOffset)
                    return true
                }

                const dataLength = node.nodeType === Node.TEXT_NODE
                    ? (node as Text).data.length
                    : 0

                end += dataLength

                return false
            })

            break
        }
    }

    return [start, end]
}

export function getInnerText(container: Node) {
    const buffer = []
    searchNode(container, container, node => {
        if (node.nodeType === Node.TEXT_NODE) {
            buffer.push((node as Text).data)
        }
        return false
    })
    return buffer.join('')
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.