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

时间:2011-09-20 14:07:20

标签: javascript webkit range mozilla

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

我还试着看看像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返回对前一个兄弟的引用,并且使用offset == 0返回对nextSibling的引用。但是正确的引用将位于中间。我也可以处理这个问题。

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

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

    迎接!


    更具体的细节:

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

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

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

    <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始终指向受鼠标选择影响的textnode(没有空跨度,它们总是包含其他跨度或textnode),标记整个选择的开头和结尾
    • startOffset和endOffset指示选择在开头和结尾的textnode中的位置

    在上面发布的代码中,我发现了两个案例,其中Mozilla的行为与webkit不同。

    因此,如果我知道Mozilla DOM-range的规则,我可以在我的层中对其进行操作,以便webkit和Mozilla的行为相同。

    非常感谢您的回答。

2 个答案:

答案 0 :(得分:3)

没有规则说选择边界必须用文本节点表示。例如,考虑仅包含<img>元素的元素内的选择。那么,你所谓的Mozilla中的bug根本就不是bug;事实上,WebKit的选择处理是much buggier than Mozilla's。但是,您观察到浏览器在文本节点末尾处考虑选择边界时的精确位置是有效的并且确实使事情复杂化。处理它的最佳方式实际上取决于你想要做什么,这在你的问题中并不清楚。

如果您希望选择边界纯粹根据元素的文本内容中的字符偏移量you can do this(尽管我通常建议不要出于链接答案中列出的原因)。

最后,作为Rangy的作者,我想指出它基于浏览器实现的相同API(DOM范围和选择),因此我认为它不比那些API复杂或简单。参考文献:

  • Selection(正在进行中)
  • DOM 2 Range(由所有主流浏览器的当前版本实施)
  • DOM4 Range(DOM 2 Range的继任者,正在进行中)

答案 1 :(得分:3)

你注意到一个错误,Gecko / Firefox和Presto / Opera错误地选择了鼠标光标点击但未选择的节点。等等,什么?会发生的情况是,如果点击注册的字符少于HALF(虽然大于0像素),则不会选择VISUALLY,但Firefox和Opera仍会选择节点本身! Trident / IE和WebKit(Chrome / Safari)不会这样做。

我一直在与这个bug争斗一段时间,在Mozilla上提出了一个错误(不记得我是否在去年使用过Opera),今天终于直接写给了DOM4规范的编辑,问他们为浏览器供应商实现有关如何定义startContainer

的EXPLICIT说明

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

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

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

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

您可以使用startContaineranchorNode方法确定正确的focusNode,也可以使用对象检测和符合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);}