范围对象:Webkit和基于Mozilla的浏览器之间的区别 [英] Range object: differences between Webkit and Mozilla based browsers

查看:105
本文介绍了范围对象:Webkit和基于Mozilla的浏览器之间的区别的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

目前,我在为基于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. 有时,我会返回对偏移量== prevSibling.length的前一个兄弟的引用和对偏移量== 0的nextSibling的引用.但是正确的引用将在中间.我也可以处理.

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

应该假定DOM范围对象是标准的(我不是在说IE,这是另一项任务……)

打招呼!


此处有更多详细信息:

这并不是对Rangy的批评.所以,如果听起来像那样,我很抱歉.我可以想象处理这些不同的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

根据我对仅包含textnodes和spans的div的具体情况的看法,我认为:

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

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

因此,如果我知道Mozilla DOM范围的规则,则可以在我的层中对此加以说明,以使webkit和Mozilla的行为相同.

非常感谢您的回答.

解决方案

您注意到了一个错误,其中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

非常重要的是要记住,尽管通过INITIAL CLICK,anchorNode并非明确地位于左侧的起始位置.如果用户单击文本的右侧并将鼠标拖动到左侧,则锚点将在范围的右侧结束.如果用户单击文本的左侧,然后将鼠标拖到右侧,则锚点将位于范围的左侧.

基本上,您可以尝试做的是看看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并从第二个实例开始进行擦除,以仅保留相关的方法. /p>


请记住要使用警报,我经常使用多行来同时显示多条信息,以帮助我确定自己所拥有的.例如...

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

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.

解决方案

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

这篇关于范围对象:Webkit和基于Mozilla的浏览器之间的区别的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆