4

I have two span elements inside of p tag. While selecting some text in that p I need to get the start and end offset of selected value using window.getSelection().

My expectation is that while selecting the second span text like 'country', the start offset value would be 28 and end offset would be 34 without considering its immediate parent. However the actual value of start offset is 10 and end offset is 16.

How can I meet my expected values?

<p style="margin: 0px;">
  <span>Welcome to India which</span>
  <span>
    <span>was great country for tourist places</span>
  </span>
</p>
private onKeyUp(event: any): void {
  let labelSelection = window.getSelection ? window.getSelection() : document.getSelection();
  let range: any = this.labelSelection.getRangeAt(0);
}
  • That is the offset in the node (a text node here). I cannot understand where you come up with the numbers you quote as the expectation here, Please add more detail regarding that. – Mark Schultheiss Jun 7 at 9:50
  • the expected values should come up without considering its immediate parent (span element) @MarkSchultheiss – Pandi Jun 7 at 9:53
  • So am i correct in thinking that you want the inner spans to be treated like one long string within a <p> - ignoring any nested nodes etc? – Francis Leigh Jun 7 at 10:17
  • Absolutely yes @FrancisLeigh. – Pandi Jun 7 at 10:18
  • can i ask why is it that you have nested your text within spans? – Francis Leigh Jun 7 at 10:19
0

It's quite a tricky problem, but was enjoyable to play around with.

Here's one day to do it:

Attach mouse event listeners to the containing element. Use event.target to identify the start and end nodes that were selected. Use these nodes to add offsets to the numbers reported by the raw selection.

Html:

<p id="outer" style="margin: 0px;">
  <span>Welcome to India which</span>
  <span>
    <span>was great country for tourist places</span>
  </span>
</p>

Javascript:

const flattenNodes = node =>
  Array.from(node.children).reduce((acc, curr) => {
    if (curr.childElementCount > 0) {
      acc.push(flattenNodes(curr))
    } else {
      acc.push(curr)
    }
    return acc
  }, [])

const getOffsetForElementAtIndex = nodes => elementIndex =>
  nodes.reduce((sum, node, i) => (i < elementIndex ? sum + node.textContent.length : sum), 0)

const getSelection = ev => {
  const nodes = flattenNodes(ev.currentTarget).flat()
  const startElementIndex = nodes.indexOf(mouseDownElement)
  const endElementIndex = nodes.indexOf(ev.target)
  const { startOffset, endOffset } = document.getSelection().getRangeAt(0)

  const start = startOffset + getOffsetForElementAtIndex(nodes)(startElementIndex)
  const end = endOffset + getOffsetForElementAtIndex(nodes)(endElementIndex)
  console.log(`start: ${start}, end: ${end}`)
}
let mouseDownElement = null

const el = document.getElementById('outer')
el.addEventListener('mousedown', ev => (mouseDownElement = ev.target))
el.addEventListener('mouseup', ev => getSelection(ev))

codepen here

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.