First, how my question differs from other questions:
- I am not looking to count all the characters in a div like here, I'd like to count all the characters before some selection (which may occur in the middle of a div or element).
- The characters I'd like to count can "span" many nodes and child nodes unlike here.
Second, the question itself:
Suppose I have some HTML as follows:
<h1>This is a header</h1>
<div id="not-included-in-count">
Here is text I don't want to include in count.
</div>
<div id="included-in-count">
<br />
<p>The rest of this text I want included in a count.</p>
<br />
<ul>
<li>This is the first bullet point.</li>
<li>This is the <i>second</i> bullet point.</li>
</ul>
</div>
Here's what I want to do:
- Whenever the user highlights a selection, I'd like the function to return the number of characters before the first selected character.
- Only count characters in
<div id="included-in-count">
(i.e. ignoring any earlier text in<div id="not-included-in-count">
). - It is also important the the HTML tags are not included in the count. I'd like to only count text that is rendered.
For example, if the user highlights The rest of this tex
, the function should return 1
(since there is a \n
to start the text). If the user highlights xt I want inc
, the function should return 20
. If the user highlights st bullet point. This is the second
, the function should return 70
.
Third, my attempted solution:
So far I was able to get something working with code like this. But I'm wondering if there is a better way of doing it that makes better use of the browser API or is better for some other reason (e.g. edge cases, performance, readability, etc.)
function countCharsBeforeStart() {
const range = document.getSelection().getRangeAt(0);
let node = range.startContainer;
let count = range.startOffset;
const includedNode = document.getElementById("included-in-count");
if (!includedNode.contains(node)) return 0;
while (true) {
if (node.previousSibling) {
node = node.previousSibling;
count += node.textContent.length;
} else if (node.parentNode && node.parentNode.id !== "included-in-count") {
node = node.parentNode;
} else {
break;
}
}
return count;
}