Created
May 25, 2023 01:54
-
-
Save richkuz/2f9ab6defa7a9b711d5772ae29b7bfe3 to your computer and use it in GitHub Desktop.
JavaScript to select a range of text in a paragraph excluding HTML markup
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Given an element, select a range of text given a start and end character position offset relative | |
// to the element's innerText, not counting any HTML markup in the element. | |
// Partial credit: https://stackoverflow.com/questions/16662393/insert-html-into-text-node-with-javascript | |
function injectMarkerIntoTextNode(node, startIndex) { | |
// startIndex is beginning location of the text to inject a span | |
let parentNode = node.parentNode; | |
var s = node.nodeValue; | |
var beforePart = s.substring(0, startIndex); | |
var afterPart = s.substring(startIndex); | |
// replace the text node with the new nodes | |
var textNode = document.createTextNode(beforePart); | |
parentNode.replaceChild(textNode, node); | |
// create a span node and add it to the parent immediately after the first text node | |
var spanNode = document.createElement("span"); | |
spanNode.className = "SelectionMarker"; | |
//spanNode.innerHTML = 'MARKER'; | |
parentNode.insertBefore(spanNode, textNode.nextSibling); | |
// create a text node for the text after the highlight and add it after the span node | |
textNode = document.createTextNode(afterPart); | |
parentNode.insertBefore(textNode, spanNode.nextSibling); | |
return spanNode; | |
} | |
// Depth-first search all child elements, reading text chars until we find | |
// a text node within range of the desired startTextOffset, then inject a marker span element. | |
function injectMarkerRecursively(el, startTextOffset, state = { charsRead: 0 }) { | |
// Recurse through all descendents | |
for (var i = 0; i < el.childNodes.length; i++) { | |
var child = el.childNodes[i]; | |
let injectedMarker = injectMarkerRecursively(child, startTextOffset, state); | |
if (injectedMarker) { | |
// Done! | |
return injectedMarker; | |
} | |
if (child.nodeType === Node.TEXT_NODE) { | |
let nodeValueLen = child.nodeValue.length; | |
if (startTextOffset <= state.charsRead + nodeValueLen) { | |
// Inject a marker inside this text node | |
return injectMarkerIntoTextNode(child, startTextOffset - state.charsRead); | |
} | |
else { | |
// Advance the cursor to the end of this node and keep looking | |
state.charsRead += nodeValueLen; | |
} | |
} | |
} | |
// No marker injected yet, keep recursing... | |
return null; | |
} | |
function clearMarkers(el) { | |
const elements = el.getElementsByClassName('SelectionMarker'); | |
while(elements.length > 0){ | |
elements[0].parentNode.removeChild(elements[0]); | |
} | |
} | |
// Credit: http://roysharon.com/blog/37 | |
function scrollIntoView(t) { | |
if (typeof(t) != 'object') return; | |
// if t is not an element node, we need to skip back until we find the | |
// previous element with which we can call scrollIntoView() | |
o = t; | |
while (o && o.nodeType != 1) o = o.previousSibling; | |
t = o || t.parentNode; | |
if (t) t.scrollIntoView(); | |
} | |
// Given an element, select a range of text given a start and end character position offset relative | |
// to the element's innerText, not counting any HTML markup in the element. | |
function selectTextRange(el, startTextOffset, endTextOffset) { | |
clearMarkers(el); | |
// Inject span element markers at the specified positions | |
const injectedMarkerEnd = injectMarkerRecursively(document.querySelector('#output'), endTextOffset); | |
const injectedMarkerStart = injectMarkerRecursively(document.querySelector('#output'), startTextOffset); | |
// Set the range relative to the span element markers | |
let range = new Range(); | |
range.setStartAfter(injectedMarkerStart); | |
range.setEndBefore(injectedMarkerEnd); | |
// Select the range | |
document.getSelection().removeAllRanges(); | |
document.getSelection().addRange(range); | |
scrollIntoView(injectedMarkerStart); | |
} | |
// Example usage: | |
selectTextRange(document.querySelector('#mydiv'), 200, 205); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment