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

2022-01-24 00:00:00 webkit range javascript mozilla

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

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).

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

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!).

我想要的只是这个:

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

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

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
    };
},
};

我现在发现了两个 Mozilla 错误:

I have identified two Mozilla bugs for now:

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

  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

有时我会用偏移量 == prevSibling.length 返回对前一个兄弟的引用,并用偏移量 == 0 获得对 nextSibling 的引用.但正确的引用会在中间.这个我也能搞定.

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.

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

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

应该假设 DOM-range 对象是标准的(我不是在说 IE,这是另一项任务......)

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

你好!

这里有更具体的细节:

这并不是对 Rangy 的批评.所以我很抱歉,如果它听起来像那样.我可以想象,处理这些不同的 API 本身并不容易.

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.

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

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).

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

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>

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

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.

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

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

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

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

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

在上面发布的代码中,我确定了 Mozilla 与 webkit 的行为不同的两种情况.

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

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

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.

非常感谢您的回答.

推荐答案

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

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.

我一直在与这个错误作斗争一段时间,在 Mozilla 提交了一个错误(不记得我是否在去年使用 Opera),今天终于直接写信给 DOM4 规范的编辑,询问他们为浏览器供应商明确说明如何定义 startContainer.

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

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

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.

基本上你可以尝试做的是查看anchorNode和focusNode是否都明确匹配startContainer.

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!');}

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

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.

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

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[]

在处理 s (strike), strong, em (emphasis) 等样式元素时,您可以访问 textNode使用 firstChild 除非您在文本周围包含多个样式元素.

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>

<小时>

如果您在确定我推荐使用 in 运算符的哪些部分可用的方法时遇到困难.例如...


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+'
'+i;
}

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

...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
+'

'+
'startContainer = '+window.getSelection().getRangeAt(0).startContainer.nodeValue
+'

'+
e1
+'

'+
e2
+'

'+
e3
+'

'+
e4
+'

anchorNode = '+
window.getSelection().anchorNode.nodeName
+'

'+
window.getSelection().anchorNode.nodeValue
+'

focusNode = '+
window.getSelection().focusNode.nodeName
+'

'+
window.getSelection().focusNode.nodeValue
);

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

相关文章