Range对象:基于Webkit和Mozilla的浏览器之间的差异

发布于 2024-12-05 23:03:58 字数 3125 浏览 1 评论 0原文

目前,我在为基于 Mozilla 和 Webkit 的浏览器编写抽象层以使用 DOM 范围对象(获取和处理用户选择)时遇到一些麻烦。

我也尝试过看看像 Rangy 这样的框架,但这对于我的任务来说似乎太复杂了(我不知道在代码中到底在哪里可以找到我需要的信息。如果有人能给我一个提示,我将不胜感激!)。

我想要的只是这样:

  • 获取对选择开始的文本节点及其偏移量的引用
  • 获取对选择结束的文本节点及其偏移量的引用

到目前为止,我的图层如下所示:

var SEL_ABSTR = {
get_selection: function(window_object) {
    return window_object.getSelection();
},
get_range: function(selection) {
    return (selection.getRangeAt) ? selection.getRangeAt(0) : selection.createRange();
},
get_range_info: function(range, div_ele) {
    var first_node, start_offset;
    var last_node, end_offset;

    if (range.startContainer == div_ele) {
        // selections affects the containing div
        first_node = div_ele.childNodes[0];
        last_node = first_node;
        start_offset = 0;
        end_offset = first_node.nodeValue.length;
    } else if (range.startOffset == range.startContainer.nodeValue.length && range.endOffset == 0) {
        // known bug in Firefox
        alert('firefox bug');
        first_node = range.startContainer.nextSibling.childNodes[0];
        last_node = first_node;
        start_offset = 0;
        end_offset = first_node.nodeValue.length;
    } else {
        first_node = range.startContainer;
        last_node = range.endContainer;
        start_offset = range.startOffset;
        end_offset = range.endOffset;
    }

    return {
        first_node: first_node,
        start_offset: start_offset,
        last_node: last_node,
        end_offset: end_offset,
        orig_diff: end_offset - start_offset
    };
},
};

我已经识别了两个 Mozilla目前的错误:

  1. 有时,当在包含的 div 中选择整个(如果是唯一的)文本节点时,我会返回对此 div 的引用,而不是对文本节点的引用。现在我可以处理它并返回对 div 的子节点(即文本节点)的引用

  2. 有时,我会返回对前一个同级的引用,其中 offset == prevSibling.length 以及对 nextSibling 的引用,其中 offset = = 0。但正确的参考应该在中间。我也可以处理这个问题。

那么 Mozilla 还有什么呢? Webkit 工作正常!

人们应该假设 DOM 范围对象是标准的(我不是在谈论 IE,这是另一项任务......)

欢迎!


更具体的细节在这里:

这并不是对兰吉的批评。所以,如果听起来是这样的话,我很抱歉。我可以想象处理这些不同的 API 本身并不容易。

你是对的,我没有具体说明我要完成的任务。我的结构相当简单:我有一个 div (属性 contenteditable=true)和该 div 中的文本(开头有一个文本节点)。

从这个结构开始,现在可以用鼠标选择文本并为其添加属性;然后,该属性由包含所选文本的范围和分配给代表所选属性的该范围的类来表示。现在可以再次选择一些文本和属性。如果它是相同的文本和另一个属性,则另一个类将被分配给该范围,或者如果该属性已存在则被删除。如果选择包含多个跨度的文本,它们将被分割以表达该结构(也许您还记得我七月的帖子)。

<div contenteditable="true">
hello I am 
<span class="red">text but I am also <span class="underline">underlined</span></span>
<span class="underline"> also without color</span>
</div>

该算法现在对于“对称”情况运行良好:我可以构建一个复杂的结构,然后向后撤消它。它适用于 Safari 和 Chrome。现在我当然要进一步开发算法。

但现在我在 Mozilla 上遇到了问题,因为我不了解 DOM 范围对象的系统:startContainer、endContainer、startOffset、endOffset

根据我对仅包含文本节点和跨度的 div 的具体情况的看法,我假设:

  • startContainer 和 endContainer 始终指向受鼠标选择影响的文本节点(没有空跨度,它们总是包含其他跨度或文本节点),标记整个选择的开始和结束
  • ,startOffset和endOffset指示的位置 在上面发布的代码

中,我已经确定了 Mozilla 与 webkit 的行为不同的两种情况。

因此,如果我知道 Mozilla DOM 范围的规则,我可以将其整合到我的层中,以便 webkit 和 Mozilla 的行为相同。

非常感谢您的回答。

at the moment I have some troubles writing an abstraction layer for Mozilla and Webkit based browsers for using the DOM-range object (getting and processing user selections).

I have also tried to have a look at frameworks like Rangy but this seems far to complex for my task (I have no idea where exactly in the code to find the information I need. If someone could give me a hint, I would be grateful!).

What I want is simply this:

  • get back the reference to the text node the selection starts in and its offset
  • get back the reference to the text node the selection ends in and its offset

So far my layer looks like this:

var SEL_ABSTR = {
get_selection: function(window_object) {
    return window_object.getSelection();
},
get_range: function(selection) {
    return (selection.getRangeAt) ? selection.getRangeAt(0) : selection.createRange();
},
get_range_info: function(range, div_ele) {
    var first_node, start_offset;
    var last_node, end_offset;

    if (range.startContainer == div_ele) {
        // selections affects the containing div
        first_node = div_ele.childNodes[0];
        last_node = first_node;
        start_offset = 0;
        end_offset = first_node.nodeValue.length;
    } else if (range.startOffset == range.startContainer.nodeValue.length && range.endOffset == 0) {
        // known bug in Firefox
        alert('firefox bug');
        first_node = range.startContainer.nextSibling.childNodes[0];
        last_node = first_node;
        start_offset = 0;
        end_offset = first_node.nodeValue.length;
    } else {
        first_node = range.startContainer;
        last_node = range.endContainer;
        start_offset = range.startOffset;
        end_offset = range.endOffset;
    }

    return {
        first_node: first_node,
        start_offset: start_offset,
        last_node: last_node,
        end_offset: end_offset,
        orig_diff: end_offset - start_offset
    };
},
};

I have identified two Mozilla bugs for now:

  1. Sometimes when the whole (if its the only one) text node is selected within the containing div I get back a reference to this div instead of a reference to the text node. Now I can handle it and give back a reference to the child of the div which is the text node

  2. Sometimes I get back a reference to the previous sibling with offset == prevSibling.length and and a reference to nextSibling with offset == 0. But the correct reference would be in the middle. I can also handle this.

So what more is there for Mozilla? Webkit works fine!

One should assume that the DOM-range object is standard (and I am not talking of IE, this is another task ...)

greets!


more specific details here:

It was't meant as a critique on Rangy. So I am sorry if it sounded like that. I can imagine that handling these different APIs is not easy per se.

You are right, I wasn't specific regarding the task I am trying to fulfill. My structure is rather simple: I have a div (with attribute contenteditable=true) and text within that div (one text node at the beginning).

Starting from this structure, it is now possible to select text with the mouse and add a property to it; this property is then expressed by a span embracing the selected text and a class assigned to that span representing the selected property. Now it is possible to select again some text and a property. If it is the same text and another property, another class will be assigned to that span or removed if the property already exists. If text is selected which embraces several spans, they will be split in order to express that structure (perhaps you remember my post of July).

<div contenteditable="true">
hello I am 
<span class="red">text but I am also <span class="underline">underlined</span></span>
<span class="underline"> also without color</span>
</div>

The algorithm works fine now for "symmetrical" cases: I can build a complex structure and then undo it backwards. It works fine for Safari and Chrome. Now I have of course to develop the algorithm further.

But for now I have problems with Mozilla because I do not understand the system for DOM range objects: startContainer, endContainer, startOffset, endOffset

In my perception regarding my specific case with a div only containing textnodes and spans I assume:

  • that startContainer and endContainer always point to a textnode affected by the mouse selection (there are no empty spans, they always contain either other spans or a textnode), marking the beginning and the end of the whole selection
  • that startOffset and endOffset indicate the position of the selection within the textnode at the beginning and at the end

In the posted code above I have identified two cases in which Mozilla acts differently from webkit.

So if I knew the rules of Mozilla DOM-range I could inegrate that in my layer so that the behaviour would be the same for webkit and Mozilla.

Thank you very much for your answer.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

好听的两个字的网名 2024-12-12 23:03:58

没有规则规定选择边界必须用文本节点来表达。例如,考虑仅包含 元素的元素内的选择。所以,你所说的 Mozilla 中的 bug 根本就不是 bug;而是 bug。事实上,WebKit 的选择处理比 Mozilla 的问题多。然而,您观察到浏览器在认为选择边界位于文本节点末尾时的精确位置上有所不同,这是有效的,并且确实使事情变得复杂。处理它的最佳方法实际上取决于您想要做什么,这从您的问题中并不清楚。

如果您想要纯粹根据元素文本内容内的字符偏移量来选择边界,你可以这样做(尽管出于链接答案中列出的原因,我通常建议不要这样做)。

最后,作为 Rangy 的作者,我想指出它基于浏览器实现的相同 API(DOM Range 和 Selection),所以我想说它并不比这些 API 更复杂或更简单。参考文献:

  • 选择(正在进行中)
  • DOM 2 Range(由所有主要浏览器的当前版本实现)
  • DOM4 Range(DOM 2 Range 的后续版本,正在进行中)

There is no rule that says selection boundaries must be expressed in terms of text nodes. Consider a selection inside an element that contains only <img> elements, for example. So, what you're calling bugs in Mozilla are not bugs at all; in fact, WebKit's selection handling is much buggier than Mozilla's. However, your observation that browsers vary in precisely where they consider a selection boundary to lie when it is at the end of a text node is valid and does complicate things. The best way to deal with it really depends on what you're trying to do, which isn't clear from your question.

If you want selection boundaries purely in terms of character offsets within the text content of an element, you can do this (although I'd generally recommend against it for reasons laid out in the linked answer).

Finally, as author of Rangy, I'd like to point out that it's based on the same APIs (DOM Range and Selection) that browsers implement, so I'd say it's no more or less complicated than those APIs. References:

  • Selection (work in progress)
  • DOM 2 Range (implemented by current versions of all major browsers)
  • DOM4 Range (successor to DOM 2 Range, work in progress)
坐在坟头思考人生 2024-12-12 23:03:58

您注意到一个错误,Gecko/Firefox 和 Presto/Opera 错误地选择了鼠标光标单击的节点,但并未选择。等等,什么?发生的情况是,如果单击注册的字符少于一半(尽管大于 0 像素),则不会在视觉上选择它,但是 Firefox 和 Opera 仍然会选择节点本身! Trident/IE 和 WebKit (Chrome/Safari) 不执行此操作。

我已经与这个 bug 作斗争有一段时间了,在 Mozilla 上提交了一个 bug(不记得我是否在 Opera 上这样做过,因为这是去年的事),今天终于直接写信给 DOM4 规范的编辑,要求他们实现一个向浏览器供应商明确说明如何定义 startContainer

可以调整代码来处理这个问题,但是我没有时间为您写出所有代码。

这是我们将使用的方法列表......

window.getSelection().getRangeAt(0).startContainer;

window.getSelection().anchorNode

window.getSelection().focusNode

记住锚节点并不是通过初始单击明确地左起始位置,这一点非常重要。如果用户单击文本的右侧并向左拖动鼠标,则锚点最终位于范围的右侧。如果用户单击文本的左侧,然后将鼠标拖动到右侧,则锚点将位于范围的左侧。

本质上,您可以尝试做的就是查看anchorNode 和focusNode 是否与startContainer 明确匹配。

var sc = window.getSelection().getRangeAt(0).startContainer;
var an = window.getSelection().anchorNode
var fn = window.getSelection().focusNode

if (sc!==an && sc!==fn) {alert('startContainer bug encountered!');}

即使您在所有情况下都不需要 startContainer,您最终仍然会引用它,因此最好使用一个对象来表示 startContainer,如果浏览器第一次就正确(Trident/WebKit),否则您必须纠正它(Gecko/Presto)。

这就是有点棘手的地方,特别是因为不同的人会有不同的目标和方法,所以我会尽量保持以下内容的通用性。

您可以使用 anchorNodefocusNode 方法确定正确的 startContainer,也可以使用对象检测和符合 W3C 的方法。这些其他方法包括......

window.getSelection().getRangeAt(0).startContainer
window.getSelection().getRangeAt(0).startContainer.parentNode
window.getSelection().getRangeAt(0).startContainer.previousSibling
window.getSelection().getRangeAt(0).startContainer.nextSibling
window.getSelection().getRangeAt(0).startContainer.childNodes[]

在处理诸如 s (罢工)、strongem (强调)等样式元素时您可以使用 firstChild 访问 textNode,除非文本周围包含多个样式元素。

.nextSibling.firstChild
.nextSibling.firstChild.nodeValue
<em>textNode here</em>

如果您难以确定哪些部分可以使用哪些方法,我建议使用 in 运算符。例如...

for (i in window.getSelection())
{
 document.getElementById('textarea_example').value = document.getElementById('textarea_example').value+'\n'+i;
}

...请记住,如果您处于循环内部,它可能会重复 textarea 元素中的选项,因此对于第一个方法按 CTRL+f 并从第二个实例中删除以仅保留相关方法。


请记住使用警报,我经常使用多行来同时显示多条信息,以帮助我确定我拥有什么。例如...

var e1 = scp.nodeName;
if (scp.nextSibling) {var e2 = scp.nextSibling.nodeName;} else {var e2 = 'null';}
var e3 = sc.nodeName;
if (sc.nextSibling) {var e4 = sc.nextSibling.nodeName;} else {var e4 = 'null';}

alert(
'startContainer = '+window.getSelection().getRangeAt(0).startContainer.nodeName
+'\n\n'+
'startContainer = '+window.getSelection().getRangeAt(0).startContainer.nodeValue
+'\n\n'+
e1
+'\n\n'+
e2
+'\n\n'+
e3
+'\n\n'+
e4
+'\n\nanchorNode = '+
window.getSelection().anchorNode.nodeName
+'\n\n'+
window.getSelection().anchorNode.nodeValue
+'\n\nfocusNode = '+
window.getSelection().focusNode.nodeName
+'\n\n'+
window.getSelection().focusNode.nodeValue
);

if (e2=='#text') {alert('e2 = '+scp.nextSibling.nodeValue);}
if (e4=='#text') {alert('e4 = '+scp.nextSibling.nodeValue);}

You are noticing a bug where Gecko/Firefox and Presto/Opera incorrectly select the node in which the mouse cursor clicked though did not select. Wait, what? What happens is that if the click registers less than HALF of a character (though greater than 0 pixels) it is not VISUALLY selected HOWEVER Firefox and Opera still select the node itself! Trident/IE and WebKit (Chrome/Safari) do not do this.

I have been battling against this bug for some time, filed a bug at Mozilla (can't remember if I did so with Opera since this was last year) and today finally wrote directly to the editors of the DOM4 specification asking them to implement an EXPLICIT clarification for browser vendors on how to define the startContainer.

It IS possible to adapt the code to handle this however I do not have the time to write out all the code for you.

Here is the list of methods we'll use...

window.getSelection().getRangeAt(0).startContainer;

window.getSelection().anchorNode

window.getSelection().focusNode

It's VERY important to remember that the anchorNode is not EXPLICITLY the left starting position though the INITIAL CLICK. If the user click on the right side of text and drags the mouse to the left then the anchor ends up on the RIGHT side of the range. If the user click on the left side of text and then drags the mouse to the right the anchor is then on the left side of the range.

Essentially what you can try to do is see if neither the anchorNode nor the focusNode EXPLICITLY match the startContainer.

var sc = window.getSelection().getRangeAt(0).startContainer;
var an = window.getSelection().anchorNode
var fn = window.getSelection().focusNode

if (sc!==an && sc!==fn) {alert('startContainer bug encountered!');}

Even if you don't need the startContainer in ALL situations you're still going to eventually reference it SO it's best to use an object to represent the startContainer be it if the browser gets it right the first time (Trident/WebKit) or you have to correct it (Gecko/Presto).

This is where it gets a bit tricky especially because different people will have different goals and approaches so I will try to keep the following as generic as possible.

Either you can determine the correct startContainer using anchorNode or focusNode methods OR you can use object detection and W3C compliant methods. Those other methods include....

window.getSelection().getRangeAt(0).startContainer
window.getSelection().getRangeAt(0).startContainer.parentNode
window.getSelection().getRangeAt(0).startContainer.previousSibling
window.getSelection().getRangeAt(0).startContainer.nextSibling
window.getSelection().getRangeAt(0).startContainer.childNodes[]

When dealing with style elements such as s (strike), strong, em (emphasis) and so on you may access the textNode using the firstChild unless you have multiple style elements enclosed around the text.

.nextSibling.firstChild
.nextSibling.firstChild.nodeValue
<em>textNode here</em>

If you're having difficulty with determining what methods are available at what parts I recommending using the in operator. In example...

for (i in window.getSelection())
{
 document.getElementById('textarea_example').value = document.getElementById('textarea_example').value+'\n'+i;
}

...keep in mind that if you're inside of a loop that it may repeat the options in your textarea element so CTRL+f for the first method and erase from it's second instance down to retain only relevant methods.


Remember to use alert and I often use multiple lines to show multiple pieces of information simultaneously to help me determine what I have. In example...

var e1 = scp.nodeName;
if (scp.nextSibling) {var e2 = scp.nextSibling.nodeName;} else {var e2 = 'null';}
var e3 = sc.nodeName;
if (sc.nextSibling) {var e4 = sc.nextSibling.nodeName;} else {var e4 = 'null';}

alert(
'startContainer = '+window.getSelection().getRangeAt(0).startContainer.nodeName
+'\n\n'+
'startContainer = '+window.getSelection().getRangeAt(0).startContainer.nodeValue
+'\n\n'+
e1
+'\n\n'+
e2
+'\n\n'+
e3
+'\n\n'+
e4
+'\n\nanchorNode = '+
window.getSelection().anchorNode.nodeName
+'\n\n'+
window.getSelection().anchorNode.nodeValue
+'\n\nfocusNode = '+
window.getSelection().focusNode.nodeName
+'\n\n'+
window.getSelection().focusNode.nodeValue
);

if (e2=='#text') {alert('e2 = '+scp.nextSibling.nodeValue);}
if (e4=='#text') {alert('e4 = '+scp.nextSibling.nodeValue);}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文