Given the following HTML (just an example):
<div id="container"><span>This is <strong>SOME</strong> great example, <span style="color: #f00">Fred!</span></span></div>
One can extract the text using e.g. jQuery's text()
function:
var text = $('container').text();
Now, what would be the simplest, fastest, most elegant way to determine that the offset 10
in the extracted text corresponds to the offset 2
of the text node inside the <strong>SOME</strong>
node in the example above? Also, how would one do the inverse, i.e. determining the offset 10
in the extracted text from the <strong>
DOM object and the offset 2
?
You can use TreeWalker
to get a pretty elegant solution:
/**
* @param {Element} element
* @param {number} absoluteOffset
* @returns {[textNode: Text, relativeOffset: number] | null}
*/
function getRelativeOffsetWithinChildTextNode(element, absoluteOffset) {
let offset = absoluteOffset
const walker = element.ownerDocument.createTreeWalker(element, NodeFilter.SHOW_TEXT)
while (walker.nextNode()) {
const { currentNode } = walker
const text = currentNode.nodeValue
if (text.length >= offset) {
return [currentNode, offset]
}
offset -= text.length
}
return null
}
// usage
const parent = document.getElementById('container')
const absoluteOffsets = [0, 8, 10, 12, 20, 30, 999]
for (const absoluteOffset of absoluteOffsets) {
const result = getRelativeOffsetWithinChildTextNode(parent, absoluteOffset)
if (result == null) {
console.log(`Absolute offset ${absoluteOffset} is out of bounds (no text node at this offset)`)
} else {
const [textNode, relativeOffset] = result
const { parentElement } = textNode
const childNodes = [...parentElement.childNodes].filter((node) => node instanceof Text)
const num = childNodes.indexOf(textNode) + 1
console.log(`Absolute offset ${absoluteOffset} => relative offset ${relativeOffset} in ${parentElement.tagName}'s text node #${num}`)
}
}
<div id="container">This is <strong>SOME</strong> great example, <em>Fred!</em></div>