Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Range object , get Selection parent node Chrome vs Firefox

I am trying to make a small text editor.

When I select a text, I want to know if the selected text is alone in a span or a div, then I want to change the style of this element.

Example :

<span style="font-size:12px">Hola</span>

If I select Hola I want to retrieve the parent node <span style="font-size:12px">Hola</span>

<span style="font-size:56px">
Hola <span style="font-size:12px">Hello</span>
</span>

If I select Hello I want to retrieve only <span style="font-size:12px">Hello</span>

Now, when I try in Chrome, I get the correct result with

range.startContainer.parentNode;

But with Firefox in the second example I retrieve

<span style="font-size:56px">
    Hola <span style="font-size:12px">Hello</span>
</span>

How can I get the same result in Chrome and in Firefox?

like image 958
Valerio Cicero Avatar asked Jan 13 '23 17:01

Valerio Cicero


1 Answers

Your problem is that selections work slightly differently between browsers when one or both of the selection boundaries coincide with DOM node boundaries. For example, if the user selected the word "bar" in page content with the following HTML

<p>foo<i>bar</i></p>

... there are four different possible locations for the selection start that all look visually identical to the user:

  1. At the end of the "foo" text node (node "foo", offset 3)
  2. After one child node of the <p> element (node <p>, offset 1)
  3. After zero children of the <i> element (node <i>, offset 0)
  4. At character index 0 of the "bar" text node (node "bar", offset 0)

Most browsers do not allow all these positions in practice, but unfortunately different browsers make different choices. In Firefox, the selection you've made selects the whole <span> element and the selection range's startContainer is the <span>'s parent node. You could detect this by examining the range to see if it selects a single node:

function rangeSelectsSingleNode(range) {
    var startNode = range.startContainer;
    return startNode === range.endContainer &&
           startNode.hasChildNodes() &&
           range.endOffset === range.startOffset + 1;
}

You can use this to detect what kind of selection you have and obtain the selected element accordingly:

var selectedElement = null;
if (rangeSelectsSingleNode(range)) {
    // Selection encompasses a single element
    selectedElement = range.startContainer.childNodes[range.startOffset];
} else if (range.startContainer.nodeType === 3) {
    // Selection range starts inside a text node, so get its parent
    selectedElement = range.startContainer.parentNode;
} else {
    // Selection starts inside an element
    selectedElement = range.startContainer;
}
like image 135
Tim Down Avatar answered Jan 21 '23 17:01

Tim Down