DOM2 和 DOM3
本章内容:DOM2 和 DOM3 的变化、操作样式和 DOMAPI、DOM遍历与范围
DOM1 级主要定义的是HTML 和 XML 文档的底层结构。DOM2 和 DOM3 级则在这个结构的基础上引入了更多的交互功能,也支持了更高级的XML特性。
DOM2 和 DOM3 的模块
- 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,满足操作文档的需求,同时提供更好的错误处理及特性检测能力。
为了确定浏览器是否支持这些DOM模块,可以使用 hasFeature() 来检测它们(这个方法在上一章提到过。)
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')
1.1、针对XML 命名空间的变化
有了XML命名空间,不同XML文档的元素就可以混合在一起,共同构成格式良好的文档。HTML不支持XML命名空间,但XHTML支持XML命名空间。以下给出实例中,皆为XHTML文档格式
命名空间要使用xmlns特性来制定,并应将其包含在<html>元素中。
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Example XHTML page</title>
</head>
<body>
Hello World
</body>
</html>
在这个例子中,所有的元素都默认被视为XHML 命名空间的元素。要明确地为XML命名空间创建前缀,可以使用xmlns 后跟冒号,再跟前缀。
<xhtml:html xmlns:xhtml="http://www.w3.org/1999/xhtml">
<xhtml:head>
<xhtml:itle>EX XHTML Page</xhtml:title>
</xhtml:head>
<xhtml:body>
Hello World
</xhtml:body>
</xhtml:html>
有时候为了避免不同语言间的冲突,也需要使用命名空间来限定特性。
<xhtml:html xmlns:xhtml="http://www.w3.org/1999/xhtml">
<xhtml:head>
<xhtml:title>EX XHTML Page</xhtml:head>
</xhtml:head>
<xhtml:body xhtml:class="home">
Hello World
</xhtml:body>
</xhtml:html>
在只基于一种语言编写XML文档的情况下,命名空间实际上也没有什么用。不过,在混合使用两种语言的情况下,命名空间的用处就非常大了。
比如下面这个混合了 XHML 和 SVG 语言的文档。
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Ex XHTML Page</title>
</head>
<body>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1"
viewBox="0 0 100 100" style="width: 100%;height: 100%">
<react x="0" y="0" width="100" height="100" style="fill:red" />
</svg>
</body>
</html>
通过设置命名空间,将<svg>标识为了与包含文档无关的元素。<svg> 所有子元素以及所有的特性,都被认为是 http://www.w3.org/2000/svg 命名空间
1.1.1、Node类型的变化
在 DOM2 级中, Node 类型包含下列特定命名空间的属性。
- localName: 不带命名空间前缀的节点名称
- namespaceURL:命名空间 URL 或者(在未指定的情况下是)null。
- prefix:命名空间前缀或者(在未指定的情况下是)null
当节点使用命名空间前缀时,其nodeName等于 prefix+ ":" + localName。
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Ex XHTML Page</title>
</head>
<body>
<s:svg xmlns:s="http://www.w3.org/200/svg" version="1.1"
viewBox="0 0 100 100" style="width: 100%; height: 100%">
<s:react x="0" y="0" width="100" height="100" style="fill:red" />
</s:svg>
</body>
</html>
以上面这段代码为例。
- 对 <html> 来说
- localName 和 tagName 是 "html",
- namespaceURI 是 "http://www.w3.org/1999/xhtml"
- prefix 是 null
- 对 <svg> 来说
- localName 是 "svg"
- tagName 是 "s:svg"
- namespaceURI 是 "http://www.w3.org/2000/svg"
- prefix 是 "s"
DOM3 级在此基础上更进一步,又引入了下列与命名空间相关的方法。
- isDefaultNa mespace(namespaceURI):在指定的 namespaceURI是当前节点的默认命名空间的情况下返回 true。
- lookupNamespaceURI(prefix):返回给定 prefix 的命名空间。
- lookupPrefix(namespaceURI):返回给定 namespaceURI的前缀。
针对前面的 代码调用 以上API
console.log(document.body.isDefaultNamespace('http://www.w3.org/1999/xhtml')) // true
var svg = document.getElementsByTagName('s:svg')[0] // 获取 svg的 引用
console.log(svg.lookupPrefix('http://www.w3.org/2000/svg')) // s
console.log(svg.lookupNamespaceURI('s')) // http://www.w3.org/2000/svg
1.1.2、Document 类型的变化
DOM2 级中的 Document 类型 包含下列与命名空间相关的方法:
- createElementNS(namespaceURI, tagName):使用给定的 tagName 创建一个属于命名空间 namespaceURI 的新元素
- createAttributeNS(namespaceURI, attributeName):使用给定的 attributeName 创建一个属于命名空间 namespaceURI 的新特性。
- getElementsByTagNameNS(namespaceURI, tagName):返回属于命名空间 namespaceURI 的tagName 元素的 NodeList
// 创建一个 新的 SVG 元素
var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
// 创建一个属于 某个命名空间 的新特性。
var att = document.createAttributeNS('http://www.somewhere.com', 'radom')
// 取得所有 XHTML 元素
var eles = document.getElementsByTagNameNS('http://www.w3.org/1999/xhml', '*')
只有在文档中存在两个或多个命名空间时,这些与命名空间有关的方法才是必须的。
1.1.3、Element类型
DOM2级核心 中有关Element的变化,主要涉及操作特性。新增的方法如下。
- getAttributeNS(namespaceURI, localName):取得属于命名空间 namespaceURI 且 名为 localName 的特性
- getAttributeNodeNS(namespaceURI,localName):取得属于命名空间 namespaceURI 且名为 localName的特性节点。
- getElementsByTagNameNS(namespaceURI, tagName):返回属于命名空间 namespaceURI 的tagName 元素的 NodeList
- hasAttributeNS(namespaceURI, localName):确定当前元素是否有一个名为 localName 的特性,而且该特性的命名空间是 namespaceURI。注意:"DOM2 级核心"也增加了一个 hasAttribute() 方法,用于不考虑命名空间的情况。
- removeAttributeNS(namespaceURI, localName):删除属于命名空间 namespaceURI 且名为 localName 的特性
- setAttributeNS(namespaceURI, qualifiedName, value):将属于命名空间 namespaceURI 且名为 qualifiedName 的特性 将其值设置为 value
-
setAttributeNodeNS(attNode):设置属于命名空间namespaceURI的特性节点。
除了第一个参数外,这些方法与 DOM1级中相关方法的作用相同;第一个参数始终都是一个命名空间URI
1.1.4、NamedNodeMap 类型的变化
NameNodeMap 类型也新增了下列与命名空间有关的方法。由于特性是通过 NameNodeMap 表示的,因此这些方法多少情况下只针对特性使用。
- getNamedItemNS(namespaceURI, localName):取得属于命名空间 namespaceURI 且名为 localName 的项。
- removeNamedItemNS(namespaceURI, localName):移除属于命名空间 namespaceURI 且民委 localName 的项。
- setNamedItemNS(node):添加 node,这个节点已经事先指定了 命名空间的信息。
由于一般都是通过 元素 访问特性,所以这些方法很少使用。
1.2、其他方面的变化
这些变化与 XML 命名空间无关,而是更倾向于确保 API 的可靠性及完整性。
1.2.1、DocumentType 类型的变化
DoucmentType 类型新增了 3个属性:publicId、ststemId 和 internalSubset。前两个属性表示的是文档类型声明中的两个信息段。
<!DOCTYPE HTML PUBLIC "-//W3C/DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
console.log(document.doctype.publicId) // -//W3C/DTD HTML 4.01//EN
console.log(document.doctype.systemId) // http://www.w3.org/TR/html4/strict.dtd
internalSubset 用于访问包含在文档类型中的额外定义。在HTML中极少用到,在XML中常用一些。
1.2.2、Document 类型的变化
Document类型变化中唯一于 命名空间无关的方法是 importNode(),这个方法的用途是从一个文档中取得一个节点,然后将其导入到另一个文档,使其成为这个这个文档的一部分。需要注意的是,每个节点都有一个 ownerDocument 属性,表示所属文档。
如果调 appendChild() 是传入的节点属于 不同文档(ownerDocument的值不一样),则会导致错误。在调用 importNode()时传入不同文档的节点则会返回一个新节点,这个新节点的所有权归当前文档所有。
importNode() 方法和 cloneNode() 方法非常相似,接受两个参数:要复制的节点、是否复制子节点,返回原来节点的副本。
var newNode = document.importNode(oldNode, true) // 深复制,复制子节点
document.body.appendChild(newNode)
同样这个方法在HTML中并不常用,在XML中使用较多。
“DOM2级视图” 模块添加了一个 名为 defaultView 的属性,其中保存着一个指针,指向拥有给定文档窗口(或框架)。IE不支持此属性,IE中 的等价属性时 prentWIndow(Opera也支持这个属性)。
确定文档的归属窗口
var parentWindow = document.defaultView || document.parentWindow
"DOM2级核心" 还为 docunebt.implementation 对象规定了两个新方法:
-
createDocumentType(): 用于创建一个 新的 DocumentType 节点,接 受以下参数:
- 文档类型名称
- publicId
- systemId
var doctype = document.implementation.createDocumentType('html', '-//W3C/DTD HTML 4.01//EN', 'http://www.w3.org/TR/html4/strict.dtd')
由于既有文档类型不能改变,因此这个方法只在创建新文档时有用
- createDocument():用于创建一个新文档,接受三个参数:
- 针对文档元素的 namespaceURI
- 文档元素的标签名
- 文档类型
// 创建一个新的XML文档
var xmlDoc = document.implementation.createDocument('', 'root', null)
// 创建一个XHML 文档
// 这里的 doctype 引用 上面例子中 通过 createDocumentType创建的
var xhtmlDoc = document.implementation.createDocument('http://www.w3.org/1999//xhtml', 'html', doctype)
"DOM2级HTML" 模块 页为 document.implementation 新增了一个方法,createHTMLDocument(),改方法创建一个完整的 HTML 文档。接受一个参数:即新创建文档的标题(<title>中间的文本)。
var htmlDoc = document.implementation.createHTMLDocument('New Doc')
通过 createHTMLDocument() 创建的这个文档,是HTMLDocument类型的实例,具有该类型的所有属性和方法包括 title 和 body 属性。 这个方法存在兼容问题
1.2.3、Node类型的变化
添加了 isSupported() 方法。与 DOM1级的 hasFeature() 方法类似。isSupported() 方法用于确定当前节点具有什么能力。接受两个参数:特性名 和 特性版本号;返回一个布尔值。
if (document.body.isSupported('HTML', '2.0')) {
// todo
}
与 hasFeature() 类似,建议在 确定某个特性是否可用时,最好还是使用能力检测
DOM3级 引入了 两个辅助比较节点的方法:isSameNode() 和 isEqualNode()。这两个方法都接受一个节点参数,
- isSameNod():传入节点与引用节点相同时(两个节点引用的是同一个对象)返回 true。
- isEqualNode():传入节点与引用节点相等时(相同的类型,具有相等的属性,并且 attributes 和 childNodes 也相等)返回true
var div1 = document.createElement('div')
div.setAttribute('class', 'box')
var div2 = document.createElement('div')
dvi2.setAttribute('class', 'box')
console.log(div1.isSameNode(div1)) // true
console.log(div1.isEqualNode(div2)) // true
// 引用的不是同一个对象,不相同
console.log(div1.isSameNode(div2)) // false
1.2.4、框架的变化
DOM2级中有一个新属性:contentDocument,这个属性包含一个指针,指向表示框架内容的文档对象。IE8之前不支持 这个属性。但支持一个 contentWindow 的属性,该属性返回框架的 window 对象,而这个window 对象又有一个 document 属性。
// 访问内嵌框架的文档对象
var iframe = document.getElementById('myIframe')
var iframeDoc = iframe.contentDocument || iframe.contentWindow.document
所有浏览器都支持 contentWindow属性
二、样式
"DOM2级样式"围绕样式机制提供了一套API。可以通过 hasFeature() 检测是否支持。
var supportsDOM2CSS = document.implementation.hasFeature('CSS', '2.0')
var supportsDOM2CSS2 = document.implementation.hasFeature('CSS2', '2.0')
2.1、访问元素的样式
任何支持style 特性的 HTML 元素在 JavaScript中都有对应的 style 属性。这个 style 对象是 CSSStyleDeclaration 的实例。包含HTML的style 特性指定的所有样式信息。
css 中的 '-' 在JavaScript中要变成 驼峰式,比如:background-color = backgroundColor
var myDiv = document.getElementById('div')
// 设置背景颜色
myDive.style.backgroundColor = 'cyan'
// 改变大小
myDiv.style.width = '200px'
// 指定边框
myDiv.style.border = '1px solid black'
同时通过 style 对象也可以获取 style特性中指定的 样式
// 获取背景色
console.log(myDiv.style.backgroundColor)
2.1.1、DOM样式属性和方法
"DOM2级样式"规范还未 style 对象定义了一些属性和方法。
- cssText:能够访问到 style 特性中的 css代码
- length:应用元素的CSS属性的数量
- parentRule:表示CSS信息的CSSRule对象
- getPropertyPriority(propertyName):如果给定的属性使用了 !important 设置,则返回 'important';否则,返回空字符串
- getPropertyValue(propertyName):返回给定属性的字符串值。
- item(index):返回给定位置的CSS属性的名称。
- removeProperty(propertyName):从样式中删除给定属性
- setProperty(propertyName, value, priority):将给定属性设置为相应的值,并加上优先权标志('important'或一个空字符串)。
通过 cssText属性可以访问style 特性中的代码。并且如果通过 cssText 修改 特性,会重写整个 style 特性的值。
// 设置
myDiv.style.cssText = 'width: 24px; height: 30px'
// 获取
console.log(myDive.style.cssText)
length属性 与 item()方法配套使用,以便在迭代元素中定义的css属性,在使用length 和 item() 时,style 对象实际上就相当于一个集合,可以使用方括号语法来代替item()
for (var i = 0, len = myDiv.style.length; i < len; i++) {
console.log(myDiv.style[i]) // myDiv.style.item(i)
}
通过如上遍历可以取得所有的特性名,然后通过 getPropertyValue() 获取获取每一个特性值
var prop, value, i, len
for(i = 0, len = myDiv.length; i < len; i++) {
prop = myDiv.style.item(i)
value = myDiv.style.getPropertyValue(prop)
console.log(prop + ": " + value )
}
getPropertyValue() 返回的始终是CSS属性值的字符串表示。
使用getPropertyCSSValue()方法,返回一个对象。这个对象又两个属性:
- cssText
这个属性的值与 getPropertyValue() 返回的值相同 - cssValueType
返回一个数值常量:- 0:表示继承的值
- 1:表示基本的值
- 2:表示值列表
- 3:表示自定义的值
var i, prop, value, len
for(i = 0, len = myDiv.length; i < len; i++) {
prop = myDiv.style.item(i)
value = myDiv.style.getPropertyCSSValue(prop) // 获取 CSSValue 对象
console.log(prop + ': ' + value.cssText + '(' + value.cssValueType + ')')
}
要从元素的样式中移除某个 CSS 属性,需要使用 removePropertyValue() 方法。这个方法移除一个属性
myDiv.style.removeProperty('border')
2.1.2、计算的样式
"DOM2级样式"增强了 document.defaultView,提供了 getComputedStyle() 方法。这个方法接受两个参数:要取得计算样式的元素、一个伪元素字符串(例如 :after)如果不需要伪元素可以设置为 null。 返回一个 CSSStyleDeclaration 对象。
<html>
<head>
<title>EX</title>
<style>
#div {
color: pink;
width: 20px;
height: 30px;
}
</style>
</head>
<body>
<div id="div" style="color: pink">
</body>
</html>
var myDiv = document.getElementById('div')
var computedStyle = document.defaulltView.getComputedStyle(myDiv, null)
console.log(computedStyle .color) // rgb(255, 192, 203)
console.log(computedStyle .width) // 20px
console.log(computedStyle .height) // 30px
IE不支持 getComputedStyle() 方法。 在IE中,每个具有 style 属性的元素还有一个 currentStyle 属性。这个属性是CSSStyleDeclaration的实例,包含当前元素全部计算后的样式。
var computedStyle = myDiv.currentStyle
console.log(computedStyle.width) // 20px
在所有浏览器中,所有计算后的样式都是只读的;不能修改计算和样式对象中的css属性。
2.2、操作样式表
CSSStyleSheet类型表示的是样式表,是一套只读的接口,CSSStyleSheet 继承自 StyleSheet,从 StyleSheet接口继承而来的属性如下。
- disabled: 表示样式表是否被禁用的布尔值。这个属性是 可读/写的,将这个属性设置为 true,从以禁用样式表。
- href:如果样式表是通过<link>包含的,则是样式表的URL;否则,是null
- media:当前样式表支持的所有媒体类型的集合。与所有 DOM 集合一样,这个集合也有一个length 属性和 一个 item() 方法。也可以使用方括号语法取得集合中特定的项。如果集合是空列表,表示样式表适用于所有媒体。在IE中,meida是一个反映 <link> 和 <style> 元素的 media 特性值的字符串。
- ownerNode:指向拥有当前样式表的节点的指针,样式表可能是在 HTML 中通过 <link> 或 <style> 引入的。如果当前样式表通过 @import 导入,则这个属性值为 null。IE不支持这个属性
- parentStyleSheet:在当前样式表是通过 @import 导入的情况下,这个属性是一个指向导入它的样式表的指针。
- title:ownerNode 中 title 属性的值。
- type:表示样式表类型的字符串。对CSS样式表而言,这个字符串是"type/css"
除了disable 属性之外,其他属性都是只读的。在支持以上所有这些属性的基础上,CSSStyleSheet还支持下列的属性和方法
- cssRules:样式表中包含的样式规则的集合。IE不支持这个属性,但有一个类似的rules属性
- ownerRule:如果样式表是通过@import 导入的,这个属性就是一个值正,指向表示导入的规则;否则,值为null。IE不支持这个属性。
- deleteRule(index):删除 cssRules 集合中指定位置的规则。IE不支持这个方法,但支持一个类似的 removeRule() 方法。
- insertRule(rule, index):向cssRules 集合中指定的位置插入 rule 字符串。IE不支持这个方法,类似的支持一个 addRule() 方法。
应用于文档的所有样式表是通过 document.styleSheets 集合来表示的`
var sheet = null
for (var i = 0, len=document.styleSheets.length; i < len; i++){ // 遍历 样式表
sheet = document.styleSheets[i]
console.log(sheet.href) // 访问 href 属性
}
不同浏览器的 document.styleSheets 返回的样式表也不同。所有浏览器都会包含<style>元素 和 rel 特性被设置为 ”stylesheet“的<link>元素引入的样式表。IE 和 Opera也包含 rel 被设置为 "alternate stylesheet"的<link>元素的引入的样式表。
也可以直接通过<link> 或 <style> 元素取得 CSSStyleSheet。DOM规定了一个包含 CSSStyleSheet 对象的属性,名叫 sheet;IE支持的是 styleSheet 属性。
// 在不同浏览器中取得 样式对象
function getStyleSheet(element) {
return element.sheet || element.styleSheet
}
// 取得第一个 <link> 元素引入的样式表
var link = document.getElementsByTagNames('link').item(0)
var sheet = getStyleSheet(link)
这里的 getStyleShee() 返回的样式表对象 于 document.styleSheets 集合中样式表对象相同。
2.2.1、CSS规则
CSSRule对象表示样式表中的每一条规则。实际上,CSSRule 是一个供其他多种类型继承的基类型,其中最常见的就是 CSSStyleSheet类型,表示样式信息。CSSStyleRule对象包含下列属性。
- cssText:返回整条规则对应的文本。由于浏览器对样式表的内部处理方式不同,返回的文本可能会于样式表中实际的文本不一样; Safari 始终都会将文本转换成全部小写。IE不支持这个属性。
- parentRule:如果当前规则是导入的规则,这个属性引用的就是导入的规则;否则,这个值为 null。IE不支持这个属性。
- parentStyleSheet:当前规则所属的样式表。IE不支持这个属性。
- selectorText:返回当前规则的选择符文本。由于浏览器对样式表的内部处理方式不同,返回的文本可能于样式表中实际的文本不一样。只读属性(除了Opera)
- style:一个CSSStyleDeclaration对象,可以通过它设置和取得规则中特定的样式值。
- type:表示规则类型的常量值。对于样式规则,这个值是1。IE不支持这个属性
其中最常用的是cssText、selectorText、style。
cssText 属性与 style.cssText 属性类似,但并不相同。前者包含选择符文本和围绕样式信息的花括号,后者只包含样式信息(类似于元素的 style.cssText)。此外,cssText是只读的,style.cssText 也可以被重写。
大多数情况下,使用style属性就可以满足所有操作样式规则的需求了。这个对象就像每个元素上的style属性一样,可以通过它读取和修改规则中的样式信息。
div.box {
background-color: blue;
width: 100px;
height: 200px
}
假设上面这条规则位于页面中的第一个样式表中
var sheet = document.styleSheets[0]
var rules = sheet.cssRules || sheet.rules // 取得规则列表
var rule = rules[0] // 取得第一条 规则
console.log(rule.selectorText) // div.box
console.log(rule.style.cssText) // 完整的CSS代码
console.log(rule.style.backgroundColor) // blue || rgb(..)
也可以像下面这样来修改信息
var sheet = document.styleSheets[0]
var rules = sheet.cssRules || sheet.rules
var rule = rules[0]
rule.style.backgroundColor = 'red'
需要注意的是,以这种方法修改会影响页面中适用该规则的所有元素
2.2.2、创建规则
inertRule()
要向现有样式表中 添加新规则,需要使用 inertRule()方法,这个方法接受两个参数;规则文本、表示在哪里插入的规则的索引:
sheet.insertRule('body { background-color: silver}', 0)
插入的规则将成为样式表中的第一条规则——规则的次序在确定层叠之后应用到文档的规则时至关重要。Firefox、Safari、Opera 和 Chrome 都支持 这个方法。
IE8及更早版本中,类似的支持 addRule() 方法;
- 接受两个必选参数:
- 选择符文本
- css样式信息
- 一个可选参数
- 插入的位置。
sheet.addRule('body', 'background-color: silver', 0) // 仅对IE有效
IE中说明,关于这个方法最多可以使用添加 4095条样式规则。超出这个上限的调用将会导致错误。
要跨越浏览器项样式表中插入规则,可以使用下面的函数。接收四个参数:要向其中添加规则的样式表、选择符文本、css样式信息、插入位置
function insertRule(sheet, selectorText, cssText, position) {
if (sheet.insertRule) {
sheet.insertRule(selectorText + '{' + cssText + '}', position)
} else if (sheet.addRUle) { // IE
sheet.addRule(selectorText, cssText, position)
}
}
insertRule(document.styleSheets[0], 'body', 'background-color: cyan', 0)
如果要添加较多的规则,这样的方法就会比较繁琐。因此,在需要大量添加规则的时候,推荐使用前面提到的 动态加载样式表的技术。
2.2.3、删除规则
从样式表中删除规则
deleteRule() 接受一个参数:要删除的规则的位置
IE支持removeRule()接受一个参数:要删除的位置。
function deleteRule(sheet, index) {
if (sheet.deleteRule) {
sheet.deleteRule(index)
} else if(sheet.removeRule) { // IE
sheet.removeRule(index)
}
}
deleteRule(document.styleSheets[0], 0)
与添加规则相似,删除规则也不是实际Web开发中常见的做法。
2.3、元素大小
最初DOM中没有规定如何确定页面中元素的大小。IE为此率先引入了一些属性,以便开发人员使用。目前所有主流浏览器都已经支持这些属性。
2.3.1、偏移量
元素的可见大小由其高度、宽度决定,包括所有内边距、滚动条和边框大小(注意,不包括外边距)。通过下列4给属性可以取得元素的偏移量
- offsetHeight:元素在垂直方向上占用的空间大小,以像素计。包括元素的高度 、(可见的)水平滚动条的高度、上边框高度和下边框高度。
- offsetWidth:元素在水平方向上占用的空间大小,以像素计。包括元素的宽度、(可见的)垂直滚动条的宽度、左边框宽度和右边框宽度。
- offsetLeft:元素的左外边框至包含元素的左内边框之间的像素距离。
- offsetTop:元素的上外边框包含元素的上内边框之间的像素距离。
其中,offsetLeft 和 offsetTop属性与包含元素有关,包含元素的引用保存在 offsetParent 属性中。offsetParent 和 parentNode的值不一样相等(父元素,不一定就是包含引用指向的元素)
要想知道某个元素在页面上的偏移量,将这个元素的offsetLeft 和 offsetTop 与其 offsetParent的相同属性相加,如此循环直至根元素就可以得到一个基本准确的值了。
// 到屏幕左边的偏移值
function getElementLeft(element) {
var actualLeft = element.offsetLeft
var current = element.offsetParent
while(current !== null) {
actualLeft += current.offsetLeft
current = current.offsetParent
}
return actualLeft
}
// 到屏幕顶部的偏移值
function getElementTop(element) {
if (element.offsetParent) {
return arguments.callee(current) + element.offsetTop
}
return element.offsetTop
}
对于简单的CSS布局的页面,这两个函数可以得到非常精确的结构。
2.3.2、客户区大小
元素的客户区大小,指的是元素内容及其内边距所占据的空间大小。有关客户区大小的属于又两个:
-
clientWidth
元素内容区宽度加上左右内边距宽度 -
clientHeight
元素内容区高度加上上下内边距高度
客户区大小.png
客户区大小就是元素内部的空间大小,因此滚动条占用的空间不计算在内。最常用到这些属性的情况,就是确定浏览器视口大小的时候。
function getViewport() {
if (document.compatMode == 'BackCompat') { // 混杂模式
return {
width: document.body.clientWidth,
height: document.body.clientHeight
}
} else {
return {
width: document.documentElement.clientWidth,
height: document.documentELement.clientHeight
}
}
}
2.3.3、滚动大小
滚动大小,指的是包含滚动内容的元素的大小。
- scrollHeight:在没有滚动条的情况下,元素内容的总高度。
- scrollWidth:在没有滚动条的情况下,元素内容的总高度。
- scrollLeft:被隐藏在内容区域左侧的像素数。通过设置这个属性可以改变元素的滚动位置。
- scrollTop:被隐藏在内容区域上方的像素数。通过设置这个属性可以改变元素的滚动位置。
scrollWidth 和 scrollHeight 主要用于确定元素内容的实际大小。对于不包含滚动条的页面而言,scrollWidth 和 scrollHeight 与 clientWidth 和 clientHeight 之间的关系并不十分清晰。在这种情况下,基于 document.documentElement 查看这些属性会在不同浏览器间发现一些不一致问题。
在确定文档的总高度时(包括基于视口的最小高度时),必须取得 scrollWidth / clientWidth 和 scrollHeight / clientHeight 中的最大值,才能保证在跨浏览器的环境下得到精确的结果。
var docHeight = Math.max(document.documentElement.scrollHeight, document.documentElement.clientHieght)
var docWidth = Math.max(document.documentElement.scrollWidth, document.documentElement.clientWidth)
对于运行在混杂模式下的IE,则需要用 document.body 代替 document.documentElement
通过 scrollLeft 和 scrollTop 属性既可以确定元素当前滚动的状态,也可以设置元素的滚动位置。
下面这个函数会检测元素是否位于顶部,如果不是就将其回滚到顶部
function scrollToTop(element) {
if (element.scrollTop != 0) element.scrollTop = 0
}
2.3.4、确定元素大小
浏览器为每个元素提供了一个 getBoundingClientRect()方法。这个方法返回一个矩形对象,包含四个属性:left、top、right、bottom这些属性给出了元素在页面中相对于视口的位置。
浏览器的实现稍微有点不同。IE8及更早版本认为文档的左上角坐标是(2, 2),而其他浏览器包括 IE9 则将传统的(0, 0)作为起点坐标。在IE8及更早版本中,会返回(2, 2), 而其他浏览器返回(0, 0)
// 元素距离当前视口的精确距离
function getBoundingClientRect(element) {
// 是否为初始化(是否定义过),避免多次执行
if (typeof arguments.callee.offset != 'number') {
var scrollTop = document.documentElement.scrollTop // 文档滚动的高度
// 创建元素 测试左上角坐标
var temp = document.createElement('div')
temp.style.cssText = 'position:absolute; left: 0; top: 0;'
document.body.appendChild(temp)
// 计算 文档左上角的坐标
arguments.callee.offset = -temp.getBoundingClientRect().top - scrollTop
// 移除元素
document.body.removeChild(temp)
// 回收
temp = null
}
var rect = element.getBoundingClientRect() // 距离当前视口的距离
var offset = arguments.callee.offset // 文档左上角坐标的值
return {
left: rect.left + offset,
right: rect.right + offset,
top: rect.top + offset,
bottom: rect.bottom + offset
}
}
对于不支持 getBoundingClientReact()的浏览器,可以使用其他手段取得相同信息。
// 对于不支持 getBoundingClientRect() 的浏览器
function getBoundingClientRect(element) {
var scrollTop = document.documentElement.scrollTop
var scrollLeft = document.documentElement.scrollLeft
if (element.getBoundingClientRect) {
// 是否为初始化(是否定义过),避免多次执行
if (typeof arguments.callee.offset != 'number') {
var scrollTop = document.documentElement.scrollTop // 文档滚动的高度
// 创建元素 测试左上角坐标
var temp = document.createElement('div')
temp.style.cssText = 'position:absolute; left: 0; top: 0;'
document.body.appendChild(temp)
// 计算 文档左上角的坐标
arguments.callee.offset = -temp.getBoundingClientRect().top - scrollTop
// 移除元素
document.body.removeChild(temp)
// 回收
temp = null
}
var rect = element.getBoundingClientRect() // 距离当前视口的距离
var offset = arguments.callee.offset // 文档左上角坐标的值
return {
left: rect.left + offset,
right: rect.right + offset,
top: rect.top + offset,
bottom: rect.bottom + offset
}
} else {
var actualLeft = getElementLeft(element) // 获取到屏幕左边的偏移值
var actualTop = getElementTop(element) // 获取到屏幕上边的偏移值
// 偏移值 - 滚动的距离 = 当前视口的值
return {
left: actualLeft - scrollLeft,
right: actualLeft + element.offsetWidth - scrollLeft,
top: actualTop - scrollTop,
botoom: actualTop + element.offsetHeight - scrollTop
}
}
}
由于这里使用了 arguements.callee,所以这个模式不能再严格模式下使用
三、遍历
"DOM2级遍历和范围"模块定义了凉饿用于辅助完成顺序遍历DOM结构的类型:NodeIterator 和 TreeWalker。这两个类型能够基于给定的起点对DOM结构执行深度优化(depth-first)的遍历操作。
可以通过下列代码检测浏览器对DOM2级遍历能力的支持
var supportsTraversals = document.implementation.hasFeature('Traversal', '2.0')
var supportsNodeIterator = (typeof document.createNodeIterator == 'function')
var supportsTreeWalker = (typeof document.createTreeWalker == 'function')
DOM遍历是深度优化的DOM结构遍历,也就是说,移动的方向至少有两个(取决于使用的遍历类型)。遍历以给定节点为根,不可能向上超出DOM树的根节点。
DOM树
如上图的DOM树,从document依序向前。从文档最后的文本节点开始,遍历可以反向移动到DOM树的顶端。NodeIterator 和 TreeWalker 都以这种方式执行遍历。
3.1、NodeIterator
NodeIterator类型是两者中比较简单的第一个,可以使用document,createNodeIterator() 方法创建它的实例。这个方法接受以下4个参数:
- root:想要作为搜索起点的节点
- whatToShow:表示要访问哪些节点的数字代码。
- filter:是一个NodeFilter 对象,或者一个表示应该接受还是拒绝某种特定节点的函数。
- entityReferenceExpansion:布尔值,表示是否要扩展实体引用。这个参数在HTML页面中没有用,因为其中的实体引用不能扩展。
whatToShow参数
是一个位掩码,通过应该一或多个过滤器(filter) 来确定要访问哪些节点。这个参数的值以常量形式在 NodeFilter 类型中定义,如下(忽略了对HTML没用的常量):
- NodeFilter.SHOW_ALL: 显示所有类型的节点。
- NodeFIlter.SHOW_ELEMENT:显示元素节点。
- NodeFilter.SHOW_ATTRIBUTE:显示特性节点。由于DOM结构原因,实际上不能使用这个值。
- NodeFilter.SHOW_TEXT:显示文本节点。
- NodeFilter.SHOW_COMMENT:显示文本节点
- NodeFilter.SHOW_DOCUMENT:显示文档节点
- NodeFilter.SBOW_DOCUMENT_TYPE:显示文档类型节点
filter参数指定自定义的NodeFilter对象,或者指定一个功能类似节点过滤器(node filter)的函数。每个NodeFilter对象只有一个方法,即accept-Node();
- 如果应该访问给定的节点,该方法返回 NodeFilter.FILTER_ACCEPT;
- 如果不应该访问给定的节点,该方法返回NodeFilter.FILTER_SKIP
由于NodeFilter是一个抽象的类型,因此不能直接创建它的实例。在必要时,只要创建一个包含 acceptNode() 方法对象,然后将这个对象传入 createNodeIterator() 中即可
创建一个 值显示 <p> 元素的节点迭代器
var filter = {
acceptNode: function(node) {
return node.tagName.toLowerCase() == 'p' ?
NodeFilter.FILTER_ACCEPT :
NodeFilter.FILTER_SKIP
}
}
var iterator = document.createNodeIterator(root, NodeFilter.SHOW_ELEMENT, filter, false)
第三个参数也可以是一个与acceptNode() 方法类似的函数
var filter = function() {
return node.tagName.toLowerCase() == 'p' ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP
}
一般来说,后者比较常用。如果不知道过滤器,那么应该把第三个参数设置为 null
NodeIterator 类型的两个主要方法 nextNode()、previousNode(),分别表示向前一步和向后一步。
在刚刚创建的 NodeIterator 对象中,有一个内部指针指向根节点,因此第一个调用 nextNode() 会返回根节点。当遍历到DOM子数的最后一个节点时,nextNode() 返回 null
。previousNode()的工作机制类似,在 previousNode()返回根节点之后,再次调用返回null
<div id="div">
<p><b>Hello World</b></p>
<ul>
<li>List Item 1</li>
<li>List Item 2</li>
<li>List Item 3</li>
</ul>
</div>
遍历如上结构
var div = document.getElementById('div')
var iterator = document.createNodeIterator(div, NodeFilter.SHOW_ELEMENT, null, false)
var node = iterator.nextNode()
while (node != null) {
console.log(node.tagName)
node = iterator.nextNode()
}
/*
DIV
P
B
UL
LI
LI
LI
*/
如果只想返回某一个元素,比如LI,可以为他定义一个过滤器:
var filter = function(node) {
return node.tagName.toLowerCase() == 'li' ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP
}
var iterator = document.createNodeIterator(div, NodeFilter.SHOW_ELEMENT, filter , false)
// todo
由于 nextNode() 和 previousNode() 方法都基于 NodeIterator 在 DOM 结构中的内部指针工作,所以DOM结构的变化会反映在遍历的结果中。
3.2、TreeWalker
TreeWalker 时 NodeIterator 的一个更高级版本。除了包括 nextNode() 和 previousNode() 在内的相同功能之外,还提供了下列的方法:
- parentNode():遍历到当前节点的父节点
- firstChild():遍历到当前节点的第一个子节点
- lastChild():遍历到当前节点的最后一个子节点
- nextSibling():遍历到当前节点的下一个同辈节点
- previousSibling():遍历到当前节点的上一个同辈节点。
创建 TreeWalker 对象要使用 document.createTreeWalker() 方法,这个方法接受4个参数(与 createNodeIterator()相同 ):
- 作为遍历起点的根节点
- 要显示的节点类型
- 过滤器
- 表示是否扩展实体引用的布尔值
var div = document.getElementById('div')
var filter = function(node) {
return node.tagName.toLowerCase() == 'li' ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP
}
var walker = document.createTreeWalker(div, NodeFilter.SHOW_ELEMENT, filter, false)
var node =walker.nextNode()
while (node != null) {
console.log(node.tagName)
node = walker.nextNode()
}
由于这两个创建方法很相似,所以很容易用 TreeWalker 来代替 NodeIterator:
TreeWalker 真正强大的地方在于能够在DOM结构中沿任何方向移动。使用TreeWalker 遍历DOM树,即使不定义过滤器,也可以取得所有的 <li> 元素。
var div = document.getElementById('div')
var walker = document.createTreeWalker(div, NodeFilter.SHOW_ELEMENT, null, false)
walker.firstChild() // 转到 <p>
walker.nextSibling() // 转到 <ul>
var node = walker.firstChild(); // 第一个<li>
while(node !== null) {
console.log(node.tagName)
node = nextSibling() // 同级<li>
}
TreeWalker 类型还有一个属性,名叫 currentNode,表示任何遍历方法在 上一次遍历中返回的节点。通过设置这个属性也可以修改继续进行的起点
var node =walker.nextNode()
console.log(node == walker.currentNode) // true
walker.currentNode = document.body // 修改起点
四、范围
"DOM2级遍历和范围"模块定义了“范围”(range)接口。通过范围可以选择文档中的一个区域,而不必考虑节点的界限。
4.1、DOM中的范围
DOM2级在 Document 类型中定义了 createRange() 方法。使用 hasFeature() 或者直接检测该方法,都可以确定浏览器是否支持范围。
var supportsRange = document.implementation.hasFeature('Range', '2.0')
var alsoSupportsRange = (typeof document.createRange == 'function')
创建范围通过 createRange()方法
var range = document.createRange()
新创建的范围也直接与创建它的文档关联在一起,不能用于其他文档。
每一个范围由一个Range 实例表示,拥有以下属性和方法:
- startContainer:包含范围起点的节点(即选区中第一个节点的父节点)
- startOffset:范围在 startContainer 中起点的偏移量。如果 startContainer 是文本节点、注释节点、CDATA节点,那么startOffset 就是范围起点之前跳过的字符数量,否则, startOffset 就是范围中第一个子节点的索引
- endContainer:包含范围终点的节点(即选区中第一个节点的父节点)
- endOffset:范围在 endContainer 中终点的偏移量(与startOffset 遵循相同的取值规则)
- commonAncestorContainer:startContainer 和 endContainer 共同的祖先节点咋爱文档树中位置最深的那个
在把范围放到文档中特定的位置时,这些属性都会被赋值。
4.1.1、用DOM范围实现简单选择
要使用范围来选择文档中的一部分,最简单的方式就是 使用 selectNode() 或 selectNodeContents()。这两个方法都接受一个参数:即一个DOM节点,然后使用该节点中的信息来填充范围。selectNode() 方法选择整个节点,包括器子节点; 而 selectNodeContents() 方法则只选择节点的 子节点。
<body>
<p id="p1"><b>Hello</b> World</p>
</body>
根据如上代码来创建文档
var rang1 = document.createRange()
var rang2 = document.createRange()
var p1 = document.getElementById('p1')
rang1.select(p1) // 包含 p 元素
rang2.selectNodeContents(p1) // 不包含 p 元素
示例图
在调用selectNode() 时,startContainer、endContainer、commonAncestorContainer都等于 传入节点的父节点,而 startOffset 属性等于 给定节点在其父节点的 childNodes 集合中的索引(在这个例子中是1——因为兼容DOM的浏览器会将空格算作第一个文本节点),endOffset 等于 startOffset 加 1(因为只选择了一个节点)
在掉头 selectNodeContents() 时,startContainer、endContainer 和 commonAncestorContainer 等于传入的节点,而startOffset 属性始终等于0,因为范围从给定节点的第一个子节点开始。endOffset 等于子节点的数量,在这个例子中为2
为了 更精确地控制将哪些节点包含在范围中,还可以使用下列方法
- setStartBefore(refNode):将范围的起点设置在 refNode 之前,因此 refNode 也就是范围选区中的第一个子节点。同时会将 startContainer 属性设置为 refNode.parentNode,将startOffset 属性设置为 refNode 在其父节点的 childNodes 集合中的索引。
- setStartAfter(refNode):将范围的起点设置在 refNode 之后,因此 refNode也就不再范围之内了,其下一个同辈节点才是范围选区中的第一个子节点。同时会将 startContainer 属性设置为 refNode.parentNode, 将 startOffset 属性设置为 refNode 在其父节点的 childNodes 集合中的索引 加 1
- setEndBefore(refNode):将范围的终点设置在 refNode 之前,因此 refNode 也就不在范围之内了,其上一个同辈节点才是范围选区中的最后一个子节点。同时会将 endContainer 属性设置为 refNode.parentNode 将 endOffset 属性设置为 refNode 在其父节点的 childNodes 集合中的索引。
- setEndAfter(refNode):将范围的终点设置在 refNode 之后,因此 refNode 也就是 范围选区中的最后一个子节点。同时会将 endContainer 属性设置为 refNode.parentNode,将endOffset 属性设置为 refNode 在其父节点的 childNodes 集合中的索引 加 1。
调用这些方法的时候,所有属性都会自动为你设置号。要想创建复杂的范围选区,也可以直接指定这些属性的值。
4.1.2、用DOM 范围实现复杂选择
要创建复杂的范围就得使用 setStart() 和 setEnd() 方法。这两个方法都接受两个参数:参照节点、偏移量。可以使用这两个方法来模仿 selectNode() 和 selectNodeContents()
var range1 = document.createRange()
var range2 = document.createRange()
var p1 = document.getElementById('p1')
var p1Index = -1
for (var i = 0, len = p1.parentNode.childNodes.length; i < len; i++) {
if (p1.parentNode.childNodes[i] == p1) {
p1Index = i
break
}
}
range1.setStart(p1.parentNode, p1Index)
range1.setEnd(p1.parentNode, p1Index + 1)
range2.setStart(p1, 0)
range2.setStart(p1, p1.childNodes.length)
模仿selectNode() 和 selectNodeContents() 并不是 setStart() 和 setEnd() 的主要用途,它们更胜一筹的地方在于能够选择节点的一部分。
假设你只想选择前面 HTML 实例代码从 "Hello" 的 "llo" 到 "world" 的 "o" ——很容易就能做到
// 获取所有节点的引用
var p1 = document.getElementById('p1')
var helloNode = p1.firstChild.firstChild
var worldNode = p1.lastChild
// 创建范围 指定起点和终点
var range = document.createRange()
range.setStart(helloNode, 2) // 设置偏移两个字符
range.setEnd(worldNode, 3) // 偏移三个字符(前面的空格)
图解
当然,仅仅是选择了文档中的某一部分用处并不是很大。但重要的是,选择之后才可以对选区进行操作
4.1.3、操作DOM 范围中的内容
在创建范围时,内部会为这个范围创建一个文档片段,范围所属的全部节点都被添加到了这个文档片段中。为了创建这个文档片段,范围内容的格式必须正确有效。
对于上年截取字符的代码,范围经过计算知道选区缺少了一个开始的<b>标签,就会在后台动态加入一个该标签,同时还会在前面加入一个表示结束的</b>标签以结束“He”。并且重构一个有效良好的DOM格式
llo</b> wo ====> <p><b>He</b><b>llo</b> world!</p>
在创建了范围之后,就可以使用各种方法对范围的内容进行操作了(注意,表示范围的内部文档片段中的所有节点,都只是指向文档中相应节点的指针)
deleteContents()
这个方法能够从文档中删除范围所包含的内容
var range = document.createRange()
range.setStart(helloNode, 2)
range.setEnd(worldNode, 3)
range.deleteContents()
此时页面会显示如下HTML代码
<p id="p1"><b>He</b>rld</p>
图解
由于范围选区在修改底层DOM 结构时能够保证格式良好,因此即使内容被删除了,最终的 DOM 结构依旧是 格式良好的。
extractContents() 也会从文档中移除范围选区。区别在于:extractContents() 会返回范围的文档片段。利用这个返回的值,可以将范围的内容插入到文档中的其他地方。
var fragment = range.extractContents()
p1.parentNode.appendChild(fragment)
此时的文档结构
<p id="p1"><b>He</b>rld</p>
<b>llo</b> Wo
还有一种做法,使用 cloneContents() 创建范围对象的一个副本,然后在文档的其他地方插入该副本
var fragment = range.cloneContents()
p1.parentNode.appendChild(fragment)
此时的文档结构
<p id="p1"><b>Hello</b> World</p>
<b>llo</b> Wo
需要注意的是:在调用上面介绍的方法之前,拆分的节点并不会产生格式良好的文档片段。换句话说,原始的HTML在DOM被修改之前会始终保持不变。
4.1.4、插入DOM范围中的内容
insertNode() 方法可以想范围选区的开始插入一个节点。
假如在前面示例的 HTML 代码中插入 <span style="color: red"> Insterted text <span>
var p1 = document.getElementById('p1')
var helloNode = p1.firstChild.firstChild
var worldNode = p1.lastChild
var range = document.createRange()
var span = document.createElement('span')
span.style.color = 'red'
span.appendChild(document.createTextNode('Insterted text'))
range.insertNode(span)
此时的文档结构
<p id="p1"><b>He<span style="color: red;">Insterted text</span>llo</b> World</p>
除了向范围内部 插入内容之外,还可以围绕范围插入内容,surroundContents() 方法。接受一个参数,即环绕范围内容的节点。在环绕范围插入内容时,后台会执行下列步骤。
- 提取除范围中的内容(类是执行 extractContent())
- 将给定节点插入到文档中原来范围所在的位置上;
- 将文档片段的内容添加到给定节点中。
range.selectNode(helloNode) // <b>Hello</b>
var span = document.createElement("span")
span.style.backgroundColor = 'yellow'
range.surroundContents(span)
此时的文档变化
<b><span style="background-color: yellow;">Hello</span></b>
为了插入<span>,范围必须包含整个 DOM 选区( 不能仅仅包含选中的 DOM 节点 )
4.1.5、折叠DOM范围
所谓 折叠范围,就是指范围中未选择文档的任何部分。
collapse()方法来折叠范围,这个方法接受一个参数,一个布尔值,表示要折叠到范围的哪一端。参数 true 表示折叠到范围的起点,false表示折叠到范围的终点。要确定范围异界折叠完毕,可以检测 collapsed 属性。
range.collapse(true) // 折叠到起点
console.log(range.collapsed)
某个范围是否处于折叠状态,可以帮我嫩确定范围中的两个节点是否紧密相邻。
<p id="p1"> Paragraph 1</p><p id="p2">Paragraph 2</p>
如上结构:如果我们不知道其构成(比如说,这是动态生成的代码),那么可以像下面这样创建一个范围。
var p1 = document.getElementById('p1')
var p2 = document.getElementById('p2')
var range = document.createRange()
range.setStartAfter(p1)
range.setEndBefore(p2)
console.log(range.collapsed) // ,true
这个例子中,新创建的范围时折叠的 因为p1 的后面 和 p2的前面什么也没有。
4.1.6、比较DOM范围
在有多个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。
<p id="p1"><b>Hello</b> World!</p>
var range1 = document.createRange()
var range2 = document.createRange()
var p1 = document.getElementById('p1')
range1.selectNodeContents(p1)
range2.selectnodeContents(p1)
range2.setEndBefore(p1.lastChild)
console.log(range1.compareBoundaryPoints(Range.START_TO_START, range2)) // 0
console.log(range1.compareBoundaryPoints(Range.END_TO_END, range2)) // 1
图示
4.1.7、复制 DOM 范围
cloneRange() 方法 可以复制范围。这个方法会创建调用它的范围的一个副本
var newRange = range.cloneRange()
要创建的范围于原来的范围包含相同的属性,而修改它的端点不会影响到原来的副本
4.1.8、清理DOM 范围
在使用完范围之后,最好是调用 detach() 方法,以便从创建范围的文档中分离出该范围。调用detach()之后,就可以方法的解除对范围的引用,从而让垃圾回收机制回收其内存了。
range.detach() // 从文档中分离
range = null // 解除引用
4.2、IE8及更早版本中的范围
虽然IE9支持DOM范围,但IE8及之前版本不支持DOM范围。IE8及早期版本支持一种类似的概念,即文本范围(text range)。文本范围是IE专有的特性,其他浏览器都不自持。
通过<body>、<button>、<input>、<textarea>等这几个元素,可以调用 createTextRange() 方法来创建文本范围
var range = document.body.createTextRange()
4.2.1、用IE范围实现简单的选择
findText()
选择页面中某一区域的最简单方式,就是使用范围的findText()方法。这个方法会找到第一次出现的给定文本,并将范围移过来以环绕该文本。如果没有找到文本,这个反法返回false;否则返回 true。
以如下html 为例
<p id="p1"><b>Hello</b> World!</p>
var range = document.body.createTextRange()
var found = range.findText("Hello")
console.log(found) // true——代表找到文本
console.log(found.text) // Hello
还可以为 findText() 传入另一个参数,表示向哪个方向继续搜索的数值。负值表示应该从当前位置向后搜索,而正值表示应该从当前位置向前搜索。
var found = range.findText('Hello')
var foundAgain = range.findText('Hello', 1)
IE中与DOM中的 selectNode() 方法最接近的方法是 moveToElementText(),这个方法接受一个 DOM 元素,并选择改元素的所有文本,包括HTML标签。
var range = document.body.createTextRange()
var p1 = document.getElementById('p1')
range.moveToElementText(p1)
在文本范围中包含HTML的情况下,可以使用 htmlText 属性取得范围的全部内容,包括 HTML 和文本。
console.log(range.htmlText) // <P id=p1><B>Hello</B> World!</P>
IE的范围没有任何属性可以随着范围选区的变化而动态更新。不过,其 parentElement() 方法倒是与 DOM 的 commonAncestorContainer 属性类似
var ancestor = range.parentElement()
这样得到的父元素 始终都可以反映文本选区的父节点
4.2.2、使用IE范围实现复杂的选择
在IE中创建范围的方法,就是以特定的增向量向四周移动范围。IE提供了4个方法:move()、moveStart()、moveEnd() 和 expand()。这些方法都接受两个参数:移动单位、移动单位的数量
移动单位是下列一种字符串值
- character:逐个字符地移动
- word:逐个单词(一系列非空格字符)地移动
- sentence:逐个句子(一些列以句号、问好、叹号结尾的字符)地移动
- textedit:移动到当前范围选区的开始或接受位置
通过 moveStart()方法可以移动范围的起点,通过moveEnd()可以移动范围的终点,移动的幅度由单位数量指定
range.moveStart('word', 2) // 移动两个单词
range.moveEnd('character', 1) // 移动一个字符
使用expand()方法可以将范围规范化。expand() 方法的作用是将任何部分选择的文本全部选中。例如,当前选择的是一个单词中间的两个字符,调用expand("word")可以将整个单词都包含在范围之内。
move()方法则首先会折叠当前范围(让起点和终点相等),然后再将范围移动指定的单位数量。
range.move('character', 5)
调用 move() 之后,范围的起点和终点相同,因此必须再使用 moveStart() 或 moveEnd() 创建新的选区
4.2.3、操作IE范围中的内容
使用 text属性 或 pasteHTML() 方法。通过 text 属性可以取得范围中的内容文本;但是,也可以通过这个属性设置范围中的文本内容
var range = document.body.createTextRange()
range.findText('Hello')
range.text = 'Howdy'
此时的文档结构
<P id=p1><B>Howdy</B> World!</P>
要向 范围中插入 HTML代码,就得使用 pasterHTML() 方法,
var range = document.body.createTextRange()
range.findText('Hello')
range.pasteHTML('<em>Howdy</em>')
此时的文档结构
<P id=p1><B><EM>Howdy</EM></B> World!</P>
不过,在范围中包含HTML代码是,不应该使用pasteHTML(),因为这样很可能会导致不可预料的结果——很可能是格式不对的HTML
4.2.4、折叠IE范围
IE为范围提供的 collapse() 方法与相应的 DOM 方法用法一样:传入 true 把范围折叠到起点,传入false把范围折叠到终点
range.collapse(true) // 折叠到起点
可惜的是,没有对应的 collapsed 属性让我们知道范围是否折叠完毕。为此,需要使用到 boundingWidth 属性,该属性返回范围的宽度(以像素为单位)。如果 boundingWidth 属性等于 0,就说明范围已经折叠了。
var isCollapsed = (range.boundingWidth == 0)
此外还有 boundingHeight、boundingLeft、boundingTop 等属性,虽然他们都不像 boundingWidth 那么有用,但也可以提供一些有关范围位置的信息。
4.2.5、比较IE范围
IE中的 compareEndPoints() 方法与 DOM 范围的 compareBoundaryPoints() 方法类似。接受两个参数:比较的类型、比较的范围。
比较类型与DOM相似
- StartToStart
- StartToEnd
- EndToEnd
- EndToStart
与DOM类似,返回值有三种情况
- 第一个范围边界位于第二个范围边界前面,返回 -1
- 二者边界相同,返回 0
- 如果第一个范围的边界位于第二个范围的边界后面,返回 1
var range1 = document.body.createTextRange()
var range2 = document.body.createTextRange()
range1.findText('Hello World')
range2.findText('Hello')
console.log(range1.compareEndPoints('StartToStart', range2)) // 0
console.log(range1.compareEndPoints('EndToEnd', range2)) // 1
IE中 还有两个方法,也是用于比较范围的:isEqual() 用于确定两个范围是否相等,inRange() 用于确定一个范围是否包含另一个范围。
var range1 = document.body.createTextRange()
var range2 = document.body.createTextRange()
range1.findText('Hello World')
range2.findText('Hello')
console.log(range1.isEqual(range2)) // false
console.log(range1.inRange(range2)) // true
4.5.6、复制IE范围
在IE中使用 duplicate() 方法可以复制文本范围,结果会创建原范围的一个副本
var newRange = range.duplicate()
新创建的范围会带有与原范围完全相同的属性
五、小结
"DOM2 级样式" 模块只要针对操作元素的样式信息而开发,其特征简要总结如下。
- 每个元素都有一个管理的 style 对象,可以用来确定和修改行内的样式。
- 要确定某个元素的计算样式(包括应用给它的所有CSS规则),可以使用 getComputedStyle() 方法。
- IE不支持 getComputedStyle() 方法,但为所有元素都提供了能够返回相同信息的 currentStyle属性。
- 可以通过 document.styleSheets 集合访问样式表
- 除IE之外的所有浏览器都支持对样式表的接口,IE也为几乎所有相应的DOM功能提供了自己的一套属性和方法
"DMO2级遍历和范围"模块提供了与DOM结果交互的不同方式,简要总结如下: - 遍历即使用 NodeIterator 或 TreeWalker 对 DOM执行深度优化的遍历
- NodeIterator 是一个简单的接口,只允许一个节点的步幅前后移动。而 TreeWalker 在提供相同功能的同时,还支持在DOM结构的各个方向上移动,包括父节点、同辈节点、子节点等。
- 范围是选择 DOM 结构中特定部分,然后再执行相应操作的一种手段
- 使用范围选区可以在删除文档中某些部分的同时,保存文档结构的良好,或者复制文档中的相应部分
- IE8及更早版本不支持"DOM2级遍历和范围" 模块,但它提供了一个专有的文本范围对象,可以用来完成简单的基于文本的范围操作。IE9完全支持DOM遍历。