Get character offsets of beginning and end of selected text

For a given string I am able to get the selected text with

var text = window.getSelection().toString();

and I am trying to get the character offset of the selected text. I have tried

window.getSelection().getRangeAt(0).startOffset

for the character offset of the beginning of the selection and

window.getSelection().getRangeAt(0).endOffset

for the character offset of the end of the selection however this gives the incorrect result.

For example, for the example string

lskdjfldjf sdlkjfsdl jflsdk fjlksdjf lksdjfl sdjfl sdjflsdkjfskl

When I select the first word (i.e. everything up to the first space), the beginning offset is 41 and the end offset is 51 according to the above method. The correct offsets should be 0 and 10. I feel like I am somewhat on the right track since the difference between the offsets is correct but its just the values that are wrong. Is there some way to programatically get the value that I should subtract from the offsets to get the correct offsets (41 in this case)? Or is there a better method to get the offsets of the selected text?

UPDATE:

Here is the larger html block where this is happening

  <div class="panel panel-default">
              <div class="panel-heading"><strong>Example {{i + 1}} of {{examples.length}}</strong></div>
                      <div class="panel-body">
                            <div (mouseup)="showSelectedText(i)">
                                            {{example.labelled}}
                            </div>
                       </div>
    </div>

this is an angular2 app. Where the function showSelectedText() contains the selection logic

2 answers

  • answered 2018-07-11 05:06 Damodhar Meshram

    used below function that will return you the new range on the based on the document.

    restoreSelection = function(savedSel,doc) {
    var charIndex = 0;
    var range = doc.createRange();
    range.setStart(doc.body, 0);
    range.collapse(true);
    var nodeStack = [doc.body],
        node, foundStart = false,
        stop = false;
    savedSel.end = savedSel.end;
    while (!stop && (node = nodeStack.pop())) {
        if (node.nodeType == 3) {
            var nextCharIndex = charIndex + node.length;
            if (!foundStart && savedSel.start >= charIndex && savedSel.start <= nextCharIndex) {
                range.setStart(node, savedSel.start - charIndex);
                foundStart = true;
            }
            if (foundStart && savedSel.end >= charIndex && savedSel.end <= nextCharIndex) {
                range.setEnd(node, savedSel.end - charIndex);
                stop = true;
            }
            charIndex = nextCharIndex;
        } else {
            var i = node.childNodes.length;
            while (i--) {
                nodeStack.push(node.childNodes[i]);
            }
        }
    }
    return range;
    

    }

    savedSel : your range and doc will the document

    this function will return you the new range with the offsets

  • answered 2018-07-11 06:20 Vignesh Raja

    Its because of the HTML structure.

    var elem = document.getElementById("text");
    
    document.addEventListener("mouseup",function(){
        if(window.getSelection)
        {
            var text = window.getSelection().toString();
            var range = window.getSelection().getRangeAt(0);
            console.log(range.startContainer.textContent,
                        range.startOffset,
                        range.endOffset)
        }
    },false);
    <div>
    You made me a, you made me a Believer!
    </div>
    <div>You made me a, you made me a Believer!</div>
    <div>You made me a, you made me a Believer!
    </div>

    Observe the HTML structure of the above snippet below,

    HTML structure of the example snippet

    Now select a line in the above output. On selecting,

    1. first line - index starts with 5 - Text content with no tags are appended to the body tag with a newline character and a tab (4 spaces by default), which defaults the startindex to 5.
    2. second line - index starts with 1 - A new line character is added as per HTML snippet been written.
    3. third line - Normal behaviour.
    4. fourth line - index ends with a value 1 greater than the actual index - Appends a newline character at the end.

    document.addEventListener("mouseup",function(){
        if(window.getSelection)
        {
            var selectedtext = window.getSelection().toString();
            var range = window.getSelection().getRangeAt(0);
            var content = range.startContainer.textContent;
            content = content.replace(/(?:\r\n|\r|\n)/g, '');
            if(range.startContainer.parentElement.tagName=="BODY")
            {
                content = content.replace(/^\s*|\s*$/, '');
            }
            console.log(content, content.indexOf(selectedtext), content.indexOf(selectedtext)+selectedtext.length)
        }
    },false);
    You made me a, you made me a Believer!
    
    <div>
    You made me a, you made me a Believer!
    </div>
    <div>You made me a, you made me a Believer!</div>
    <div>You made me a, you made me a Believer!
    </div>

    The above snipper works perfectly, only on selecting the content in a single element. If selected in multiple elements, need to be modify the implementation.