二刷《高程》做的笔记,没什么技术含量就不发到首页啦!~
DOM1级主要定义HTML和XML文档底层结构,DOM2和DOM3在这个结构基础上引入更多交互能力,也支持更高级的XML特性。DOM2和DOM3级分为许多模块(模块之间具有某种关联),分别描述DOM的某个非常具体的子集。这些模块如下:
- DOM2级核心:在文档1级核心的基础上构建,为节点添加了更多方法和属性
- DOM2级视图:为文档定义了基于样式信息的不同视图
- DOM2级事件:说明了如何使用事件与DOM文档交互
- DOM2级样式:如何以编程方式访问和改变CSS样式信息
- DOM2级遍历和规范:引入了遍历DOM文档和选择其特定部分的新接口
- DOM2级HTML:在1级HTML基础上构建,添加更多属性,方法和新接口
- DOM3级XPath模块
- DOM3级加载与保存模块
DOM变化
DOM2和DOM3级的目的是扩展DOM API,以满足操作XML的所有需求,同时提供更好的错误处理和特性检测能力,从某种意义上讲实现这一目的很大程度意味着对命名空间的支持。
DOM2级核心没有引入新类型,只是在DOM1级基础上通过增加新方法和新属性来增强既有类型。
DOM3级核心同样增强即有类型,但也引入一些新类型。
DOM2级视图和DOM2级HTML也增强了DOM接口,提供了新的属性和方法。
- 针对XML命名空间的变化
- 其他方面的变化
(1)针对XML命名空间的变化:有了XML命名空间,不同XML文档的元素就可以混合在一起使用,共同构成格式良好的文档,而不用担心发生命名冲突。从技术上讲,HTML不支持XML命名空间,但XHTML支持XML命名空间。命名空间要使用 xmlns 特性来指定,XHTML的命名空间是http://www.w3.org/1999/xhtml,在任何格式良好的XHTML页面中,都应该将其包含在<html>元素中
<html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Example XHTML Pages</title> </head> <body> Hello! </body> </html>
该例中其中所有元素默认都被视为XHTML命名空间中的元素,想为XML命名空间创建前缀可以 xmlns:前缀 ="http://www/w3/org/1999/xhtml"
<xhtml:html xmlns:xhtml="http://www.w3.org/1999/xhtml"> <xhtml:head> <xhtml:title>Example XHTML page</xhtml:title> </xhtml:head> <xhtml:body xhtml:class="home"> Hello </xhtml:body> </xhtml:html>
这里为XHTML的命名空间定义了一个名为xhtml的前缀,并要求所有XHTML元素都以该前缀开头,在避免不同语言间的冲突时需要使用命名空间来限定特性。例如混合了XHTML和SVG语言的文档:
<html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Example XHTML Page</title> </head> <body> <s:svg xmlns:s="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 100 100" style="100%;height:100%"> <s:rect x="0" y="0" width="100" height="100" style="fill:red"/> </s:svg> </body> </html>
该例中设置命名空间将<svg>标识为了与包含文档无关的元素,此时svg元素的所有子元素以及这些元素的所有特性都被认为属于http://www.w3.org/2000/svg命名空间,即使这个文档从技术上说是一个XHTML文档,但因为有了命名空间,其中的SVG代码也仍然是有效的。对于这类文档最有意思的是调用方法操作文档节点,例如创建一个元素这个元素属于哪个命名空间呢?DOM2级核心通过为大多数DOM1级方法提供特定于命名空间的版本解决了这个问题。
- Node类型的变化:
DOM2级中Node类型包含下列特定于命名空间的属性,均继承自Element.ptototype
localName:不带命名空间前缀的节点名称
namespace:命名空间URI或者(在未指定情况下是)null
prefix:命名空间前缀或者(在未指定情况下是)null
当节点使用命名空间前缀时其nodeName等于prefix:localName
DOM3级在此基础上引入了下列与命名空间有关的方法,均继承自Node.prototype
isDefaultNamespace(namespaceURI):在指定的namespaceURI是当前节点的默认命名空间的情况下返回true
lookupNamespaceURI(prefix):返回给定prefix的命名空间
lookupPrefix(namespaceURI):返回给定namespaceURI的前缀
在取得了一个节点,但不知道该节点与文档中其他元素之间的关系的情况下,这些方法很有用。 - Document类型的变化:DOM2级包含下列与命名空间有关的方法
createElementNS(namespaceURI, tagName):继承自Document.ptototype,使用给定的tagName创建一个属于命名空间namespaceURI的新元素。
createAttributeNS(namespaceURI, attributeName):继承自Document.ptototype,使用给定attributeName创建一个属于命名空间namespaceURI的新特性。
getElementsByTagNameNS(namespaceURI, tagName):继承自Document.prototype和Element.prototype,返回属于命名空间namespaceURI的tagName元素的HTMLcollection集合
只有在文档中存在两个或多个命名空间时,这些与命名空间有关的方法才是必须的。 - Element类型的变化:DOM2级核心中有关Element的变化主要涉及操作特性,继承自Element.prototype。
getAttributeNS(namespaceURI, localName):取得属于命名空间namespaceURI且名为localName的特性值
getAttributeNodeNS(namespaceURI, localName):取得属于命名空间namespaceURI且名为localName的特性节点。
getElementsByTagNameNS(namespaceURI, localName):返回属于命名空间namespaceURI的localName元素的HTMLCollection
hasAttributeNS(namespaceURI, localName):确定当前元素是否有一个名为localName的特性,而且该特性的命名空间为namespaceURI。DOM2级也增加了个hasAttribute()不用考虑命名空间
removeAttributeNS(namespaceURI, localName):删除属于命名空间namespaceURI且名为localName的特性
setAttribueNS(namespaceURI, qualifedName, value):设置属于命名空间namesapceURI且名为qualifedName的特性的值为value。
setAttributeNodeNS(attrNode):设置属于命名空间namespaceURI的特性节点
除了第一个参数之外,这些方法与DOM1级中相关方法的作用相同。 - NamedNodeMap类型的变化:特性是通过NamedNodeMap表示的,因此这些方法只针对特性节点使用,继承自NamedNodeMap.prototype。
getNamedItemNS(namespaceURI, localName):取得属于命名空间namespaceURI且名为localName的特性节点项。
removeNamedItemNS(namespaceURI, localName):移除属于命名空间namespaceURI且名为localName的项。
setNamedItemNS(node):添加node,这个特性节点已经事先指定了命名空间信息。
(2)其他方面的变化:DOM的其他部分在DOM2级核心上中也发生了些变化,这些变化与XML命名无关,而是更倾向于确保API的可靠性及完整性。
- DocumentType类型的变化:DocumentType.prototype新增3个属性,publicId,systemId,internalSubset。前两个属性表示文档类型声明中的两个信息段,这两个信息段在DOM1级中是没办法访问的。
internalSubset用于访问包含在文档类型声明中的额外定义,在XML中会常见。 - Document类型的变化:
(1).Document.prototype中唯一与命名空间空间无关的方法是importNode(),这个方法的用途是从一个文档中取得一个节点,然后将其导入另一个文档,使其成为这个文档结构的一部分。每个节点都有一个ownerDocument属性(继承自Node.prototype)表示所属文档。如果用appendChild()时候传入的节点属于不同的文档(ownerDocument属性的值不一样)则会导致错误(??Chrome下测试无错误)。但在调用importNode()传入不同文档的节点则会返回一个新节点,这个节点的所有权归当前文档所有。
importNode与cloneNode(继承自Node.prototype)方法非常相似,两个参数:要复制的节点,表示是否复制子节点的布尔值。返回的是原来节点的副本,但能够在当前文档中使用。这个方法在XML文档中常用。
框架里的document对象和主页面的document是不同的两个:
此时再查看HTML页面发现框架里table元素还在的。
(2).DOM2级视图模块添加了一个名为defaultView的属性(继承自Document.prototype),其中保存着一个指针,指向拥有给定文档的窗口(或框架)。除IE8之外的所有浏览器都支持defaultView,在所有IE中有一个等价的属性名parentWindow(Opera也支持这个属性),因此要确定文档归属窗口,兼容性代码为
var parentWindow = document.defaultView || document.parnetWindow;
(3).DOM2级核心还为document.implementation对象规定了两个新方法:createDocumentType,createDocument,均继承自DOMImplementation.prototype。
createDocumentType(doctypename, publicId, systemId):创建一个新的DocumentType节点,三个参数文档类型名称,publicId,systemId。如下面代码会创建一个新的HTML4.01 Strict文档类型var doctype = document.implementation.createDocumentType('html', "-//W3C//DTD HTML 4.01//EN", "http://www.w3.org/TR/html4/strict.dtd");
由于既有文档的文档类型不能改变因此createDocumentType只在创建新文档时有用。
createDocument(namespaceURI, tagName, doctype):创建新文档,三个参数:namespaceURI,文档元素的标签名,新文档的文档类型。
(4).DOM2级HTML模块也为document.implementation增加了一个方法,叫createHTMLDocument(title),它的用途是创建一个完整的HTML文档,包括<html>,<head>,<title>,<body>元素。参数为文档的标题(放在title元素中的字符串),返回新的HTML文档实例(原型为HTMLDocumetn.prototype,因而继承该原型链上的所有属性和方法,包括title和body,均在Document.prototype上):
- Node类型的变化:
(1).唯一与命名空间无关的变化就是添加了isSupported(特性名, 版本号)方法,与DOM1级为document.implementation引入的isFeature()方法类似,用于确定当前节点具有什么能力。如果浏览器实现了相应特性而且能够基于给定节点执行该特性,isSupported就返回true,但是chrome下貌似没有这个属性啊...IE倒是有。由于不同实现在决定对什么特性返回true或false并不一致,这个方法同样也存在与hasFeature方法相同的问题,在确定某个特性是否可用时,最好还是使用能力检测。
(2).DOM3级引入了两个辅助比较节点的方法:isSameNode和isEqualNode,继承自Node.prototype。都接受一个节点参数,并在传入节点与引用节点相同或相等时返回返回true。相同是指两个节点引用的是同一个对象,相等是指两个节点是相同类型,具有相等的属性(nodeName,nodeValue等等)而且它们的attributes和childNodes属性也相等(相同位置包含相同的值)。
(3).Node.setUserData:已废弃 - 框架的变化:框架和内嵌框架分别用HTMLFrameElement和HTMLIFrameElement表示,它们在DOM2级中都有新属性为contentDocument(继承自HTMLFrameElement.prototype和HTMLIFrameElement.prototype)这个属性包含一个指针指向表示框架内容的文档对象(是HTMLDocument类型的实例,因此原型链继承属性和方法),在此之前无法直接通过元素取得取得这个文档对象(只能使用frames集合:window.frames[0].document)
IE8之前不支持框架集中的contentDocument属性,但支持contentWindow(继承自HTMLFrameElement和HTMLIFrameElement)的属性,该属性返回框架的window对象而这个window对象又有一个document属性,所有浏览器都支持contentWindow。兼容性代码为:
var iframe = document.getElementById('myIframe'); var iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
但要注意:访问框架或内嵌框架的文档对象要受到跨域安全策略的限制,如果某个框架中的页面来自其他域或不同域,或者使用了不同协议,那么要访问这个框架的文档对象就会导致错误。
样式
在HTML中定义样式方式有三种,通过link元素包含外部样式表文件,使用style元素定义嵌入样式表,使用style特性定义针对特定元素的样式。DOM2级样式模块围绕这三种应用样式的机制提供了一套API。
- 访问元素的样式:任何支持style特性的元素在JavaScript中都有一个对应的style属性(HTMLELement.prototype),这个style对象是CSSStyleDeclaration的实例,包含着通过HTML的style特性指定的所有样式信息,但不包含与外部样式表或嵌入式样式表经层叠而来的样式。在style特性中指定的任何css属性都将表现为都将表现为这个style对象的相应属性。对于使用短划线的CSS属性名,必须将其转化为驼峰大小写形式,才能通过JS访问。但也有例外,比如float,由于float是JS中的保留字,因此不能用作属性名,DOM2级样式规范规定样式对象上相应属性名应该是cssFloat,>=IE9和其他浏览器支持该属性名,所有IE也支持styleFloat属性名。
原型链继承关系为:document.body.style.__proto__->CSSStyleDeclaration.prototype->Object.prototype
在设置样式时,元素的外观会被自动更新,在标准模式下所有度量值都必须指定一个度量单位。混杂模式下可以将style.width设置为20,浏览器会假设它是“20px”,但在标准模式下,将style.width设置为20会导致被忽略,因为没有度量单位。
如果没有为元素设置style特性,那么style对象中可能会包含一些默认的值,但这些值并不能准确地反映该元素的样式信息。
之所以能手动改变元素样式值,是因为这样的,每个CSSStyleDeclaration的实例通过构造函数后都会拥有自己的这355(固定属性)+n(通过style特性设置,与ele.style.length值有关)个属性。
var i=0; var styles = document.body.style; for(k in styles){ if(styles.hasOwnProperty(k)){ console.log(k, styles[k]); i++; } } console.log("count", i); //控制台结果 0 overflow-x 1 overflow-y 2 font-size alignContent '' alignItems '' alignSelf '' alignmentBaseline '' all '' animation '' ...
overflow hidden
overflowWrap ''
overflowX hidden
overflowY hidden
... zoom '' cssFloat '' count 358
(1).DOM样式属性和方法:DOM2级样式规范还为style对象定义了一些属性和方法,它们均来自CSSStyleDeclaration.prototype,这些属性和方法在提供元素的style特性值得同时也可以修改样式。
cssText:读模式下,访问元素style特性中的css代码;写模式下赋给cssText的值会重写整个style特性的值。设置cssText是为元素应用多项变化最快捷的方式,因为可以一次性应用所有变化。
length:应用给元素的css属性的数量,就是定义在style特性中的样式数量,并且在style对象中以0,1,2...所以体现。其与item()属性配套使用以便迭代在元素中定义的css属性。在使用length和item()时候,style对象实际上就相当于一个集合,都可以使用方括号语法代替item()来取得给定位置的css属性名。
for(var i=0,len = document.body.style.length; i<len; i++){ console.log(document.body.style[i]); } // overflow-x overflow-y font-size
无论使用方括号语法还是item方法都可以取得被设置在style特性上的css属性名(是background-color不是backgroundColor)。然后可以在getPropertyValue()中使用取得的属性名进一步取得属性值。
var prop, value, i, len; for(i=0,len = document.body.style.length; i<len; i++){ prop = document.body.style[i]; value = document.body.style.getPropertyValue(prop); console.log(prop, value); } // overflow-x hidden overflow-y hidden font-size 16px
getPropertyPriority(propertyName):返回给定的属性使用了!important设置,则返回“important”,否则返回空字符串。
getPropertyValue(propertyName):返回给定属性的字符串值。
item(index):返回给定位置的CSS属性的名称,这个范围和length对应,即返回被设置在style特性中的样式,否则返回空字符串。
removeProperty(propertyName):从样式中删除给定属性,并即时反应在文档中,返回属性值。使用这个方法移除一个属性意味着将会为该属性应用默认样式(从其他样式表层叠而来),在不确定某个给定的CSS属性拥有什么默认值的情况下就可以使用这个方法,只要移除相应的属性就可以为元素应用默认样式。
setProperty(propertyName, value, priority):将给定属性设置为相应值,并加上优先权标志("important"或者一个空字符串)
注:这里的propertyName如果必要均用css短划线属性
(2).计算的样式:虽然style对象能提供支持style特性的任何元素的样式信息,但它不包含那些从其他样式表层叠而来的并影响到当前元素的样式信息。DOM2级样式增强了document.defaultView(defaultView继承自Document.prototype,document.default其实就是window),提供了getComputedStyle(ele, str/null)方法(该方法是window对象上的),两个参数要取得计算样式的元素,一个伪元素字符串(例如':after'),如果不需要伪元素信息,第二个参数可以是null。getComputedStyle()返回一个CSSStyleDeclaration对象(与style属性类型相同),其中包含当前元素的所有计算的样式。355+ n (计算的那些样式,索引从0开始)
这样看来,window.getComputedStyle()得到的CSSStyleDeclaration实例上的各个属性也是可重设置值的,但是并不然不清楚是为什么??明明实例上的writable是true的啊...知道的同学感谢告知...
这里要注意border边框属性可能会也可能不会返回样式表中实际的border规则,存在这个差别原因是不同浏览器解释综合属性(如border)的方式不同,因为设置这种属性实际上会涉及很多其他属性,在设置border时实际上是设置了四个边的边框宽度,颜色,样式属性(border-left-width,border-top-color,border-bottom-style等)。因此即使computedStyle.border不会在所有浏览器都返回相同的值,但computedStyle.borderLeftWidth会返回值。
Chrome:
FF:
IE:IE9+在使用getComputedStyle获取颜色时
IE5+通过currentStyle获取颜色:
<=IE8不支持getComputed方法,但在所有IE中每个具有style属性的元素都有一个currentStyle属性(继承自HTMLELement.prototype),这个属性是CSSStyleDeclaration的实例,包含当前元素全部计算样式。
注意:FF和Safari会将所有颜色转换成RGB格式,就连color属性设置的也会转为RGB,因此在使用getComputedStyle方法时最好多在几种浏览器下测试一下。
计算后的样式也包含属于浏览器内部样式表的样式信息,因此任何具有默认值的CSS属性都会表现在计算后的样式中。例如,所有浏览器中的visibility属性都有一个默认值,这个值因实现而异,默认情况下visibility值为visible,有的浏览器为“inherit”。如果你需要元素具有某个特定的默认值,应该手动在样式表中指定该值。 - 操作样式表:CSSStyleSheet类型表示的是样式表,包括通过<link>元素包含的样式表和在<style>元素中定义的样式表,这两个元素分别是HTMLLinkElement和HTMLStyleElement的实例。
CSSStyleSheet更加通用些,它只表示样式表,而不管这些样式表在HTML中是如何定义的。上述两个针对元素的类型允许修改HTML特性,但CSSStyleSheet对象则是一个只读接口(个别属性除外)。
原型链继承关系:document.styleSheets.__proto__->StyleSheetList.prototype->Object.prototype
应用于文档的所有样式表是通过document.styleSheets集合来表示的,通过这个集合的length属性(继承自StyleSheetList.prototype)可以获知文档中样式表的数量。通过[]或item方法(继承自StyleSheetList.prototype)可以访问每一个样式表。styleSheets属性继承自Document.prototype。
var sheet =null; for(var i=0, len = document.styleSheets.length; i<len; i++){ sheet = document.styleSheets[i]; console.log(sheet.href); }
可以看出,document.styleSheet的实例里的每一项都是CSSStyleSheet的实例
document.styleSheet[0].__proto__->CSSStyleSheet.prototype->StyleSheet.prototype->Object.prototype。
(1).StyleSheet.prototype上的:
除了disabled属性外,其他属性都是只读的
disabled:表示样式表是否被禁用的布尔值。这个属性是可读写的(get/set)的,将这个值设置为true可以禁用样式表。
href:如果样式表是通过<link>包含的,则是样式表的url,否则是null。(undefined/get)
media:当前样式表支持的所有媒体类型的集合。它的值为MediaList的实例,原型链关系为
document.styleSheets[0].media.__proto__-> MediaList.prototype->Object.prototype
如果集合是空列表表示适用于所有媒体。在<=IE8中,media是一个反映<link>和<style>元素media特性值的字符串。
ownerNode:指向拥有当前样式表的节点的指针,样式表可能是在HTML中通过link或style元素引入的(在XML中可能是通过处理指令引入的)。如果当前样式表是其他样式表通过@import导入的,则这个属性值为null。<=IE8不支持这个属性。
parentStyleSheet:在当前样式表是通过@import导入情况下,这个属性是一个指向导入它的样式表的指针。
title:ownerNode中title特性的值。
type:表示样式表类型的字符串,对css样式表来说这个字符串是“type/css”
(2).CSSStyleSheet.prototype上的:
cssRules:规则们,样式表中包含的样式规则的集合。其中每一项都是CSSStyleRule的实例,
原型链继承关系为:document.styleSheets[0].cssRules[0].__proto__->CSSStyleRule.prototype->CSSRule.protoype->Object.prototype
<=IE8不支持该属性,但有一个类似的rules属性。
ownerRule:如果样式表是通过@import导入的,这个属性就是一个指针,指向表示导入的规则,否则值为null,<=IE8不支持该属性。
deleteRule(index):删除cssRules集合中指定位置的规则,<=IE8不支持这个规则,但支持类似的removeRule方法。
insertRule(rule, index):向cssRules集合中指定的位置插入rule字符串。<=IE8不支持这个规则,但支持一个类似的addRule()方法。
不同浏览器的document.styleSheets返回的样式表也不同,所有浏览器都会包含<style>元素和rel特性被设置为‘stylesheet’的<link>元素引入的样式表。IE和Opera也包含rel特性被设置为“alternate stylesheet”的<link>元素引入的样式表。
也可以通过<link>或<style>元素取得CSSStyleSheet对象。DOM中规定了一个包含CSSStyleSheet对象的属性,为sheet(在HTMLLinkElement.prototype和HTMLStyleElement.prototype上)。除了<=IE8其他浏览器都支持这个属性,但所有的IE有个styleSheet属性,兼容性代码:
function getStyleSheet(ele){ return ele.sheet || ele.styleSheet; } var link = document.getElementsByTagName('link')[0]; var sheet = getStyleSheet(link);
(1).css规则:CSSRule对象表示样式中每一条规则,实际上CSSRule是一个供其他多种类型继承的基类型,其中最常见的就是CSSStyleRule类型,表示样式信息。
CSSRule.prototype包含下列属性:
cssText:返回整条规则对应的文本。但浏览器对样式表的内部处理方式不同,返回的文本可能与样式表中实际的文本不一样。 Safari始终会将文本转换为全部小写,<=IE8不支持这个属性。
parentRule:如果当前规则是导入的规则,这个属性引用的就是导入规则;否则这个值为null。<=IE8不支持这个属性
parentStyleSheet:当前规则所属的样式表,<=IE8不支持这个属性。
type:表示规则类型的常量值,对于样式规则这个值是1,对于@import导入的规则这个值为3。<=IE8不支持这个属性。
CSSStyleRule.prototype上的:
selectorText:返回当前规则的选择符文本,由于浏览器对样式表的内部处理方式不同,返回的文本可能会与样式表中实际的文本不一样(Safari3之前的版本始终会将文本转换成全部小写)。<=IE8不支持这个属性。
style:一个CSSStyleDeclaration对象,可以通过它设置和取得规则中特定的样式值。
最常用的三个属性是cssText,selectorText,style。这里document.styleSheets[0].cssRules[0].cssText和document.getElementsByTagName('body')[0].style.cssText不一样
前者包含选择符文本和围绕样式信息的花括号,后者只包含样式信息。大多数情况下仅使用style属性就可以满足所有操作样式规则的需求了。
通过样式规则也可修改样式,但以这种方式修改规则会影响页面中适用于该规则的所有元素。
(2).创建规则:DOM规定要向现有样式表中添加新规则,需要使用insertRule(rule, idx)方法,两个参数:规则文本和表示在哪里插入规则的索引,插入的规则将成为第idx条规则。IE早期版本支持addRule(selectorstr, styleinfo, idx)方法:三个参数,选择符文本,css样式信息,插入规则的位置(可选)。跨浏览器代码:
/* sheet: 样式表 selectorText:选择符文本 cssText:css样式信息 position:插入规则的位置 */ function insertRule(sheet, selectorText, cssText, position){ if(sheet.insertRule){ sheet.insertRule(selectorText + '{' + cssText + '}', position); }else if(sheet.addRule){ sheet.addRule(selectorText, cssText, position); } }
虽然这样可以动态添加规则但要添加的规则若是多还是建议使用动态加载样式表的技术。
(3).删除规则:从样式表中删除规则的方法,deleteRule(idx),参数为要删除规则的位置,IE支持一个removeRule(idx)。跨浏览器代码
function deleteRule(sheet, idx){ if(sheet.deleteRule){ sheet.deleteRule(idx); }else if(sheet.removeRule){ sheet.removeRule(idx); } }
考虑到删除规则可能会影响CSS层叠的效果,不推荐使用。
- 元素大小:下面几项并不属于DOM2级样式规范,但却与HTML样式息息相关。下面图片引用自(http://www.cnblogs.com/panjun-Donet/articles/1294033.html)
(1).偏移量:包括元素在屏幕上占用的所有可见空间(height/width+padding+滚动条+border),下面四个属性均继承自HTMLElement.prototype
offsetHeight:元素在垂直方向上占用的空间,以像素计。包括元素高度,可见的水平滚动条高度,上边框高度,下边框高度。
offsetWidth:元素在水平方向上占用空间大小,以像素计。
offsetLeft:元素的左外边框至包含元素的左内边框之间的像素距离。
offsetTop:元素的上外边框至包含元素的上内边框之间的像素距离。
包含元素的引用保存在offsetParent属性中(继承自HTMLElement.prototype)。offsetParent属性不一定与parentNode值相等。
offsetParent属性是一个只读属性,指向最近的定位元素,如果没有定位元素,则offsetParent为最近的table元素对象或根元素(标准模式下为HTML,严格模式下为body,不过我测试这个结果与MDN描述的有一些不相符,仅测试Chrome标准模式下仍是body...)。当元素的display设置为none,则其offsetParent返回null。webkit中如果元素为隐藏的(该元素或其祖先元素的style.display为none)或该元素的position被设置为fixed,offsetParent也返回null。IE9中如果该元素的position设置为fixed,则offsetParent返回null,设置为display:none无影响。
要向知道某元素在页面上(距离html)的偏移量,将这个元素的offsetLeft和offsetTop与其offsetParent相同属性相加,如此循环至根元素,就可得到一个基本准确的值(但是我觉得这样做还是忽略了border的宽度和高度,不过要是预先知道border可以后期加上border的距离)。
function getElementLeft(ele){ var actualLeft = ele.offsetLeft; var current = ele.offsetParent; while(current !== null ){ actualLeft += current.offsetLeft; current = current.offsetParent; } return actualLeft; } function getElementTop(ele){ var actualTop = ele.offsetTop; var current = ele.offsetParent; while(current !== null){ actualTop += current.offsetTop; current = current.offsetParent; } return actualTop; }
这两个函数利用offsetParent属性在DOM层次中逐级向上回溯,将每个层次中的偏移量属性合计到一块,对于简单的css页面这两个函数可以得到精确结果,对于表格和内嵌框架布局的页面,由于不同浏览器实现这些元素的方式不同因此得到的值不太精准。
(2).客户区大小:元素的内容及其内边距所占据空间大小,下面两属性继承自Element.prototype
所有这些偏移量属性都是只读的,而且每次访问它们都需要重新计算。因此该尽量避免重复访问这些属性。如果需要重复使用其中某些属性的值,可以将它们保存在局部变量中,以提高性能。
clientWidth:元素内容区宽度+左右内边距宽度
clientHeight:元素内容区高度+上下内边距高度
从字面看,客户区大小就是元素内部空间大小,因此滚动条占用的空间不计算在内。在确定浏览器视口大小的时候
function getView(){ if(document.compatMode == 'BackCompat'){ //<IE7之前版本document.documentElement.clientWidth会返回0 return { width : document.body.clientWidth, height : document.body.clientHeight } }else{ return { width : document.documentElement.clientWidth, height : document.documentElement.clientHeight } }
}与偏移量相似,客户区的大小也是只读的,每次访问得重新计算。
(3).滚动大小:包含滚动内容的元素的大小。有些元素(例如html元素,我对高程上这块说的html有点异议,在Chrome下测试document.documentElement.scrollTop = 20;并没有什么卵用,但给body元素设置就可以),即使没有执行任何代码也能自动地添加滚动条,但另外一些元素,则需要通过css的overflow属性进行设置才能滚动。下面四个均继承自Element.protptype
scrollHeight:在没有滚动条情况下,元素内容总高度
scrollWidth:在没有滚动条情况下,元素内容总宽度
scrollLeft:被隐藏在内容区域左侧像素数,通过设置(设置的时候不带单位,带来单位的话Chrome默认是0)这个属性可以改变元素的滚动位置。
scrollTop:被隐藏在内容区域上方的像素数,通过设置这个属性可以改变元素滚动位置。
scrollWidth和scrollHeight主要用来确定元素内容的实际大小,通常认为html元素是在Web浏览器的视口中滚动的元素(IE6之前版本运行在混杂模式下是body元素。又经我测试...Chrome下是body元素,醉了)。在不包含滚动条的页面中,scrollWidth/scrollHeight和clientWidth/clientHeight之间的关系并不十分明确,这种情况下基于document.documentElement查看这些属性会在不同浏览器间发现一些不一致问题:
FF:两组属性值始终都是相等,但大小代表的是文档内容区域的实际尺寸,而非视口尺寸
Opera,Safari3.1+,Chrome中这两组属性有差别:scrollWidth/scrollHeight等于视口大小,clientWidth/clientHeight等于文档内容区域大小
IE(标准模式下)两组属性不想等:scrollWidth/scrollHeight等于文档内容区大小。clientWidth/clientHeight等于视口大小
在确定文档总高度时(包括基于视口的最小高度),必须取得scrollWidth/clientWidth和scrollheight/clientHeight最大值,才能保证在跨浏览器下得到精准结果。前提是html元素或body元素必须包含整个页面,像这样就不行
这样获得的scrollHeight为0,clientHeight为视口高度
var docHeight = Math.max(document.documentElement.scrollHeight, document.documentElement.clientHeight); var docWidth = Math.max(document.documentElement.scrollWidth, document.documentElement.clientWidth);
就算这样我觉得也还是不精准,万一碰上像上图那样html元素高度为0,这获取文档高度就成clientHeight视口高度了,所以页面中一定要注意html元高度是否为0,高程上给的html这个元素太绝对了,我觉得应该是给包裹整个文档的元素才能由scrollHeight正确得到当前文档实际的高度。
(4).确定元素大小:多数浏览器提供了getBoundingClientRect()方法(继承自Element.prototype),这个方法返回一个ClientRect实例对象,这个对象包含四个属性:left,top,right,bottom,给出了元素在页面中相对于视口的位置。原型链继承关系为:document.body.getBoundingClientRect().__proto__->ClientRect.prototype->Object.prototype。
<=IE8认为文档左上角坐标(2,2)(又经我测试,IE8是(-2,-2),,IE7是(2,2),IE5是(0,0)哎尼玛...兼容性我想爆粗口),其他浏览器则认为(0,0),因此需要一开始检查一下位于(0, 0)处元素的位置。高程上给出一段代码来确定是否要对坐标进行调整(反正我是没咋看懂)
第一步检测属性是否有定义,如果没有就定义一个,需要创建一个临时元素将其位置设置为(0,0)然后再调用其getBoundingClientRect(),之所以减掉视口的scrollTop为了防止调用这个函数时窗口被滚动了。
第二步,在传入的元素上调用Element.prototype的getBoundingClientRect方法并基于新的公式创建一个对象。
function getBoundingClientRect(ele){ 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 = ele.getBoundingClientRect(); var offset = arguments.callee.offset; return { left: rect.left + offset, right: rect.right + offset, top: rect.top + offset, bottom: rect.bottom + offset }; }
对于不支持getBoundingClientRect的浏览器可以通过其他手段取得相同信息,一般来说right和left的差值与offsetWidth值相等,bottom和top的差值与offsetHeight值相等。而且left和top属性大致等于前面的getElementLeft和getElementTop():综上创建下面这个跨浏览器函数(在某些情况下这个函数返回的值可能会有所不同,例如使用表格布局或使用滚动元素情况下,由于这里使用了argument.callee,所以这个方法不能在严格模式下使用)
function getBoundingClientRect(ele){ var scrollTop = document.documentElement.scrollTop; var scrollLeft = document.documentElement.scrollLeft; if(ele.getBoundingClientRect){ if(typeof arguments.callee.offset != 'number'){ 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 = ele.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(ele); var actualTop = getElementTop(ele); return { left : actualLeft - scrollLeft, right : actualLeft - scrollLeft + ele.offsetWidth, top: actualTop - scrollTop, bottom: actualTop- scrollTop +ele.offsetHeight } } }
遍历
DOM2级遍历和规范模块定义了两个用于辅助完成顺序遍历DOM结构的类型:NodeIterator和TreeWalker。这两个类型能够基于给定的起点对DOM结构执行深度优先的的遍历操作。<=IE8不支持DOM遍历,且在>IE8中 typeof NodeIterator == 'object'; typeof TreeWalker == 'object'; Chrome下是'function'。任何节点都可作为遍历的根节点,如果假设以html元素作为根节点,那么遍历的第一步就是访问head元素,第二部就是title节点...以document为根节点的遍历可以访问到文档中的全部节点,从document节点开始依序向前,访问的第一个节点是document,从文档最后节点开始,遍历可以反向移动到DOM树的顶端,NodeIterator和TreeWalker都以这种方式遍历
(1).NodeIterator:原型链继承关系为:document.createNodeIterator().__proto__->NodeIterator.prototypr->Object.prototype
NodeFilter.prototype->Object.prototype
可以通过document.createNodeIterator(root, whatToShow, filter, entityReferenceExpansion)方法创建NodeIterator的实例,四个参数
root:想要作为搜索起点的树中的节点
whatToShow:表示想要访问哪些节点的数字代码,是一个位掩码,通过应用一个或多个过滤器来确定要访问哪些节点。这个参数值以常量形式在NodeFilter类型中定义,如下:
NodeFilter.SHOW_ALL:显示所有类型节点,4294967295
NodeFilter.SHOW_ELEMENT:显示元素节点,1
NodeFilter.SHOW_ATTRIBUTE:显示特性节点,由于DOM结构原因,实际上不能使用这个值,2
NodeFilter.SHOW_TEXT:显示文本节点,4
NodeFilter.SHOW_CDATA_SECTION:显示CDATA节点,对HTML页面没有用,8
NodeFilter.SHOW_ENTITY_REFERENCE:显示实体引用节点,对HTML页面没有用,16
NodeFilter.SHOW_ENTITY:显示实体节点,对HTML页面没有用,32
NodeFilter.SHOW_PROCESSING_INSTRUCTION:显示处理指令节点,对HTML页面没有用,64
NodeFilter.SHOW_COMMENT:显示注释节点,128
NodeFilter.SHOW_DOCUMENT:显示文档节点,256
NodeFilter.SHOW_DOCUMENT_TYPE:显示文档类型节点,512
NodeFilter.SHOW_DOCUMENT_FRAGMENT:显示文档片段节点,对HTML页面没有用,1024
NodeFilter.SHOW_NOTATION:显示符号节点,对HTML页面没有用,2048
除了NodeFilter.SHOW_ALL外,可以使用按位或操作符组合多个选项,如 var whatToShow = NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT;
filter:是一个NodeFilter实例,或者一个表示应该接受还是拒绝某种特定节点的函数。如果不指定过滤器应该在第三个参数的位置传入null
1.每个NodeFilter实例对象上继承acceptNode方法,如果应该访问该节点该方法返回NodeFilter.FILTER_ACCEPT(1),如果不应该访问给定节点该方法返回NodeFIlter.FILTERT_SKIP(3),由于NodeFilter是一个抽象类型因此不能直接创建它的实例,在必要时只要创建一个包含acceptNode方法的对象,然后将这个对象传入createNodeIterator()中即可。
var filter = { accpectNode : function(node){ return node.tagName.toLowerCase() == 'p'? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP; } }; var iterator = document.createNodeIterator(root, NodeFilter.SHOW_ELEMENT, filter, false);
2.这个参数也可以是一个与accpectNode方法类型的函数
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);
entityReferenceExpansion:布尔值,表示是否要扩展实体引用,这个参数在HTML页面中没有用,因为其中实体引用不能扩展。
创建一个能够访问所有类型节点的NodeIterator:
var iterator = document.createNodeIterator(document, NodeFilter.SHOW_ALL, null, false);
iterator继承了NodeIterator.prototype上的nextNode和previousNode,这两方法都是基于NodeIterator在DOM结构中的内部指针工作,所以DOM结构的变化会反映在遍历结果中。
nextNode方法先返回遍历器的内部指针所在的节点,然后会将指针移向下一个节点。所有成员遍历完成后,返回null。previousNode方法则是先将指针移向上一个节点,然后返回该节点。所以
var nodeIterator = document.createNodeIterator( document.body, NodeFilter.SHOW_ELEMENT ); var currentNode = nodeIterator.nextNode(); var previousNode = nodeIterator.previousNode(); currentNode === previousNode // true是相等的
在深度优先的DOM子树遍历中,nextNode方法用于前进一步(不是绝对的一步,而是找到filter里限定条件的那个节点为止,又可能走了好多步,所以是执行了多次过滤filter,每次过滤都会选择跳过还是接受)返回找到的那个节点,previousNode用于同理向后后退。在刚刚创建的NodeIterator对象中,有一个内部指针指向根节点,因此第一次调用nextNode会返回根节点,当遍历到DOM子树最后一个节点时nextNode返回null。previousNode工作机制类似。当遍历到DOM子树最后一个节点,且previousNode返回根节点后,再次调用它就会返回null。以下面为例:
<div id="div1"> <p><b>Hello </b> world</p> <ul> <li>List 1</li> <li>List 2</li> <li>List 3</li> </ul> </div>
//遍历div中所有元素 var div = document.getElementById('div1'); var iterator = document.createNodeIterator(div, NodeFilter.SHOW_ELEMENT, null, false); var node = iterator.nextNode(); // 返回根节点 while(node !== null ){ console.log(node.tagName); node = iterator.nextNode(); }
//返回li元素 var div = document.getElementById('div1'); 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); var node = iterator.nextNode();//返回第一个li while(node !== null){ console.log(node.tagName); node = iterator.nextNode(); }
(2).TreeWalker:原型链继承关系为:document.createTreeWalker().__proto__->TreeWalker.prototype->Object.prototype
除了nextNode和previousNode外(和NodeIterator功能相同),还包括下列用于在不同方向上遍历DOM结构的方法
parentNode():遍历到当前节点的父节点
firstChild():遍历到当前节点的第一个子节点
lastChild():遍历到当前节点的最后一个子节点
nextSibling():遍历到当前节点的下一个同辈节点
previousSibling():遍历到当前节点上一个同辈节点
创建TreeWalker对象使用document.createTreeWalker()方法,四个参数同上,作为遍历起点的根节点,要显示的节点类型,过滤器,表示是否扩展实体引用的布尔值。
//替换上面的NodeIterator var div = document.getElementById('div1'); 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(); }
在这里filter的值可以有所不同,除了NodeFilter.FILTER_ACCEPT和NodeFilter.FILTER_SKIP之外,还可以有NodeFilter.FILTER_REJECT。在使用NodeIterator对象时,NodeFilter.FILTER_SKIP和NodeFilter.FILTER_REJECT作用相同,跳过指定节点,但在使用TreeWalker对象时,NodeFilter.FILTER_SKIP会跳过相应节点继续前进到子树中下一个节点,而NodeFilter.FILTER_REJECT则会跳过相应节点及该节点的整个子树。若将上面例子的NodeFilter.FILTER_SKIP改为NodeFilter.FILTER_REJECT,结果就是不会访问任何节点,因为当filter第一个节点div时判断是应该REJECT的,这一REJECT可把div和在其子树都跳过,而li就是在div子树中啊。所以就会停止遍历。
TreeWalker真正强大的地方在于能够在DOM结构中沿任何方向移动,使用TreeWalker遍历DOM树,即使不定义过滤器,也可以取得所有<li>元素,
var div = document.getElementById('div1'); var walker = document.createTreeWalker(div, NodeFilter.SHOW_ELEMENT, null, false); walker.firstChild(); //返回当前节点div的第一个子节点p walker.nextSibling();// 返回当前节点下一个同辈节点 ul var node = walker.firstChild();// 转到第一个<li> while(node !== null){ console.log(node.tagName); node = walker.nextSibling(); }
因为我们知道<li>元素在文档中的位置,所以可以直接定位到那里,因为使用了NodeFilter.SHOW_ELEMENT,所以只会返回元素不必担心使用nextSibling返回文本节点,nextSibling最终会返回null。
TreeWalker类型还有个currentNode属性,表示任何遍历方法在上一次遍历中返回的节点,通过设置这个属性也可以修改遍历继续进行的起点。
还可以修改起点,下次过滤就是从body的第一个子元素过滤了。
范围
1.DOM中的范围:
DOM2级遍历和范围模块定义了范围接口,通过范围可以选择文档中一个区域,不必考虑节点的界限(选择在后台完成,对用户是不可见的)。在常规的DOM操作不能有效的修改文档时,使用范围往往可达到目的。
多数浏览器均支持DOM范围,<=IE8以专有方式实现了自己的范围特性。
DOM中的范围:DOM2级在Document.prototype上定义了createRange方法,原型链继承关系为:document.createRange().__proto__->Range.prototype->Object.prototype
新创建的范围直接与创建它的文档关联在一起,不能用于其他文档。创建了范围后就可以使用它在后台选择文档中特定部分。创建了范围并设置了其位置之后还可以针对范围的内容执行很多种操作,从而实现对底层DOM树的更精细的控制。
startContainer:包含范围起点的节点(选中区第一个节点的父节点)
startOffset:范围在startContainer中起点的偏移量,如果startContainer是文本节点,注释节点,CDATA节点,那么startOffset就是范围起点之前跳过的字符数量。否则startOffset就是范围中第一个子节点的索引。
endContainer:包含范围终点的节点(选区中最后一个节点的父节点)
endOffset:范围在endContainer中终点的偏移量(与startOffset遵循相同的取值规则),可以理解为选区之外的第几个,看情况是否加一
commonAncestorContainer:startContainer和endContainer共同的祖先节点在文档树中位置最深的那个。
当把范围放到文档中特定位置时,这些属性都会被赋值。
<div> ->startContainer包含范围起点的节点 <!--注释节点--> ->范围开始,起点 <p>...</p> ->startOffset(范围在startContainer中起点的偏移量) <span>...</span> ->endOffset (范围在endContainer中终点的偏移量) <!-- 注释节点--> ->范围结束,重点 </div> ->endContainer包含范围终点的节点
(1).用DOM范围实现简单选择:
selectNode(node):选择整个节点包括其子节点
selectNodeContents(node):只选择节点的子节点
//HTML <html> <body> <p id="p1"><b>Hello</b> world!</p> </body> </html> //JS var p = document.getElementById('p1'); var range1 = document.createRange(), range2 = document.createRange(); range1.selectNode(p); range2.selectNodeContents(p);
可以看到在调用selectNode时候startContainer和endContainer和commonAncestorContainer都等于传入节点的父节点,即document.body。startOffset属性等于给定节点在其父节点的childNodes集合中的索引(这个例子是1——因为兼容DOM的浏览器将空格算作一个文本节点),endOffset等于startOffset加1(因为只选择了一个节点)
在调用selectNodeContents()时候,startContainer,endContainer和commonAncestorContainer等于传入的节点,即<p>元素,startOffset等于0因为范围从给定节点的第一个子节点开始,最后endOffset等于子节点数量(node.childNodes.length)这个例子中是2。
为了更精细地控制将哪些节点包含在范围内,还可使用下列方法:
setStartBefore(refNode):将范围的起点设置在refNode之前,因此refNode也就是范围选中区第一个子节点。同时会将startContainer属性设置为refNode.parentNode,将startOffset属性设置为refNode在其父节点的childNodes中的索引。
setStartAfter(refNode):将范围的起点设置在refNode之后,因此refNode也就不在范围之内了,其下一个同辈节点才是范围选区中第一个子节点,同时会将startContainer属性设置为refNode.parentNode,将startOffset属性设置为refNode在其父节点的childNodes集合中的索引加1.(??为甚不是索引而要加1)
setEndBefore(refNode):将范围的终点设置在refNode之前,因此refNode也就不在范围之内了。其上一个同辈节点才是范围选区中最后一个子节点,同时会将endContainer属性设置为refNode.parentNode,将endOffset属性设置为ref在其父节点的childNodes集合中的索引。
setEndAfter(refNode):将范围的终点设置在refNode之后,refNode也就是范围选区中最后一个子节点。同时会将endContainer属性设置为refNode.parentNode,将endOffset属性设置为refNode节点在其父节点的childNodes集合中的索引加1。
(2).用DOM范围实现复杂选择:创建复杂的范围,使用下面两个方法,参数为参照节点和偏移量值
setStart(refNode, offset):refNode会变成startContainer,偏移量值会变成startOffset。
setEnd(refNode, offset):refNode会变成endContainer,偏移量值会变成endoffset
可以使用这两个方法来模拟selectNode和selectNodeContents()
var range1 = document.createRange(), range2 = document.createRange(); var 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){ p1Index = i; break; } } range1.setStart(p1.parentNode, p1Index); range1.setEnd(p1.parentNode, p1Index+1); range2.setStart(p1, 0); range2.setEnd(p1, p1.childNodes.length);
要选择这个节点(使用range1),就必须确定当前节点p1在其父节点的childNodes集合中的索引,要选择这个节点的内容(使用range2),也就不必计算什么,只要通过setStart和setEnd设置默认值即可。这两方法更胜一筹的地方在于能够选择节点的一部分。
假设你只想选择前面HTML示例代码中从“Hello”的“llo”到“world!”的“o”:
先取得所有节点的引用:
var p1 = document.getElementById('p1'), helloNode = p1.firstChild.firstChild, worldNode = p1.lastChild;
然后在创建范围时指定相应的起点和终点:
var range = document.createRange(); range.setStart(helloNode, 2); range.setEnd(worldNode, 3);//3表示选区之外的第一个字符的位置
(3).操作DOM范围中的内容:在创建范围时,内部会为这个范围创建一个文档片段,范围所属的全部节点都被添加到了这个文档片段中。为了创建这个文档片段,范围内容的格式必须正确有效,在前面例子中我们创建的选区分别开始和结束两个文本节点的内部,因此不能算作是格式良好的DOM结构,也就无法通过DOM来表示,但是范围知道自身缺少哪些开标签和闭标签,它能够重新构建有效的DOM结构以便我们对其进行操作。对于这个例子,范围经过计算知道选区中缺少一个开始<b>标签,就会在后台动态加入一个该标签,同时还会在前面加入一个表示结束的</b>标签以结束“He”。于是修改后的DOM如下:
<p><b>He</b><b>llo</b> world!</p>
另外文本节点world也被拆分为两个文本节点,一个包含“wo”,一个包含“rld”。
创建了范围后就可以使用各种方法对范围的内容进行操作,例如从文档中删除范围所包含的内容:
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变为:
<p><b>He</b>rld!</p>
由于范围选区在修改底层DOM结构时能保证格式良好,因此即使内容被删除了最终的DOM结构依旧良好。
与deleteContents()相似,extractContents()也会从文档中移除范围选区,但这两个方法区别在于extractContents()会返回范围的文档片段对象,利用这个返回值可以将范围的内容插入到文档中其他地方。
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():创建范围对象的一个副本,然后在文档的其他地方插入该副本
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
(4).插入DOM范围中的内容:
insertNode():向范围选区的开始处插入一个节点,假设想为前面例子中的HTML前面插入以下HTML代码:
<span style="color: red">Inserted text</span>
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(node):环绕范围插入内容,node为环绕范围内容的节点。在环绕范围插入内容时,后台会执行下列操作:
提取出范围中的内容(类似执行extractContent())
将给定节点插入到文档原来范围所在的位置上
将文档片段的内容添加到给定节点中
可以使用这种技术来突出显示网页中某些词句:
var p1 = document.getElementById('p1'), helloNode = p1.firstChild.firstChild, lastNode = p1.lastChild; var range = document.createRange(); range.selectNode(helloNode); //选中Hello文本节点 var span = document.createElement('span'); span.style.backgroundColor = "yellow"; range.surroundContents(span);
这样会给范围选区加上一个黄色背景HTML代码如下:
<p id="p1"><b><span style="background-color:yellow">Hello</span></b> World!</p>
(5).折叠DOM范围:范围中未选择文档的任何部分,折叠范围时其位置会落在文档中的两个部分之间,可能是范围选区的开始位置也可能是结束位置
collapse(flag):布尔值表示要折叠到范围的哪一端,参数true表示要折叠到范围的起点,参数false表示要折叠到范围的终点。要确定范围已经折叠完毕可以检查collapsed属性。
range.collapse(true); //折叠到起点 console.log(range.collapsed); //输出true
检测某个范围是否处于折叠状态,可以帮我们确定范围中的两个节点是否紧密相邻,比如下面HTML代码:
<p id="p1">Paragraph 1</p><p>Paragraph 2</p>
如果我们不知道其实际构成(比如说这段代码是动态生成的),那么可以像下面这样创建一个范围:
这个例子中新创建的范围是折叠的,因为p1后面和p2前面什么也没有。
(6).比较DOM范围:在有多个范围情况下,可以使用compareBoundaryPoints()方法来确定这些范围是否有公共的边界(起点或终点),两个参数:比较方式的常量值和要比较的范围。常量值如下:
Range.START_TO_START(0):比较第一个范围和第二个范围的起点
Range.START_TO_END(1):比较第一个范围的起点和第二个范围的终点
Range.END_TO_END(2):比较第一个范围和第二个范围的终点
Range.END_TO_START(3):比较第一个范围的终点和第二个范围的起点
该函数返回值为如果第一个范围中的点位于第二个范围中的点之前,返回-1。如果两个点相等返回0,如果第一个范围中的点位于第二个范围中的点之后返回1。
var range1 = document.createRange(); var range2 = document.createRange(); var p1 = document.getElementById('p1'); range1.selectNodeContents(p1); range2.selectNodeContents(p1); range2.setEndBefore(p1.lastChild); range1.compareBoundaryPoints(Range.START_TO_START, range2); // 0 range1.compareBoundaryPoints(Range.END_TO_END, range2); // 1
(7).复制DOM范围:cloneRange()创建调用它的范围的一个副本。新创建的范围和原来的范围包含相同的属性,修改它的端点不会影响原来的范围
(8).清理DOM范围:在使用完范围后,最好调用detach()方法,以便从创建范围的文档中分离出该范围,调用detach之后,就可以放心地解除对范围的引用,从而让垃圾回收机制回收其内存。一旦分离范围就不能再恢复使用了。
range.detach(); //从文档中分离 range = null; //解除引用
2.IE8及更早版本中的范围
即文本范围,文本范围是IE专有特性其他浏览器都不支持,文本范围处理的主要是文本(不一定是DOM节点),通过<body>,<button>,<input>,<textarea>等这几个元素,可以调用createTextRange()方法来创建文本范围。这个方法分别在HTMLBodyElement.prototype,HTMLButtonElement.prototype,HTMLInputElement.prototype,HTMLTextAreaElement.prototype
var range = document.body.createTextRange();
像这样通过document创建的范围可以在页面中的任何地方使用(通过其他元素创建的范围则只能在相应的元素中使用)
(1).用IE范围实现简单的选择:使用findText(str)选择页面中某一区域,这个方法会找到第一次出现的给定文本,并将范围移过来以环绕该文本。如果没有找到文本这个方法返回false,否则返回true。这里的文本不区分大小写。range.text属性返回范围中包含的文本,或者也可以检查findText()返回值(在找到文本情况下返回true)。还可以为findText()传入另一个参数,即一个表示向哪个方向继续搜索的数值。负值表示应该从当前位置向后搜索,正值表示应该从当前位置向前搜索。
IE中与DOM中selectNode方法最接近的方法是moveToElementText(),这个方法接受一个DOM元素,并且选择该元素所有文本,包括HTML标签。在文本范围中包含HTML情况下,可以使用htmlText属性取得范围的全部内容,包括HTML和文本。
IE的范围没有任何属性可以随着范围选区的变化而动态更新。不过其parentElement()方法倒是与DOM的cummonAncestorContainer属性类似,这样得到的父元素始终都可以反映文本选区的父节点。
(2).使用IE范围实现复杂的选择:以特定的增量向四周移动范围,这四个方法都接受两个参数:移动单位和移动单位的数量。移动单位是下列一种字符串值:
character:逐个字符地移动
word:逐个单词(一系列非空格字符)地移动
sentence:逐个句子(一系列以句号,问号,叹号结尾的字符)地移动
textedit:移动到当前范围选区的开始或结束位置
move():首先会折叠当前范围(让起点和终点相等),然后再将范围移动指定的单位数量, range.move("character", 5); //移动5个字符 。调用move后范围的起点和终点相同,因此必须再使用moveStart或moveEnd创建新的选区
moveStart():移动范围的起点
moveEnd():移动范围的终点,移动的幅度由单位数量决定
expand():范围规范化,将任何部分选择的文本全部选中,例如假设当前选择的是一个单词中间的两个字符,调用expand("word")可以将整个单词都包含在范围之内
(3).操作IE范围中的内容:操作范围中的内容可以使用text属性或pasteHTML()方法。通过text属性可以取得范围中内容文本,也可通过这个属性设置范围中的内容文本。
向范围中插入HTML代码,使用pasteHTML(str)方法,会用str替换range.text,此时原来的htmlText和text属性均为空字符串,因为被替换了啊。
不过在范围中包含HTML代码时不应该使用pasteHTML(),因为这样很容易导致不可预料的结果即很可能是格式不正确的HTML。
(4).折叠IE范围:IE为范围提供的collapse()方法与相应的DOM方法用法一样,传入true把范围折叠到起点,传入false把范围折叠到终点。但没有对应的collapsed属性让我们知道范围是否已经折叠完毕,为此必须使用boundingWidth属性,该属性范围范围的宽度(以像素为单位)。如果等于0说明范围已经折叠了。
isEqual():用于确定两个范围是否相等
isRange():用于确定一个范围是否包含另一个范围
(6).复制IE范围:duplicate()复制文本范围,结果会创建原范围的一个副本,新创建的范围会带有与原范围完全相同的属性。
参考
《JavaScript高级程序设计》