DOM2和DOM3

前端开发
2018年12月12日
0

DOM1级主要定义的是HTML和XML文档的底层结构。DOM2和DOM3级则在这个结构的基础上引入了更多的交互能力,也支持了更高级的XML特性。为此,DOM2和DOM3级分为许多模块(模块之间具有某种关联),分别描述了DOM的某个非常具体的子集。这些模块如下:

  • DOM2级核心(DOM Level 2 Core):在1级核心基础上构建,为节点添加了更多方法和属性。
  • DOM2级视图(DOM Level 2 Views):为文档定义了基于样式信息的不同视图。
  • DOM2级事件(DOM Level 2 Events):说明了如何使用事件与DOM文档交互。
  • DOM2级样式(DOM Level 2 Style):定义了如何以编程方式来访问和改变CSS样式信息。
  • DOM2级遍历和范围(DOM Level 2 Traversal and Range):引入了遍历DOM文档和选择其特定部分的新接口。
  • DOM2级HTML(DOM Level 2 HTML):在1级HTML基础上构建,添加了更多属性、方法和新接口。

DOM变化

DOM2级和3级的目的在于扩展DOM API,以满足操作XML的所有需求,同时提供更好的错误处理及特性检测能力。可以通过下列代码来确定浏览器是否支持这些DOM模块。

js
var supportsDOM2Core = document.implementation.hasFeature('Core', '2.0'); var supportsDOM3Core = document.implementation.hasFeature('Core', '3.0'); var supportsDOM2HTML = document.implementation.hasFeature('HTML', '2.0'); var supportsDOM2Views = document.implementation.hasFeature('Views', '2.0'); var supportsDOM2XML = document.implementation.hasFeature('XML', '2.0');

其他的不过多讨论了。

样式

在HTML中定义样式的方式有3种:通过<link>元素包含外部样式表文件,使用<style>元素定义嵌入式样式,以及使用style特性定义针对特写元素的样式。“DOM2级样式”模块这3种应用样式的机制提供了一套API。要确定浏览器是否支持DOM2级定义的CSS能力,可以使用下列代码:

js
var supportsDOM2CSS = document.implementation.hasFeature('CSS', '2.0'); var supportsDOM2CSS2 = document.implementation.hasFeature('CSS2', '2.0');

访问元素的样式

任何支持style特性的HTML元素在JavaScript中都有对应的style属性。这个style对象CSSStyleDeclaration的实例,包含着通过HTML的style特性指定的所有样式信息,但不包含外部样式表或嵌入样式表经层叠而来的样式。在style特性中指定的任何CSS属性都将表现为这个style对象的相应属性。对于使用横杠(例如,background-image)的CSS属性名,必须将其转换成驼峰大小写形式,才能通过JavaScript来访问。

多数情况下,都可以通过简单地转换属性名的格式来实现转换。其中一个不能直接转换的CSS属性就是float。因为float是JavaScript中的保留字。“DOM2级样式”规范规定样式对象上相应的属性名应该是cssFloat;Firefox、Safari、Opera和Chorme都支持这个属性,而IE支持的则是styleFloat

只要取得一个有效的DOM元素的引用,就随时可以使用JavaScript为其设置样式。

js
var myDiv = document.getElementById('myDiv'); myDiv.style.backgroundColor = 'red'; myDiv.style.width = '100px'; myDiv.style.height = '200px'; myDiv.style.border = '1px solid black';

通过style对象同样可以取得在style特性中指定的样式。

html
<div id="myDiv" style="background-color: blue; width: 10px; height: 25px"></div>
js
var myDiv = document.getElementById('myDiv'); alert(myDiv.style.backgroundColor); // 'blue' alert(myDiv.style.width); // '10px' alert(myDiv.style.height); // '25px'

如果没有为元素设置style特性,那么stlye对象中可能会包含一些默认的值,但这些值并不能准确地反映该元素的样式信息。

DOM样式属性和方法

“DOM2级样式”规范还为style对象定义了一些属性和方法。这些属性和方法在提供元素的style特性值的同时,也可以修改样式。下面列出了这些属性和方法:

  • cssText:如前撰述,通过它能够访问到style特性中的CSS代码。
  • length:应用给元素的CSS属性的数量。
  • parentRule:表示CSS信息的CSSRule对象。
  • getPropertyCSSValue(propertyName):返回包含给定属性值的CSSValue对象。
  • getPropertyPriority(propertyName):如果给定的属性使用了!important设置,则返回'important';否则返回空字符串。
  • getPropertyValue(propertyName):返回给定属性的字符串值。
  • item(index):返回给定位置的CSS属性的名称。
  • removeProperty(propertyName):从样式中删除给定属性。
  • setProperty(propertyName, value, priority):将给定属性设置为相应的值,并加上优先权标志('important’或一个空字符串)。
js
myDiv.style.cssText = 'width: 25px; height: 100px; background-color: green'; alert(myDiv.style.cssText); var prop, // 属性名 value; // 属性值 for (var i = 0, len = myDiv.style.length; i < len; i++) { prop = myDiv.style.item(i); // 或者myDiv.style[i] value = myDiv.style.getPropertyValue(prop); alert(prop + ' : ' + value); }

getPropertyValue()方法始终都是返回CSS属性值的字符串表示。如果你需要更多的信息,可以使用getPropertyCSSValue,它返回一个包含两个属性的CSSValue对象,这两个属性分别是:cssTextcssValueType。其中,cssText属性的值与getPropertyValue()返回的值相同,而cssValueType属性则是一个数值常量,表示值的类型:0表示继承的值,1表示基本的值,2表示值列表,3表示自定义的值。

要从元素的样式中移除某个CSS属性,需要使用removeProperty()方法。使用这个方法移除一个属性,意味着将会为该属性应用默认的样式(从其他样式表经层叠而来)。

计算的样式

虽然style对象能够提供支持style特性的任何元素的样式信息,但它不包含那些从其他样式表层叠而来并影响到当前元素的样式信息。“DOM2级样式”增强了document.defaultView,提供了getComputedStyle()方法。这个方法接受两个参数:要取得计算样式的元素和一个伪元素字符串(例如:‘:after’)。如果不需要伪元素信息,第二个参数可以是null。getComputedStyle()返回一个CSSStyleDeclaration对象(与style属性的类型相同),其他包含当前元素的所有计算的样式。

html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Computed Styles Example</title> <style> #myDiv { width: 100px; height: 200px; background-color: blue; } </style> </head> <body> <div id="myDiv" style="background-color: red; border: 1px solid black"></div> </body> </html>
js
var myDiv = document.getElementById('myDiv'); var computedStyle = document.defaultView.getComputedStyle(myDiv, null); alert(computedStyle.backgroundColor); // 'red' alert(computedStyle.width); // '100px' alert(computedStyle.height); // '200px' alert(computedStyle.border); // '在某些浏览器中是"1px solid black"'

IE不支持getComputedStyle()方法,但它有一种类似的概念。在IE中,每个具有style属性的元素还有一个currentStyle属性。这个属性是CSSStyleDeclaration的实例,包含当前元素全部计算后的样式。

js
var myDiv = document.getElementById('myDiv'); var computedStyle = myDiv.currentStyle; alert(computedStyle.backgroundColor); // 'red' alert(computedStyle.width); // '100px' alert(computedStyle.height); // '200px' alert(computedStyle.border); // undefined

无论在哪个浏览器中,最重要的一条是要记住所有计算的样式都是只读的;不能修改计算后的样式对象中的CSS属性。此外,计算后的样式也包含属于浏览器内部样式表的样式信息,因此任何具有默认值的CSS属性都会表现在计算后的样式中。

操作样式表

CSSStyleSheet类型表示的是样式表,包括通过<link>元素包含的样式表和在<style>元素中定义的样式表。

js
var supportsDOM2StyleSheets = document.implementation.hasFeature('StyleSheets', '2.0');

CSSStyleSheet继承自StyleSheet,后者可以作为一个基础接口来定义非CSS样式表。从StyleSheet接口继承而来的属性如下:

  • disabled:表示样式表是否被禁用的布尔值。
  • href:如果样式表是通过<link>包含的,则是样式表的URL;否则,是null。
  • media:当前样式表支持的所有媒体类型的集合。
  • ownerNode:指向拥有当前样式表的节点的指针。如果当前样式表是其他样式表通过@import导入的,则这个属性值为null。IE不支持这个属性。
  • prarentStyleSheet:在当前样式表是通过@import导入的情况下,这个属性是一个指向导入它的样式表的指针。
  • title:ownerNode中title属性的值。
  • type:表示样式表类型的字符串,对CSS样式表而言,这个字符串是’type/css’。

除了disabled属性之外,其他属性都是只读的。在支持以上所有属性的基础上,CSSStyleSheet类型还支持下列属性和方法:

  • cssRules:样式表中包含的样式规则的集合。IE不支持这个属性,但有一个类似的rules属性。
  • ownerRule:如果样式表是通过@import导入的,这个属性就是一个指针,指向表示导入的规则,否则值为null。IE不支持这个属性。
  • deleteRule(index):删除cssRules集合中指定位置的规则。IE不支持这个方法,但支持一个类似的removeRule()方法。
  • insertRule(rule, index):向CSSRules集合中指定的位置插入rule字符串。Ie不支持这个方法,但支持一个类似的addRule()方法。

应用于文档的所有样式表是通过document.styleSheets集合来表示的。通过个这集合的length属性可以获文档中样式表的数量,而通过方括号语法或item()方法可以访问每一个样式表。

js
var sheet = null; for (var i = 0, len = document.styleSheets.length; i < len; i++) { sheet = document.styleSheets[i]; // 或者document.styleSheets.item(i); alert(sheet.href); }

也可以直接通过<link><style>元素取得CSSStyleSheet对象。DOM规定了一个包含CSSStyleSheet对象的属性,名叫sheet;除了IE,其他浏览器都支持这个属性。IE支持的是styleSheet属性

js
function getStyleSheet (element) { return element.sheet || element.styleSheet; } // 取得第一个<link>元素引入的样式表 var link = document.getElementByTagName('link')[0]; var sheet = getStyleSheet(link);

CSS规则

CSSRule对象表示样式表中的每一条规则。实际上,CSSRule是一个供其他多种类型继承的基类型,其中最常见的就是CSSStyleRule类型,表示样式信息(其他规则还有@import、@font-face、@page和@charset,但这些规则很少有必要通过脚本来访问)。CSSStyleRule对象包含下列属性:

  • cssText:返回整条规则对应的文本。
  • parentRule:如果当前规则是导入规则,这个属性引用的就是导入规则;否则这个值为null。IE不支持这个。
  • parentStyleSheet:当前规则所属的样式表。IE不支持这个属性。
  • selectorText:返回当前规则的选择符文本。
  • style:一个CSSStyleDeclaration对象,可以通过它设置和取得规则中特定的样式值。
  • type:表示规则类型常量值。对于样式规则,这个值是1。IE不支持这个属性。
css
div.box { width: 100px; height: 200px; background-color: blue; }
js
var sheet = document.styleSheets[0]; var rules = sheet.cssRules || sheet.rules; // 取得规则列表 var rule = rules[0]; // 取得第一条规则 alert(rule.selectorText); // 'div.box' alert(rule.style.cssText); // '完整的CSS代码' alert(rule.style.backgroundColor); // 'blue' alert(rule.style.width); // '100px' alert(rule.style.height); // '200px' // 修改 rule.style.backgroundColor = 'red'

注意,以这种方式修改规则会影响页面中适用于该规则的所有元素。

创建规则

DOM规定,要向现有样式表中添加新规则,需要使用insertRule()方法。这个方法接受两个参数:规则文本和表示在哪里插入规则的牵引。

js
sheet.insertRule('body { background-color: silver }', 0); // DOM方法

Firefox、Safari、Opera和Chorme都支持insertRule()访求。

IE8及更早版本支持一个类似的方法,名叫addRule(),也接收两个必选参数:选择符文本和CSS样式信息;一个可选参数:插入规则的位置。

js
// IE8及更早版本 sheet.addRule('body', 'background-color: silver', 0);

删除规则

从样式表中删除规则的方法是deleteRule(),这个方法接受一个参数:要删除的规则的位置。

js
sheet.deleteRule(0); // DOM方法

IE支持类似的方法叫removeRule()

js
// 仅IE有效 sheet.removeRule(0);

元素大小

偏移量

首先要介绍的属性涉及偏移量(offset dimension),包括元素在屏幕上占用的所有可见的空间。元素的可见大小由其高度、宽度决定,包括所有内边距、滚动条和边框大小(注意,不包括外边距)。通过下列4个属性可以取得元素的偏移量。

  • offsetHeight:元素在垂直方向上占用的空间大小。包括元素的高度、(可见的)水平滚动条的高度、上边框高度和下边框高度。
  • offsetWidth:元素在水平方向上占用的空间大小。包括元素的宽度、(可见的)垂直滚动条的宽度、左边框宽度和右边框宽度。
  • offsetLeft:元素的左外边框到包含元素的左内边框之间的像素距离。
  • offsetTop:元素的上外边框到包含元素上内边框之间的像素距离。

其中,offsetLeftoffsetTop属性与包含元素有关,包含元素的引用保存在offsetParent属性中。offsetParent属性不一定与parentNode的值相等。

客户区大小

元素的客户区大小(clinet dimension),指的是元素内容及其内边距所占据的空间大小。

  • clientWidth:元素内容区宽度 + 左右padding
  • clientHeight:元素内容区高度 + 上下padding

滚动大小

元素的滚动大小(scroll dimension),指的是滚动内容的元素的大小。有些元素(例如<html>元素)即使没有执行任何代码也能自动地添加滚动条。

  • scrollHeight:在没有滚动条的情况下,元素内容的总高度。
  • scrollWidth:在没有滚动条的情况下,元素内容的总宽度。
  • scrollLeft:被隐藏在内容区域左侧的像素数(水平滚动条滚动了多少的值)。
  • scrollTop:被隐藏在内容区域上方的像素数(垂直滚动条滚动了多少的值)。

确定元素大小

IE、Firefox3+、Safari4+、Opera9.5及Chrome为每个元素都提供了一个getBoundingClientRect()方法。这个方法返回一个矩形对象,包含4个属性:lefttoprightbottom。这些属性给出了元素在页面中相对于视口位置。但是,浏览器的实现稍有不同。

遍历

“DOM2级遍历和范围”模块定义了两个用于辅助完成顺序遍历DOM结构的类型:NodeIterator和TreeWalker。这两个类型能够基于给定的起点对DOM结构执行深度优先(depth-first)的遍历操作。IE不支持DOM遍历。

js
var supportsTraversals = document.implementation.hasFeature('Traversal', '2.0'); var supportsNodeIterator = (typeof document.createNodeIterator === 'function'); var supportsTreeWalker = (typeof document.createTreeWalker === 'function');

NodeIterator

NodeIterator类型是两者中比较简单的一个,可以使用document.createNodeIterator()方法来创建它的新实例。这个方法接受4个参数:

  • root:作为搜索起点的节点。
  • whatToShow:要访问哪些节点的数字代码。
  • filter:过滤函数。
  • entityReferenceExpansion:布尔值,表示是否要扩展实体引用。这个参数在HTML页面中没有用,因为其中的实体引用不能扩展。

whatToShow参数是一个位掩码,通过应用一个或多个过滤器(filter)来确定要访问哪些节点。这个参数的值以常量形式在NodeFilter类型中定义:

  • NodeFilter.SHOW_ALL:所有类型的节点。
  • NodeFilter.SHOW_ELEMNT:元素节点。
  • NodeFilter.SHOW_ATTRIBUTE:特性节点。
  • NodeFilter.SHOW_TEXT:文本节点。
  • NodeFilter.SHOW_CDATA_SECTION:CDATA节点。对HTML页面没有用。
  • NodeFilter.SHOW_ENTITY_REFERENCE:实体引用节点。对HTML页面没有用。
  • NodeFilter.SHOW_ENTITYE:实体节点,对HTML页面没有用
  • NodeFilter.SHOW_PROCESSION_INSTRUCTION:处理指令节点,对HTML页面没用。
  • NodeFilter.SHOW_COMMENT:注释节点。
  • NodeFilter.SHOW_DOCUMENT:文档节点。
  • NodeFilter.SHOW_DOCUMENT_TYPE:文档类型节点。
  • NodeFilter.SHOW_DOCUMENT_fRAGMENT:文档片段节点。对HTML没有用。
  • NodeFilter.SHOW_NOTATION:符号节点。对HTML页面没有用。
js
// filter可以是一个NodeFilter对象,对象只有一个方法,acceptNode() var filter = { acceptNode: function (node) { return node.tagName.toLowerCase() === 'p' ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP; } } // filter也可以是一个函数 var filter = function (node) { return node.tagName.toLowerCase() === 'p' ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP; } var iterator = document.createNodeIterator(root, NodeFilter.SHOW_ELEMENT, filter, false);

NodeIterator类型的两个主要方法是nextNode()previousNode()

在刚刚创建的NodeIterator对象中,有一个内部指针指向根节点,因此第一次调用nextNode()会返回根节点。当遍历到DOM子树的最后一个节点时,nextNode()会返回null。previousNode()的工作机制类似,当遍历到DOM子树的最后一个节点,且previousNode()返回根节点之后,再次调用会返回null。

html
<div id="div1"> <p><b>Hello</b> world!</p> <ul> <li>List Item 1</li> <li>List Item 2</li> <li>List Item 3</li> </ul> </div>
js
var div = document.getElementById('div1'); var iterator = document.createNodeIterator(div, NodeFilter.SHOW_ELEMENT, null, false); var node = iterator.nextNode(); while (node !== null) { alert(node.tagName); node = iterator.nextNode(); }

TreeWalker

TreeWalker是NodeIterator的一个更高级的版本。除了包括nextNode()previousNode()在内的相同功能之外,这个类型还提供了下列用于不两只方向上遍历DOM结构的方法:

  • parentNode()
  • firstChild()
  • lastChild()
  • nextSibling()
  • previousSibling()

创建TreeWalker对象要使用document.createTreeWalker()方法,这个方法接受的参数与document.createNodeIterator()方法一样。

范围

为了让开发人员更方便地控制页面,“DOM2级遍历和范围”模块定义了“范围”(range)接口。通过范围可以选择文档中的一个区域,而不必考虑节点的界限(选择在后台完成,对用户是不可见的)。Firefox、Opera、Safari和Chrome都支持DOM范围。IE以专有方式实现了自己的范围特性。

DOM中的范围

DOM2级在Document类型中定义的createRange()方法。在兼容DOM的浏览器中,这个方法属性document对象

js
var supportsRange = document.implementation.hasFeature('Range', '2.0'); var alsoSupportsRange = (typeof document.createRange() === 'function');

与节点类似,新创建的范围也直接与创建它的文档关联在一起,不能用于其他文档。创建了范围之后,接下来就可以使用它在后台选择文档中的特定部分。而创建范围并设置了其他位置之后,还可以针对范围的内容执行很多种操作,从而实现对底层DOM树的更精细的控制。

每个范围由一个Range类型的实例表示,这个实例拥有很多属性和方法。下列属性提供了当前范围在文档中的位置信息:

  • startContainer:包含范围起点的节点(即选区中第一个节点的父节点)。
  • endContainer:包含范围终点的节点(即选区中最后一个节点的父节点)。
  • startOffset:范围在startContainer中起点的偏移量。如果startContainer是文本节点、注释节点或CDATA节点,那么startOffset就是范围起点之前跳过的字符数量。否则就是范围中第一个子节点的索引。
  • endOffset:范围在endContainer中终点的偏移量。
  • commonAncestorContainerstartContainerendContainer共同的祖先节点在文档树中位置最深的那个。

在把范围放到文档中特定的位置时,这些属性都会被赋值。

用DOM范围实现简单选择

要使用范围来选择文档中的一部分,最简单的方式就是使用selectNode()selectNodeContents()。这两个方法都接受一个参数,即一个DOM节点,然后使用该节点中的信息来填充范围。其中,selectNode()方法选择整个节点,包括其子节点;而selectNodeContents()方法则只选择节点的子节点。

html
<!DOCTYPE html> <html> <body> <p id="p1"><b>Hello</b> world!</p> </body> </html>

我们可以使用下列代码来创建范围:

js
var range1 = document.createRange(), range2 = document.createRange(), p1 = document.getElementById('p1'); range1.selectNode(p1); range1.selectNodeContents(p1);

为了更精细地控制将哪些节点包含在范围中,还可以使用下列方法:

  • setStartBefore(refNode):将范围的起点设置在refNode之前,因此refNode也就是范围选区中的第一个子节点。同时会将startContainer属性设置为refNode.parentNode,将offset属性设置为refNode在其父节点的childNodes集合中的索引。
  • setStartAfter(refNode):将范围的起点设置在refNode之后,因此refNode也就不在范围之内了,其下一个同辈节点才是范围选区中的第一个子节点。
  • setEndBefore(refNode)
  • setEndAfter(refNode)

用DOM范围实现复杂选择

要创建复杂的范围就得使用setStart()setEnd()方法。这两个方法都接受两个参数:一个是参照节点和一个偏移量值。对setStart()而言,参照节点会变量startContainer,而偏移量值会变成startOffset。对于setEnd()来说,参照节点会变成endContainer,而偏移量会变成endOffset

可以使用这两个方法来模仿selectNode()selectNodeConents()

js
var range1 = document.createRange(), range2 = document.createRange(), p1 = document.getELementById('p1'), p1Index = -1, i, len; for (i = 0, len = p1.parentNode.childNodes.length; i < len; i++) { if (p1.parentNode.childNodes[i] === p1) { piIndex = i; break; } } range1.setStart(p1.parentNode, p1Index); range1.setEnd(p1.parentNode, p1Index + 1); range2.setStart(p1, 0); range2.setEnd(p1, p1.childNodes.length);

假设你只想选择前面HTML代码救命中从"hello"的"llo"到"world"的"o"。

js
var p1 = document.getElementById('p1'), helloNode = p1.firstChild.firstChild, worldNode = p1.lastChild; var range = document.createRange(); range.setStart(helloNode, 2); range.setEnd(worldNode, 3);

操作DOM范围中的内容

在创建范围时,内部会为这个范围创建一个文档片段,范围所属的全部节点都被添加到这个文档片段中。为了创建这个文档片段,范围内容的格式必须正确有效。在前面的例子中,我们创建的街区分别开始和结束于两个文本节点的内部,因此不能算是格式良好的DOM结构,也就无法通过DOM来表示。但是,范围知道自身缺少哪些开标签和闭标签,它能够重新构建有效的DOM结构以便我们对其进行操作。

对于前面的例子而言,范围经过计算知道选区中缺少一个开始的<b>标签,因此就会在后台动态加入一个该标签,同时还会在前面加入一个表示结束的</b>标签以结束"He"。于是,修改后的DOM就变成了如下所示:

html
<p><b>He</b><b>llo</b> world!</p>

另外,文本节点"world"也被拆分成两个文本节点:“wo"和"rld!”。

像这样创建了范围之后,就可以使用各种方法对范围的内容进行操作了(注意,表示范围的内部文档片段的所有节点,都只是指向文档中相应节点的指针)。

deleteContents()

第一个方法就是deleteContents()。这个方法能够从文档中删除范围所包含的内容。

js
var p1 = document.getElementById('p1'), helloNode = p1.firstChild.firstChild, worldNode = p1.lastChild; var range = document.createRange(); range.setStart(helloNode, 2); range.setEnd(worldNode, 3); range.deleteContents();

执行以上代码后,页面中会显示如下HTMl代码:

html
<p><b>He</b>rld!</p>

由于范围选区在修改底层DOM结构时能够保证格式良好,因此即使内部被删除了,最终的DOM结构依旧是格式良好的。

extractContents()

deleteContents()方法相似,extractContents()也会从文档中移除范围选区。这两个方法的区别在于:extractContents()会返回文档片段。利用这个返回值,可以将范围的内容插入到文档中其他的地方。

js
var p1 = document.getElementById('p1'), helloNode = p1.firstChild.firstChild, worldNode = p1.lastChild; var range = document.createRange(); range.setStart(helloNode, 2); range.setEnd(worldNode, 3); var fragment = range.extractContents(); p1.parentNode.appendChild(fragment);
html
<p><b>He</b>rld!</p> <b>llo</b> wo

cloneContents()

还有一种做法,即使用cloneContents()创建范围对象的一个副本,然后在文档的其他地方插入该副本。

js
var p1 = document.getElementById('p1'), helloNode = p1.firstChild.firstChild, worldNode = p1.lastChild; var range = document.createRange(); range.setStart(helloNode, 2); range.setEnd(worldNode, 3); var fragment = range.cloneContents(); p1.parentNode.appendChild(fragment);
html
<p><b>Hello</b> world!</p> <b>llo</b> wo

插入DOM范围中的内容

insertNode()

利用范围,可以删除或复制内容,还可以插入内容。使用insertNode()方法可以向范围选区的开始处插入一个节点。

js
var p1 = document.getElementById('p1'), helloNode = p1.firstChild.firstChild, worldNode = p1.lastChild; var range = document.createRange(); range.setStart(helloNode, 2); range.setEnd(worldNode, 3); var span = document.createElement('span'); span.style.color = 'red'; span.appendChild(document.createTextNode('Inserted text')); range.insertNode(span);
html
<p id="p1"><b>He<span style="color: red">Inserted text</span>llo</b> world!</p>

surroundContents()

除了向范围内部插入内容之外,还可以环绕范围插入内容,此时就要使用surroundContents()方法。这个方法接受一个参数:即环绕范围内容的节点。在环绕范围插入内容时,后台会执行下列步骤:

  • 提取出范围中的内容;
  • 将给定节点插入到文档中原来范转所在的位置上;
  • 将文档片段的内容添加到给定的节点中。

可以使用这种技术突出显示网页中的某些语句。

js
var p1 = document.getElementById('p1'), helloNode = p1.firstChild.firstChild, worldNode = p1.lastChild; var range = document.createRange(); range.setlectNode(helloNode); var span = document.createElement('span'); span.style.backgroundColor = 'yellow'; range.surroundContents(span);
html
<p><b><span style="background-color: yellow">Hello</span></b> world!</p>

为了插入<span>,范围必须包含整个DOM选区(不能仅仅包含选中的DOM节点)。

折叠DOM范围

所谓折叠范围,就是指范围中未选择文档的任何部分。可以用文本框来描述折叠范围的过程。假设文本框中有一行文本,你用鼠标选择了其中一个完整的单词。然后,你单击鼠标左键,选区消失,而光标则落在了其中两个字母之间。同样,在折叠范围时,其位置会落在文档中的两个部分之间。

使用collapse()方法来折叠范围,这个方法接受一个参数,一个布尔值,表示要折叠到范围的哪一端。参数true表示折叠到范围的起点,false表示终点。要确定范围已经折叠完毕,可以检查collapsed属性

js
range.collapse(true); // 折叠到起点 alert(range.collapsed); // true

检测某个范围是否处于折叠状态,可以帮我们确定范围中的两个节点是否紧密相邻的。例如,对于下面的HTML代码:

html
<p id="p1">Paragraph 1</p><p id="p2">Paragraph 2</p>

如果我们不知道其实际构成(比如说,这段代码是动态生成的),那么可以像下面这样创建一个范围。

js
var p1 = document.getElementById('p1'), p2 = document.getElementById('p2'), range = document.createRange(); // 选择p1结束p2开始的位置 range.setStartAfter(p1); range.setEndBefore(p2); alert(range.collapsed); // 输出true,表示两个节点是相邻的

比较DOM范围

在有多个范围的情况下,可以使用compareBoundaryPoints()方法来确定这些范围是否有公共的边界(起点或终点)。这个方法接受两个参数:表示比较方式的常量和要比较的范围。表示比较方式的常量值如下:

  • Range.START_TO_START(0):比较第一个范围和第二个范围的起点;
  • Range.START_TO_End(1):比较第一个范围的起点和第二个范围的终点;
  • Range.End_TO_End(2):比较第一个范围的终点和第二个范围的终点;
  • Range.End_TO_START(3):比较第一个范围的终点和第二个范围的起点;

compareBoundaryPoints()方法可能的返回值如下:如果第一个范围中的点位于第二个范围中的点之前,返回-1;两个点位置相同返回0,之后返回1。

js
var p1 = document.getELementById('p1'), range1 = document.createRange(), range2 = document.createRange(); range1.selectNodeContents(p1); range2.selectNodeContents(p2); range2.setEndBefore(p1.lastChild); alert(range1.compareBoundaryPoints(Range.START_TO_START, range2)); // 0 alert(range1.compareBoundaryPoints(Range.END_TO_END, range2)); // 1

复制DOM范围

可以使用cloneRange()方法复制范围。这个方法会创建调用它的范围的一个副本。

js
var newRange = range.cloneRange();

新创建的范围与原来的范围包含相同的属性,而修改它的端点不会影响原来的范围。

清理DOM范围

在使用完范围之后,最好是调用detach()方法,以便从创建范围的文档中分离出该范围。调用detach()之后,就可以放心地解除对范围的引用,从而让垃圾回收机制回收其内存了。

js
range.detach(); // 从文档中分离。 range = null; // 解除引用

在使用范围的最后再执行这两个步骤是我们推荐的方式。

IE8及更早版本中的范围

IE8及早期版本支持一种类似的概念,即文本范围。方本范围是IE专有的特性,其他浏览器都不支持。顾名思义,文本范围处理的主要是文本(不一定是DOM节点)。通过<body><button><input><textarea>等几个元素,可以调用createTextRange()方法来创建文本范围:

js
var range = document.body.createTextRange();

用IE范围实现简单的选择

findText()方法,会找到第一次出现的给定文本,并将范围移过来以环绕该文本。如果没有找到文本,这个方法返回false;否则返回true。

html
<p id="p1"><b>Hello</b> world!</p>

要选择"Hello",可以使用下面的代码:

js
var range = document.body.createTextRange(); var found = range.findText('Hello');

还可以为findText()传入另一个参数,即一个表示向哪个方向继续搜索的数值。负值表示应该从当前位置向后搜索,而正值表示应该从当前位置向前搜索。

js
var found = range.findText('Hello'); var foundAgain = range.findText('Hello', 1);

IE中与DOM中的selectNode()方法最接近的方法是moveToElementText(),这个方法接受一个DOM元素,并选择该元素的所有文本,包括HTML标签。

js
var range = document.body.createTextRange(); var p1 = document.getElementById('p1'); range.moveToElementText(p1);

在文本范围中包含HTML的情况下,可以使用htmlText属性取得范围的全部内容,包括HTML和文本:

js
alert(range.htmlText);

IE的范围没有任何属性可以随着范围选区的变化而动态更新。不过,其parentElement()方法倒是与DOM的commonAncestorContainer属性类似。

js
var ancestor = range.parentElement();

使用IE范围实现复杂的选择

在IE中创建复杂范围的方法,就是以特写的增量向四周移动范围。为此,IE提供了4个方法:move()moveStart()moveEnd()enpand()。这些方法都接受两个参数:移动单位和移动单位的数量。其中,移动单位是下列一种字符串量。

  • character:逐个字符地移动。
  • world:逐个单词(一系列非空格字符)地移动。
  • sentence:逐个句子(一系列以名句、问号或叹号结尾的字符)地移动。
  • textedit:移动到当前范围选区的开始或结束位置。

通过moveStart()方法可以移动范围的起点,通过moveEnd()可以移动范围的终点。

使用enpand()方法可以将范围规范化。换句话说,expand()方法的作用是将任何部分选择文本全部选中。例如,当前选择的是一个单词中的两个字符,调用expand('word')可以将整个单词包含在范围之内。

move()方法则首先会折叠当前范围(让起点和终点相等),然后再将范围移动指定的单位数量。调用move()之后,范围的起点和终点相同,因此必须再使用moveStart()moveEnd()创建新的选区。

操作IE范围中的内容

在IE中操作范围中的内容可以使用text属性pasteHTML()方法。通过text属性可以取得范围中的内容文本,也可以通过这个属性设置范围中的文本。

js
range.text = 'Hi';

要向范围中插入HTML代码,就得使用pasteHTML()方法。

js
range.pasteHTML('<em>Hi</em>');

不过,在范围中包含HTML代码时,不应该使用pasteHTML(),因为这样很容易导致不可预料的结果——很可能是格式不正确的HTML。

折叠范围

IE为范围提供的collapse()方法与相应的DOM方法用法一样。可惜的是IE并没有对应的collapsed属性让我们知道范围是否已经折叠完毕。为止,必须使用boundingWidth属性,该属性返回范围的宽度(以像素为单位)。

此处还有boundingHeightboundingLeftboundingTop等属性,虽然它们都不像boundingWidth那么有用,但也可以提供一些有关范围位置的信息。

比较IE范围

IE中的compareEndPoints()方法与DOM范围的compareBoundaryPoints()方法类似。这个方法接受两个参数:比较的类型和要比较的范围。比较类型的取值范围是下列几个字符串值:StartToStartStartToEndEndToEndEndToStart

IE中还有两个方法,也是用于比较范围的:isEqual()用于确定两个范围是否相等,inRange()用于确定一个范围是否包含另一个范围。

复制IE范围

在IE中使用duplicate()方法可以复制文本范围,结果会创建原范围的一个副本。