Determine which DOM elements the current text selection contains
Asked Answered
P

2

8

I want to be able to find out which DOM elements are present in the text currently selected by the user in the browser.

document.getSelection() would get us the currently selected text. But how can we determine which DOM elements are contained in this text selection?

Prolate answered 27/3, 2010 at 12:49 Comment(0)
T
8

window.getSelection() gives you a Selection object. Use selection.rangeCount and selection.getRangeAt() to get a Range object representing which selection you want.

Now you can get the first and last nodes in the selection from range.startContainer/startOffset and range.endContainer/endOffset. You could then walk up and down the tree to pick up all the elements in between those two positions, eg. using the getElementsBetweenTree() function from this answer.

Unfortunately, IE's TextRange model is totally different to the W3 and HTML5 standard, and in general much worse. It does't give up the start and end positions nearly as easily, and not in any reliable way. All you get is parentElement to tell you the common ancestor, and from there you're limited to guessing where the range is based on creating a new Range and calling moveToElementText on it then compareEndPoints with the selection range. And if you need to know the exact text selection you're then guessing based on moveStart/moveEnd​ing the range and comparing, which isn't fun at all.

Tristan answered 27/3, 2010 at 13:45 Comment(5)
IERange (code.google.com/p/ierange) does a good job of creating a DOM Range-like object from a TextRange. I use my own slightly modified versions of the algorithms from IERange in my projects.Coaler
Looks good. (The critical bit with finding boundaries is in TextRangeUtils.convertToDOMRange.) It won't be a complete replacement for Range as IE TextRanges don't respect range boundaries around the edges of elements in the way Range is supposed to, but it's probably good enough for the common case.Tristan
Yes, agreed. I'm not sure it's possible to do much better in general and it has worked well for everything I've used it for. Can you think of any situation in which distinguishing between a boundary being at the edge of an element and it being at the edge of its child is critical? (e.g. |<div><em>Text</em></div> versus <div>|<em>Text</em></div> versus <div><em>|Text</em></div>)Coaler
Yeah, it's about the best you can do I think. It was an issue for some of my rich-editor stuff especially with empty elements. Hoping for some belated joy from IE9 on Range!Tristan
WebKit has issues too: you can't place the caret inside an empty element or indeed at the start of any text node (bugs.webkit.org/show_bug.cgi?id=23189). I've ended up writing code to insert (and later remove) Unicode BOM characters into empty elements I want the user to be able to type inside, which is both an unspeakable hack and a pain to manage.Coaler
R
0

containsNode

To know if a DOM element is under the selection or not, you can use the method like this:

const foo = document.querySelector('#foo')
getSelection().containsNode(foo, true)

OR if you want to check which elements are currently under selection, you can instead try this:

function getElementsInSelection() {
    let selection = window.getSelection();
    if (!selection.rangeCount) return [];
    let elements = [];
    document.querySelectorAll('*').forEach(node => {
        if (selection.containsNode(node, true)) elements.push(node);
    });
    return elements;
}
Rand answered 23/6, 2024 at 16:9 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.