博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
SD9031: 各浏览器对 Range 接口的实现存在差异
阅读量:6996 次
发布时间:2019-06-27

本文共 6897 字,大约阅读时间需要 22 分钟。

  hot3.png

 

标准参考

DOM Level 2 Traversal Range 规范中定义了 Range:一个 Range(范围)是文档、文档片段或属性中的一个内容范围。从某种意义上说 Range 是连续的, 它由一对边界点之间的选中的所有内容构成并会被突出标注。

一个 Range 是由范围对应的开始和结束两个边界点组成的,在 Document 或 DocumentFragment 树中, 一个边界点位置(boundary-point's position)表现为一个节点和一个偏移量(offset)。 这个节点被称为边界点的容器或节点位置的容器,该容器以及它的祖先节点被称为边界点的祖先容器或祖先节点位置的容器。 节点中的偏移量被称为边界点和它的位置的偏移量。如果容器是一个 Attr、Document、DocumentFragment、Element 或 EntityReference 节点, 则偏移量位于它们的子节点之间;如果容器是一个 CharacterData、Comment 或 ProcessingInstruction 节点, 则偏移量位于它们包含的 UTF-16 编码的字符串中的 16 位单元之间。

Range 接口提供了访问和操作文档树的方法,这些方法比 Node 接口中类似的一些方法更高级。 每一个 Range 接口提供的插入、删除以及复制内容的方法可以直接映射到一系列 DOM Core 允许的节点编辑操作上, 也就是说 Range 接口的操作可以被看成是常用编辑操作的高效实现。

可以通过调用 DocumentRange 接口的 createRange() 方法来创建一个 Range,DocumentRange 接口可以从 Document 接口的对象实现中得到。

关于 Range 的更详细信息,请参考 Document Object Model Range。

问题描述

各浏览器对 Range 接口的实现存在差异。IE6 IE7 IE8 实现了独有的类似 Range 的 TextRange 对象,该对象拥有一些与标准 Range 接口中类似的属性及方法, 并且在创建 TextRange 时也与标准存在差异;而 Firefox Chrome Safari Opera 除了实现标准的 Range 接口外,还在此基础上扩展了一些属性及方法。 在创建 Range 时,也可以使用各自实现的 Selection 对象的 getRangeAt() 方法。

造成的影响

在使用标准 Range 接口的方法和属性时,IE6 IE7 IE8 中脚本可能会抛出异常;在使用浏览器特有的 Range 接口属性和方法时, 在其它浏览器下可能会脚本异常。这都可能导致依赖这些脚本的功能失效。

受影响的浏览器

所有浏览器  

问题分析

1. Selection 接口

根据 HTML 5 草案和 MDN 中的描述,selection 表示一个页面中的文本选择集,它可能跨越多个元素, 它由用户在页面中拖拽鼠标选中的静态文本或页面的其它部分构成。 每一个浏览上下文有一个 selection, 它可以为空,也可以拥有多个范围(range)(一个不连续的 selection)。

HTML 5 草案中定义了 Selection 接口,该接口表现为一组 Range 对象,第一个 Range 对象在组中的索引为0,以此类推。 Selection 接口中的所有成员以 Range 对象上的操作方式定义,这些操作可以像 Range 接口中定义的一样抛出异常。 可以通过 'Selection.getRangeAt(index)' 方法从当前的选择集中获得指定索引的 Range; 使用 'Selection.toString()' 方法可以获得选择集中包含的文本。

Firefox Opera 将 Selection 接口实现为 Selection 对象,Chrome Safari 实现为 DOMSelection。 在以上浏览器中,都可以通过 'window.getSelection()' 方法获得 Selection 对象;在 Firefox Chrome Safari 下,也可以通过 'document.getSelection()' 得到该对象。

IE6 IE7 IE8 实现了与上述浏览器不同的、属性和方法都很有限的 selection 对象,并且只能使用 'document.selection' 获取该对象。

关于 selection 及 Selection 接口的详细信息,请参考 HTML 5 草案 7.6 The text selection APIs。

MDN 中关于 Selection 对象的详细信息,请参考 MDN DOM:Selection。

MSDN 中关于 selection 对象的详细信息,请参考 MSDN:selection Object。

2. 各浏览器对 Range 接口的实现存在差异

IE6 IE7 IE8 实现了 TextRange 对象,该对象定义了部分和 Range 接口对应的属性及方法;同时它也实现了部分 Selection 接口的操作, 因此 TextRange 更像是一个 Range 接口和 Selection 接口的组合,但在功能上相对标准有一定的限制。 Firefox Chrome Safari Opera 则在实现 Range 和 Selection 接口的基础上,扩展了部分属性和方法。

2.1. 各浏览器创建 Range 对象的差异

规范中只给出了使用 DocumentRange 接口的 'createRange()' 方法创建 Range,除此之外,各浏览器还实现了其它的创建 Range 的方法。

IE6 IE7 IE8 中只有 TextRange,在指定的对象上创建一个 TextRange 需要使用 'object.createTextRange()'。 注意不是任何类型的对象都可以创建 TextRange,只有 body 对象、button 对象、textarea 对象和 type='text' 的 input 对象才可以。

也可以通过 selection 对象获取一个 range 对象。在 IE6 IE7 IE8 中通过 'document.selection.createRange()' 可以从当前的文本 selection 中得到一个 TextRange; 在其它浏览器中则可以使用 'window.getSelection().getRangeAt(index)' 获得一组 Range 中指定的 Range。

我们通过以下代码来测试各浏览器对各种创建 Range 对象的方法的支持程度:

<script type="text/javascript">

  var methods = ["document.createRange()", "window.getSelection().getRangeAt", "document.selection.createRange()", "document.body.createTextRange()"],

    i = 0, info, setInfo;

    window.onload = function() {

    info = document.getElementById("info"),

    setInfo = function(msg){info.innerHTML += msg + "<br/>";};

    

    for(;i<methods.length;i++){

      var method = methods[i];

      try{

        eval("(" + method + ")");

        setInfo(method);

      }catch(e){}

    }

    }

</script>

<div id="info">Support: <br/></div>

各浏览器中表现如下:

IE6 IE7 IE8 Firefox Chrome Safari Opera

IE6 IE7 IE8 Firefox Chrome Safari Opera

由此可见,IE6 IE7 IE8 只支持创建自己独有的 TextRange,它们无法创建标准的 Range。 这也说明了 IE6 IE7 IE8 的确没有实现 Range 接口,而其它浏览器也不支持 TextRange 对象。

2.2. 各浏览器中 Range 接口及 TextRange 对象的成员存在差异

Firefox Chrome Safari Opera 在实现标准 Range 接口的基础上,针对自身需求并参考 IE 的实现,添加了若干常量、属性和方法。

以下代码汇总测试了 Range 接口标准和扩展的以及 IE 下的属性及方法在各浏览器中的支持程度:

<script type="text/javascript">

  var standard = ["startContainer", "startOffset", "endContainer", "endOffset", "collapsed", "commonAncestorContainer",

      "START_TO_START", "START_TO_END", "END_TO_END", "END_TO_START","setStart", "setEnd", "setStartBefore", 

      "setStartAfter", "setEndBefore", "setEndAfter", "collapse", "selectNode", "selectNodeContents", "compareBoundaryPoints",

      "deleteContents", "extractContents", "cloneContents", "insertNode", "surroundContents", "cloneRange", "toString", "detach"],

    ext = ["compareNode", "comparePoint", "createContextualFragment", "intersectsNode", "isPointInRange", 

      "getBoundingClientRect", "getClientRects", "NODE_AFTER", "NODE_BEFORE", "NODE_BEFORE_AND_AFTER", "NODE_INSIDE", "expand"],

    ie = ["boundingHeight", "boundingLeft", "boundingTop", "boundingWidth", "htmlText", 

      "offsetLeft", "offsetTop", "text", "compareEndPoints", "duplicate", "execCommand", 

      "execCommandShowHelp", "findText", "getBookmark", "inRange", "isEqual", "move", "moveEnd", 

      "moveStart", "moveToBookmark", "moveToElementText", "moveToPoint", "parentElement", "pasteHTML", 

      "queryCommandEnabled", "queryCommandIndeterm", "queryCommandState", "queryCommandSupported", 

      "queryCommandText", "queryCommandValue", "scrollIntoView", "select", "setEndPoint"],

    range, i, count = 0, info, setInfo, testRange;

    

    window.onload = function() {

    info = document.getElementById("info"),

    setInfo = function(msg){info.innerHTML += msg + "<br/>";},

    testRange = function(member){

      if(range[member] !== undefined){

        setInfo(member);

        count++;

      }

    };

    

    if(document.createRange)

      range = document.createRange();

    else

      range = document.selection.createRange();

    

    setInfo("<strong>标准 Range 接口的常量、属性及方法:</strong>");

    for(i=0;i<standard.length;i++){

      testRange(standard[i]);

    }

    

    setInfo("<strong>扩展 Range 接口的常量、属性及方法:</strong>");

    for(i=0;i<ext.length;i++){

      testRange(ext[i]);

    }

    

    setInfo("<strong>TextRange 的常量、属性及方法:</strong>");

    for(i=0;i<ie.length;i++){

      testRange(ie[i]);

    }

    

    document.getElementById("total").innerHTML = count;

    }

</script>

<div id="info"><strong>Support: <span id="total"></span></strong><br/></div>

以上所有参与测试的常量、属性及方法来自各浏览器的官方实现文档引用及 Range.prototype。汇总测试结果如下表:1

常量、属性及方法 IE6 IE7 IE8 Firefox Chrome Safari Opera

标准 Range 接口 N2 Y Y Y

compareNode N N Y N

comparePoint N Y Y Y

createContextualFragment N Y Y Y

intersectsNode N N Y Y

isPointInRange N Y Y N

getBoundingClientRect Y N Y Y

getClientRects Y N Y Y

expand Y N Y N

NODE_AFTER N N Y N

NODE_BEFORE N N Y N

NODE_BEFORE_AND_AFTER N N Y N

NODE_INSIDE N N Y N

不包含扩展成员3的 TextRange 对象 Y N N N

注1:Y 表示支持该常量、属性或方法,N 表示不支持。

注2:IE6 IE7 IE8 在 TextRange 对象上实现了和 Range 接口相同的 collapse 方法。

注3:扩展成员指表中其它的那些常量、属性或方法,这些成员中的部分在 Range 和 TextRange 中都实现了。

MDN 中关于 Range 的详细信息,请参考 MDN DOM:range。

关于 TextRange 的详细信息,请参考 MSDN :TextRange Object。

解决方案

通过浏览器特性检测针对 IE6 IE7 IE8 正确创建 TextRange,对其它浏览器创建 Range。虽然 IE6 IE7 IE8 没有实现 Range 接口,但 TextRange 对象中也提供了许多能够实现类似功能的属性和方法。

下表总结了这些属性和方法,它们可能只是在某方面可以实现相同的功能,并不代表这两个方法或属性完全相同。

Range TextRange

compareBoundaryPoints() compareEndPoints()

cloneRange() duplicate()

setStart() moveStart()

setEnd() moveEnd()

selectNodeContents() moveToElementText()

toString() text

关于上述这些方法及属性的详细描述,请参考 DOM 2 Range Interface Range 和 MSDN 中的 TextRange Object。

转载于:https://my.oschina.net/122612475/blog/285876

你可能感兴趣的文章
批处理中的echo命令图文详解
查看>>
Chrome 自动填充的表单是淡黄色的背景,有方法自定义
查看>>
hough变换中,直线方程从XY空间转换到参数空间的转换过程
查看>>
阿里云server该数据光盘安装操作
查看>>
Onedrive 明年初基础容量缩小到5G,执行这一步骤避免(保持30G)
查看>>
IOS中NSUserDefaults的用法(轻量级本地数据存储)
查看>>
大组合取模之:1<=n<=m<=1e6,1<=p<=1e9
查看>>
百度map android sdk3.5实现定位 并跳转的指定坐标,加入标记
查看>>
Oracle VM VirtualBox技巧
查看>>
怎样自己构建一个小型的Zoomeye----从技术细节探讨到实现
查看>>
Hadoop 2.7.1 源代码目录结构分析
查看>>
《转》 Openstack Grizzly 指定 compute node 创建 instance
查看>>
[转]PhoneGap使用PushPlugin插件实现消息推送
查看>>
PHP传值与传址(引用)
查看>>
JSP简单练习-数组应用实例
查看>>
Directx11学习笔记【四】 封装一个简单的Dx11DemoBase
查看>>
DMA(STM32)
查看>>
最简单的基于FFMPEG的音频编码器(PCM编码为AAC)
查看>>
Boost.Asio基础(三)
查看>>
【转载】学习新东西的唯一方法
查看>>