【问题标题】:Range object: differences between Webkit and Mozilla based browsersRange 对象:基于 Webkit 和 Mozilla 的浏览器之间的差异
【发布时间】:2011-09-20 14:07:20
【问题描述】:

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

我也尝试过看看像 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 和对 nextSibling 的引用,偏移量 == 0。但正确的引用会在中间。这个我也能搞定。

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

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

你好!


这里有更具体的细节:

这并不是对 Rangy 的批评。所以我很抱歉,如果它听起来像那样。我可以想象,处理这些不同的 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 始终指向受鼠标选择影响的文本节点(没有空跨度,它们始终包含其他跨度或文本节点),标记整个选择的开始和结束
  • startOffset 和 endOffset 指示所选内容在 textnode 中开头和结尾的位置

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

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

非常感谢您的回答。

【问题讨论】:

    标签: javascript webkit range mozilla


    【解决方案1】:

    没有规定选择边界必须用文本节点表示。例如,考虑一个仅包含 &lt;img&gt; 元素的元素内的选择。所以,你在 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 的继任者,正在进行中)

    【讨论】:

    • 非常感谢您的回答。我试图提供有关上述任务的更具体信息。
    • 我只是想用 rangy 解决我的问题: var range = rangy.getSelection().getRangeAt(0);但是,范围似乎仍然取决于浏览器(本机范围对象)。我该怎么做才能获得在所有浏览器中表现相同的范围?有没有另一种方法可以解决这个问题?
    【解决方案2】:

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

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

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

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

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

    记住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 用于第一个方法并从第二个实例中删除以仅保留相关方法。


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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-11-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-11-01
      • 2018-07-16
      • 2012-05-09
      相关资源
      最近更新 更多