DOM为document object model三个单词的缩写,直译过来即文档对象模型。
我们知道,一个网页是由html来搭建结构的,通过css来定义网页的样式,而javascript赋予了页面的行为,通过它我们可以与页面进行交互,实现页面的动画效果等等。那javascript究竟通过什么来实现的呢?通过ECMAScript这个标准,我们可以编写程序让浏览器来解析,利用ECMAScript,我们可以通过BOM对象(即browser object model)来操作浏览器窗口、浏览器导航对象、屏幕分辨率、浏览器历史、cookie等等。但这个通过BOM来实现的交互远远不够。要实现页面的动态交互和效果,操作html才是核心。那如何操作html呢?对,就是DOM,简单的说,DOM给我们提供了用程序来动态控制html的接口,也就是早期的DHTMl的概念。因此,DOM处在javascript赋予html具备动态交互和效果的能力的核心地位上。
首先我们通过一个案例认识一下DOM在实际开发中是如何应用的;其次来了解什么是DOM,以及与DOM相关的基础概念;然后重新认识一下html与xml的两种文档类型以及文档的各种节点类型;接着来详细的实现DOMReady;接着了解如何判断元素节点的类型以及什么是元素节点的继承层次;最后了解各种元素节点的分类和规则。
在深入学习DOM之前,先做一个热身,做一个小案例感受一下DOM在web特效开发中是如何应用的。
需求:把鼠标放到想要展示的图片上,当前图片伸展打开,其他的图片堆叠关闭。
实现:搭建结构——定义样式——着手编写交互效果代码(js+DOM)
效果图
(1).搭建结构:index.html
sliding doors
(2).定义样式:slidingdoors.css
* {
margin:0;
padding:0;
}
#container {
height: 477px;
margin: 0 auto;
border-right: 1px solid #ccc;
border-bottom: 1px solid #ccc;
overflow: hidden;
position: relative;
}
#container img {
position: absolute;
display: block;
left: 0;
border-left: 1px solid #ccc;
}
(3).编写交互效果:
第一步先定义页面加载完毕再进行DOM操作的方法。
window.οnlοad=function(){
};
window.onload方法表示当页面所有的元素都加载完毕,并且所有要请求的资源也加载完毕才触发执行function这个匿名函数里边的具体内容。这里的onload就是DOM范畴的其中一部分内容——事件。我们要实现和页面进行交互,就要通过js来操作页面元素或者说操作DOM,而要想操作DOM,就必须先找到DOM元素的节点,也就是要找到操作的对象
我们先来获得div这个容器对象,再通过获得的div对象,获得四张图片的对象集合。
//获得容器对象
var box=document.getElementById("container");
//获得图片nodeList对象集合
var imgs=box.getElementsByTagName("img");
先看效果图,当前状态是第一张图片打开,我们来看四张图片的具体位置,第一张图片的位置距离容器左侧为0;第二张图片的位置距离容器左侧为一张图片的宽度;第三张图片的位置距离容器左侧为一张图片的宽度+1个堆叠的宽度;第四张图片的位置距离容器左侧为一张图片的宽度+2个堆叠的宽度;以此类推。
返回代码,根据刚才直观的查看,想要设置我们四张图片的位置就需要知道单张图片的宽度和需要定义我们堆叠的宽度
//获得单张图片的宽度(这里的每张图片大小一致,所以可以取得任意一张图片获得其宽度,这里取第一张图片imgs[0]的宽度)
var imgWidth=imgs[0].offsetWidth;
//设置掩藏门体露出的宽度
var exposeWidth=160; //这个值可按需求自定义
我们返回到效果,我们要设置每张图片的具体位置,我们有必要将我们的总盒子的宽度设置为他应该的值,这样的话,我们的盒子才正好的将我们所有的图片包含在内。那么我们盒子的总宽度应该等于我们单张打开的图片的宽度+我们剩余的图片的堆叠的宽度的和。
回到代码
//设置容器总宽度
var boxWidth=imgWidth+(imgs.length-1)*exposeWidth;
box.style.width=boxWidth+'px';
写到这,我们可以先看看效果。目前我们四张图片还是堆叠在一起,但容器的宽度已经设置好了,下面我们就让图片归位。
回到代码,我们来设置每道门的初始位置,这里通过for循环一并为所有的图片来进行定位。由于第一道门距离容器左侧为0,我们无需设置它的位置,因此我们循环的累加变量初始值为1。
//设置每道门的初始位置
function setImgsPos(){
for(var i=1,len=imgs.length;i
imgs[i].style.left=imgWidth+exposeWidth*(i-1)+'px';
}
}
setImgsPos(); //需要调用执行一下
现在我们来浏览器中看一下效果,此时四张图片已经归位,下面我们来实现门体的滑动。鼠标滑过以后,每个应该滑动的门体滑动的距离是多少呢?我们仔细观察不难看出,这个距离就是我们图片的宽度减去我们堆叠露出的宽度。
回到代码,我们首先计算每道门打开时应移动的距离
//计算每道门打开时应移动的距离
var translate=imgWidth-exposeWidth;
//为每道门绑定事件
for(var i=0,len=imgs.length;i
//使用立即调用的函数表达式,为了获得不同的i值
(function(i){
imgs[i].οnmοuseοver=function(){
//先将每道门复位
setImgsPos();
};
})(i);
}
下面我们来实现开门滑动的效果,回到浏览器观察一下,当我们所有的图片都归位以后,当我们鼠标滑到我们想打开的门的时候,比如说第三张,我们只需要将第二张和第三张图片向左滑动即可,其他图片的滑动规律类似。比如我们放到第四张,那么我们需要将第二张、第三张、第四张图片都向左滑动,这就是我们图片滑动的规律。根据得出的规律,我们要实现打开门,只需要处理第二张到当前图片之间所有的图片即可。
回到代码
//为每道门绑定事件
for(var i=0,len=imgs.length;i
//使用立即调用的函数表达式,为了获得不同的i值
(function(i){
imgs[i].οnmοuseοver=function(){
//先将每道门复位
setImgsPos();
//打开门
for(var j=1;j<=i;j++){
imgs[j].style.left=parseInt(imgs[j].style.left,10)-translate+'px';
}
};
})(i);
}
到此,这个案例的js代码和DOM操作的代码就编写完毕了。
完整的js代码:slidingdoors.js
window.onload = function() {
//容器对象
var box = document.getElementById('container');
//获得图片NodeList对象集合
var imgs = box.getElementsByTagName('img');
//单张图片的宽度
var imgWidth = imgs[0].offsetWidth;
//设置掩藏门体露出的宽度
var exposeWidth = 160;
//设置容器总宽度
var boxWidth = imgWidth + (imgs.length - 1) * exposeWidth;
box.style.width = boxWidth + 'px';
//设置每道门的初始位置
function setImgsPos() {
for (var i = 1, len = imgs.length; i < len; i++) {
imgs[i].style.left = imgWidth + exposeWidth * (i - 1) + 'px';
}
}
setImgsPos();
//计算每道门打开时应移动的距离
var translate = imgWidth - exposeWidth;
//为每道门绑定事件
for (var i = 0, len = imgs.length; i < len; i++) {
//使用立即调用的函数表答式,为了获得不同的i值
(function(i) {
imgs[i].onmouseover = function() {
//先将每道门复位
setImgsPos();
//打开门
for (var j = 1; j <= i; j++) {
imgs[j].style.left = parseInt(imgs[j].style.left, 10) - translate + 'px';
}
};
})(i);
}
};
DOM(文档对象模型)是针对xml经过扩展用于html的应用程序编程接口,我们又叫API。DOM把整个页面映射为一个多层的节点结构,html或xml页面中的每个组成部分都是某种类型的节点,这些节点又包含着不同类型的数据。
DOM级别
(1).DOM1级:由两个模块组成,即DOM Core(DOM核心)和DOM HTML。其中DOM Core规定的是如何映射基于xml的文档结构,以便简化对文档中任何部分的访问和操作;DOM HTML模块则在DOM Core的基础上加以扩展,添加了针对html的对象和方法。
其实DOM并不是针对javascript,很多别的语言也都实现了DOM,不过在web浏览器中基于ECMAScript实现的DOM的确已经成为javascript这门语言的一个重要组成部分。如果说DOM1级的目标主要是映射文档的结构,那么DOM2级的目标就要宽泛很多了。
(2).DOM2级:由四个模块组成,即DOM Views(DOM 视图)、DOM Events(DOM事件)、DOM Style、DOM Traversal and Range(DOM遍历和范围),DOM Views定义了跟踪不同文档视图的接口,比如跟踪应用css之前和应用css之后的文档视图;DOM Events定义了事件和事件处理的接口;DOM Style定义了基于css为元素应用样式的接口;DOM Traversal and Range定义了遍历和操作文档树的接口。
(3).DOM3级:进一步扩展了DOM,引入了以统一方式加载和保存文档的方法,它在DOM Load And Save这个模块中定义;同时新增了验证文档的方法,是在DOM Validation这个模块中定义的。
我们在阅读DOM标准的时候,经常会看到DOM0级这样的字眼,实际上DOM0级这个标准是不存在的。所谓DOM0级只是DOM历史坐标系中的一个参照点而已,具体地说DOM0级就是指IE4.0和Netscape navigator4.0最初支持的那个DHTML。
浏览器 | DOM兼容性 |
---|---|
Netscape Navigator 1.~4.x | - |
Netscape 6+(Mozilla 0.6.0+) |
1级、2级(几乎全部)、3级(部分) |
IE2~IE4.x |
- |
IE5 |
1级(最小限度) |
IE5.5~IE8 |
1级(几乎全部) |
IE9+ |
1级、2级、3级 |
Opera 1~6 |
- |
Opera7~8.x |
1级(几乎全部)、2级(部分) |
Opera9~9.9 |
1级、2级(几乎全部)、3级(部分) |
Opera 10+ |
1级、2级、3级(部分) |
Safari 1.0.x |
1级 |
Safari 2+ |
1级、2级(部分) |
Chrome 1+ |
1级、2级(部分) |
Firefox 1+ |
1级、2级(几乎全部)、3级(部分) |
DocumentFragment文档片段节点
这段代码在浏览器查看元素时就会发现,页面中并没有插入DocumentFragment节点自身,它只作为一个临时占位符存在。
(1).DOM nodeType
通过DOM节点类型,我们可知,可以通过某个节点的nodeType属性来获得节点的类型,节点的类型可以是数值常量或者字符常量。示例代码如下:
nodeType
这是一个元素节点
先看示例代码
nodeName,nodeValue
这是一个元素节点
节点中的nodeName和nodeValue
function myReady(fn){
//对于现代浏览器,对DOMContentLoaded事件的处理采用标准的事件绑定方式
if ( document.addEventListener ) {
document.addEventListener("DOMContentLoaded", fn, false);
} else {
IEContentLoaded(fn);
}
//IE模拟DOMContentLoaded
function IEContentLoaded (fn) {
var d = window.document;
var done = false;
//只执行一次用户的回调函数init()
var init = function () {
if (!done) {
done = true;
fn();
}
};
(function () {
try {
// DOM树未创建完之前调用doScroll会抛出错误
d.documentElement.doScroll('left');
} catch (e) {
//延迟再试一次~
setTimeout(arguments.callee, 50);
return;
}
// 没有错误就表示DOM树创建完毕,然后立马执行用户回调
init();
})();
//监听document的加载状态
d.onreadystatechange = function() {
// 如果用户是在domReady之后绑定的函数,就立马执行
if (d.readyState == 'complete') {
d.onreadystatechange = null;
init();
}
}
}
}
在页面中引入donReady.js文件,引用myReady(回调函数)方法即可。
isElement
aaa
!!一般用来将后面的表达式转换为布尔型的数据(boolean).
因为javascript是弱类型的语言(变量没有固定的数据类型)所以有时需要强制转换为相应的类型,类似的如:
a=parseInt("1234");
a="1234"+0 //转换为数字
b=1234+"" //转换为字符串
c=someObject.toString() //将对象转换为字符串
其中第1种、第4种为显式转换,2、3为隐式转换.
布尔型的转换,javascript约定和c类似,规则为 :
false、undefinded、null、0、"" 为 false ;
true、1、"somestring"、[Object] 为 true .
!!el表示判断el是否存在,存在为true,反之为false.
isElement
aaa
这样,在判断a是否是元素节点时,结果就是false了。
isXML
isXML
isXML
isHTML
有了以上判断XML和HTML文档的方法,我们就可以实现一个元素节点属于HTML还是XML文档的方法了,实现代码如下:
isHTMLElement
元素之间的包含关系,用自带的contains方法,只有两个都是元素节点,才能兼容各个浏览器,否则ie浏览器有的版本是不支持的,可以采用hack技术,自己写一个contains方法去兼容。
元素之间的包含关系:contains()方法
兼容各浏览器的contains()方法
contains 子节点内容
contains 子节点内容继承层次与嵌套规则
(1).DOM节点继承层次
DOM节点是一个非常复杂的东西,对它的每一个属性的访问,不走运的话,就可能会向上溯寻到N多个原型链,因此DOM操作是个非常耗性能的操作。风头正盛的react为了解决这个问题,提出了虚拟DOM的概念,合并和屏蔽了很多无效的DOM操作,效果非常惊人。接下来看看DOM节点究竟是如何继承的。
(1).例如使用document.createElement("p")创建p元素,其实document.createElement("p")是HTMLParagraphElement的一个实例,而HTMLParagraphElement的父类是HTMLElement,HTMLElement的父类是Element,Element的父类是Node,Node的父类是EventTarget,EventTarget的父类是Function,Function的父类是Object。创建一个p元素一共溯寻了7层原型链
(2).例如使用document.createTextNode("xxx")创建文本节点,其实document.createTextNode("xxx")是Text的一个实例,而Text的父类是CharactorData,CharactorData的父类是Node,Node的父类是EventTarget,EventTarget的父类是Function,Function的父类是Object。创建一个文本节点一共溯寻了6层原型链
DOM inheritance hierarchy
HTML存在许多种类型的标签,有的标签下面只允许特定的标签存在,这就叫HTML嵌套规则。
不按HTML嵌套规则写,浏览器就不会正确解析,会将不符合嵌套规则的节点放到目标节点的下面,或者变成纯文本。
关于HTML嵌套规则,一定要掌握块状元素和内联元素的区别。 块状元素:一般是其他元素的容器,可容纳内联元素和其他块状元素,块状元素排斥其他元素与其位于同一行,宽度(width)高度(height)起作用。常见块状元素为div和p 内联元素:内联元素只能容纳文本或者其他内联元素,它允许其他内联元素与其位于同一行,但宽度(width)高度(height)不起作用。常见内联元素为a. 块状元素与内联元素嵌套规则: (1).块元素可以包含内联元素或某些块元素,但内联元素却不能包含块元素,它只能包含其他的内联元素 例:(2).块级元素不能放在
例:里面
(3).有几个特殊的块级元素提倡只能包含内联元素,不能再包含块级元素,这几个特殊的标签是
h1、h2、 h3、h4、 h5、 h6、 p 、dt
(4).li标签可以包含div标签
(5).块级元素与块级元素并列,内联元素与内联元素并列