1、DOM
Document Object Model,文档对象模型 JS通过DOM来对HTML文档进行操作,理解了DOM就可以随意操作WEB页面 文档:整个HTML网页 对象:将网页中的每一个部分都转换为一个对象 模型:使用模型来表示对象之间的关系,方便获取对象2、节点
⑴ 概念
Node,构成网页的最基本的组成部分,网页中的每一个部分都可以称为是一个节点 节点的类型有多种,类型不同,它们的属性和方法也不相同,常用的节点有4种:① 文档节点:整个HTML文档
② 元素节点:HTML文档中的HTML标签
③ 属性节点:元素的属性
④ 文本节点:HTML标签中的文本内容
⑵ 操作节点的方法
① 根据ID获取元素节点
document.getElementById(''); 传入ID② 获取和修改元素节点的内容
元素节点.innerHTML; 通过innerHTML属性来获取。既然可以获取到属性,也可以修改其属性 元素节点.innerHTML = ???;3、事件
在文档或浏览器窗口中发生的一些特定的交互的瞬间。例如点击某个元素,移动鼠标到某个元素的上方,或按下某个按键等 为元素添加事件有2种方式,一种是写到元素对应的属性节点中;另外一种是给元素对应的事件属性赋值,需要赋一个函数。推荐使用第二种,因为它降低了代码的耦合度onclick是点击事件
ondblclick是双击事件
示例:
<body>
<button onclick="alert('被点击!')">按钮1</button>
<button id="btn">按钮2</button>
<script type="text/javascript">
var btn = document.getElementById('btn');
btn.ondblclick = function() {
alert('双击!');
};
</script>
</body>
4、文档的加载
浏览器在加载整个页面时,是按照自上向下的顺序加载的,是逐行解析运行的,如果将script标签写到head标签中,则在执行JS代码时,页面还没有加载,DOM对象也没有加载,从而导致无法获取到DOM对象 对此window提供了一个onload事件,它会在整个页面加载完毕后被触发,这样就可以确保代码执行时,所有的DOM对象都已经加载完毕了示例:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<script type="text/javascript">
var btn = document.getElementById('btn');
console.log(btn); // null
btn.onclick = function() { // Uncaught TypeError: Cannot set property 'onclick' of null
alert(123);
};
</script>
</head>
<body>
<input id="btn" type="button" value="按钮" />
</body>
</html>
可以看到这样是会报错的,因为在为btn绑定onclick事件时,因为无法找到DOM对象
解决的办法有2种:要么把现有的JS代码都写到window的onload事件函数中
window.onload = function() {
// JS代码
};
或者把script标签写到body标签的底部
如果把script标签写到head标签里面,好处是方便管理,不好的地方是写到上面,因为页面还没有加载完成,就先解析了JS代码,没有必要,而且也会影响页面加载的速度(现在网速较之前快了很多,感觉也不是很明显)
而把script标签写到body标签的底部,虽然管理上不太方便,但是它可以待页面加载完毕后,再解析JS代码,一定程度上提高了页面的加载速度
5、Document对象
⑴ 概念
每个载入浏览器的HTML文档都会成为一个Document对象,它可以让我们对HTML页面中的所有DOM元素进行访问 Document对象是Window对象的一部分,可通过 window.document 属性对其进行访问⑵ 属性
① body
即<body></body>元素② cookie
设置或获取cookie③ referrer
返回加载当前页面之前的URL,即该页面是通过其他页面跳转过来的④ title
设置或获取当前页面的标题⑶ 方法
write
向HTML文档中写入HTML表达式或JavaScript代码6、DOM查询
⑴ 根据ID查询唯一DOM对象
document.getElementById(); 传入DOM的id属性值示例:
<input id="btn" type="button" value="按钮" />
<script type="text/javascript">
var btn = document.getElementById('btn');
btn.onclick = function() {
alert(1);
};
</script>
⑵ 根据DOM标签名,查找所有该类的DOM对象
document.getElementsByTagName(); 传入DOM标签名,该方法返回结果不止一个DOM对象示例:
<span>1</span>
<span>2</span>
<span>3</span>
<script type="text/javascript">
var spans = document.getElementsByTagName('span');
for (var i = 0, len = spans.length; i < len; ++i) {
console.log(spans[i].innerHTML);
}
</script>
Tips:innerHTML属性用于返回DOM对象标签里的内容(包括HTML标签),与它类似的还有一个innerText属性,不同的是innerText只会返回文本内容,不会返回HTML标签
⑶ 根据DOM标签的name属性值,获取相同name属性的所有DOM对象
document.getElementsByName(); 传入DOM标签的name属性值,该方法返回结果不止一个DOM对象示例:
<input type="radio" class="gender" name="gender" value="man" />男
<input type="radio" class="gender" name="gender" value="woman" />女
<script type="text/javascript">
var genders = document.getElementsByName('gender');
var dom;
for (var i = 0, len = genders.length; i < len; i++) {
dom = genders[i];
console.log(dom.value + ' ' + dom['className']);
}
</script>
注意:元素的class属性值,不能通过class来获取,因为它是JS的保留字,所以需要通过className来获取
⑷ 获取当前节点的所有子节点
DOM元素.childNodes 该属性会获取包括文本节点在内的所有节点 注意:DOM标签之间的空白也会被当成文本节点示例:
<ul>
<li>A</li>
</ul>
<ol><li>B</li></ol>
<script type="text/javascript">
var ul = document.getElementsByTagName('ul')[0];
console.log(ul.childNodes.length); // 3
var ol = document.getElementsByTagName('ol')[0];
console.log(ol.childNodes.length); // 1
</script>
因为ul里的子元素前后都有换行存在,而它们被当成了文本节点来解析了,所以ul的子节点的个数是3个
⑸ 获取当前节点的第一个子节点
DOM元素.firstChild 类似的有一个属性firstElementChild,它不会将空白当成节点来解析,不过它不支持IE8及以下版本示例:
<ol><li>B</li></ol>
<script type="text/javascript">
var ol = document.getElementsByTagName('ol')[0];
console.log(ol.firstChild.innerHTML); // B
</script>
⑹ 获取当前节点的最后一个子节点
DOM元素.lastChild 类似的有一个属性lastElementChild,它不会将空白当成节点来解析,不过它不支持IE8及以下版本示例:
<ul>
<li>A</li>
</ul>
<script type="text/javascript">
var ul = document.getElementsByTagName('ul')[0];
console.log(ul.lastChild); // #text
console.log(ul.lastChild.innerHTML); // undefined
console.log(ul.lastElementChild); // <li>A</li>
console.log(ul.lastElementChild.innerHTML); // A
</script>
⑺ 获取当前节点的父节点
DOM元素.parentNode⑻ 获取当前节点的前一个兄弟节点
DOM元素.previousSibling 注意:元素间的空白会被当成文本节点来解析,所以它也会被当作兄弟节点 类似的有一个属性previousElementSibling,它不会将空白当成节点来解析,不过它不支持IE8及以下版本⑼ 获取当前节点的后一个兄弟节点
DOM元素.nextSibling 注意:元素间的空白会被当成文本节点来解析,所以它也会被当作兄弟节点 类似的有一个属性nextElementSibling,它不会将空白当成节点来解析,不过它不支持IE8及以下版本示例:
<body>
<span>A</span>
<span id="b">B</span><span>C</span>
<script type="text/javascript">
var spanB = document.getElementById('b');
var parent = spanB.parentNode;
console.log(parent); // <body>...</body>
console.log(spanB.previousSibling); // #text
console.log(spanB.previousElementSibling.innerText); // A
console.log(spanB.nextSibling.innerHTML); // C
</script>
</body>
⑽ 获取body DOM对象
document.body示例:
var body = document.body;
var body1 = document.getElementsByTagName('body')[0];
console.log(body === body1); // true
⑾ 获取html DOM对象
document.documentElement示例:
var html = document.documentElement;
var html1 = document.getElementsByTagName('html')[0];
console.log(html === html1); // true
⑿ 获取页面上所有的DOM对象
document.all 或 document.getElementsByTagName('*');示例:
var all = document.all;
console.log(typeof all); // undefined
console.log(all.length); // 5
console.log(all); // [html, head, meta, body, script]
var all1 = document.getElementsByTagName('*');
console.log(typeof all1); // object
console.log(all1.length); // 5
console.log(all1); // [html, head, meta, body, script]
⒀ 根据标签的class属性来获取对应的DOM对象
document.getElementsByClassName(); 传入类属性值 注意:该方法不支持IE8及以下版本示例:
<div class='block'>A</div>
<div class='block'>B</div>
<div class='block'>C</div>
<script type="text/javascript">
var blocks = document.getElementsByClassName('block');
var len = blocks.length;
console.log(len); // 3
for (var i = 0; i < len; ++i) {
console.log(blocks[i].innerText);
}
</script>
⒁ 根据标签的类或ID来选择对应的DOM对象
document.querySelector(); document.querySelectorAll(); 传入class属性或id属性这2个方法的区别在于:上面的方法只会返回1个DOM对象,如果匹配的结果有多个,则只返回匹配到第1个DOM对象;而下面的方法,无论匹配到的结果有几个,其结果都会封装到一个数组当中
示例:
<div class="block">A</div>
<div class="block">B</div>
<div class="block">C</div>
<div id="d">D</div>
<script type="text/javascript">
var block = document.querySelector('.block');
console.log(block.innerHTML); // A
var blocks = document.querySelectorAll('.block');
for (var i = 0, len = blocks.length; i < len; i++) {
console.log(blocks[i].innerHTML);
}
blocks = document.querySelectorAll('div');
console.log(blocks.length); // 4
var d = document.querySelector('#d');
var d1 = document.querySelectorAll('#d');
console.log(d.innerHTML); // D
console.log(d1.length); // 1
console.log(d === d1[0]); // true
</script>
7、DOM查询方法的对比
⑴ 传统方法
getElementById、getElementsByTagName、getElementsByName、getElementsByClassName① 语法简单,查询速度快。但是使用起来不简便
② 获取多个元素的方法(除了getElementById),如果增加或移除了集合中的元素,则操作前的结果数和操作后的结果数不相同,因为它是会更新的(动态NodeList)
⑵ H5新增方法
querySelector和querySelectorAll① 使用简便。但查询语法是CSS选择器,查询速度较慢
② 获取多个元素的方法(querySelectorAll),查询到的结果集是不可变的(静态NodeList)
8、NodeList
NodeList是一组元素节点的集合,每个节点都有其索引值(从0开始,类似于数组)。元素节点在NodeList中保存的顺序和它们在HTML页面中存在的顺序是一致的getElementsByName、getElementsByTagName、getElementsByClassName和querySelectorAll 这些方法所返回的结果就是一个NodeList
通过NodeList可以选择要操作的DOM元素或遍历它们
动态NodeList和静态NodeList的区别在于,操作NodeList后,查询到的结果是否会同步更新,如果更新就是动态的
示例:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>NodeList</title>
</head>
<body>
<h1>1</h1>
<h1>2</h1>
<h1>3</h1>
<script type="text/javascript">
var hDoms = document.getElementsByTagName('h1'); // 缓存DOM查询
var h1Doms = document.querySelectorAll('h1');
console.log(hDoms.length); // 3
console.log(h1Doms.length); // 3
document.body.removeChild(hDoms[1]);
console.log(hDoms.length); // 2
console.log(h1Doms.length); // 3
</script>
</body>
</html>
9、DOM增删改
⑴ 创建DOM
① 创建元素节点
document.createElement(); 传入标签名称② 创建文本节点
document.createTextNode(); 传入文本内容③ 添加子节点【在末端】
元素对象.appendChild(); 传入子节点对象示例:
var body = document.body;
var h1 = document.createElement('h1');
var txt = document.createTextNode('Hello!');
h1.appendChild(txt);
body.appendChild(h1);
添加一个h1元素到body中
④ 添加子节点【在某指定节点之前】
元素对象.insertBefore(新元素对象, 指定元素对象);示例:
<ul>
<li>1</li>
<li>2</li>
</ul>
<script type="text/javascript">
var ul = document.querySelector('ul');
var li = document.querySelector('li');
var newLi = document.createElement('li');
var txt = document.createTextNode('0');
newLi.appendChild(txt);
ul.insertBefore(newLi, li);
</script>
添加0(li)到1(li)的前面
⑵ 替换DOM
元素对象.replaceChild(新元素对象, 旧元素对象);示例:
<h1>你好!</h1>
<script type="text/javascript">
var p = document.createElement('p');
var txt = document.createTextNode('Hello!');
p.appendChild(txt);
var h1 = document.getElementsByTagName('h1')[0];
var body = document.body;
body.replaceChild(p, h1);
</script>
使用p标签替换h1标签
⑶ 删除DOM
元素对象.removeChild(目标对象);示例:
<ul>
<li>1</li>
<li>2</li>
</ul>
<script type="text/javascript">
var ul = document.querySelector('ul');
var li = document.querySelector('li');
ul.removeChild(li);
</script>
移除1(li)元素
10、DOM样式(一)
⑴ 操作内联样式
DOM对象.style.样式名称 = 样式值;注意:
① 如果样式名称是带有 - 的,则使用驼峰命名法,例如在CSS中背景颜色是 background-color;而到了JS中,则需要换成 backgroundColor
② 这种方式操作的是元素的内联样式,会修改元素的style属性。因为内联样式的优先级较高,如果想让某样式属性不被修改,则可以在CSS样式值后面添加 !important
③ 因为操作的是元素的内联样式,所以如果想获取的样式不是内联样式,则返回值结果为空串
示例:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<style type="text/css">
div {
width: 300px;
height: 300px;
background-color: #bfa !important;
border: 1px solid black;
}
</style>
</head>
<body>
<div></div>
<input type="button" id="btn" value="点我改变div样式" />
<script type="text/javascript">
var btn = document.getElementById('btn');
btn.onclick = function() {
var div = document.getElementsByTagName('div')[0];
div.style.borderTopWidth = '5px';
div.style.width = '100px';
div.style.height = '100px';
console.log(div.style.border); // 【空串】
div.style.backgroundColor = 'red';
};
</script>
</body>
</html>
⑵ 读取元素对象的样式【只能获取,不能修改】
① IE方法
DOM对象.currentStyle.样式名称;注意:该方式仅适用于IE浏览器
② 其他浏览器方法
getComputedStyle(样式名称, 伪元素).样式名; Tips:伪元素可以传入null注意:该方法不适用于IE浏览器
示例:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<style type="text/css">
div {
width: 100px;
height: 100px;
background-color: #bfa;
}
</style>
</head>
<body>
<div></div>
<script type="text/javascript">
var div = document.getElementsByTagName('div')[0];
var bgc = getComputedStyle(div, null)['backgroundColor'];
// var bgc = div.currentStyle.backgroundColor; // #bfa 【IE浏览器】
console.log(bgc); // rgb(187, 255, 170)
//getComputedStyle(div, null).backgroundColor = 'red'; // Uncaught DOMException: Failed to set the 'background-color' property on 'CSSStyleDeclaration': These styles are computed, and therefore the 'background-color' property is read-only.
// div.currentStyle['backgroundColor'] = 'red'; // SCRIPT5022: NoModificationAllowedError 【IE浏览器】
</script>
</body>
</html>
可以看到通过这种方式只能获取元素的样式,而不能修改元素的样式,因为计算后的样式属性是只读的
⑶ 通用的获取样式的方法
function getStyle(element, styleName) {
return window.getComputedStyle ? window.getComputedStyle(element, null)[styleName] : element.currentStyle[styleName];
}
分析:因为getComputedStyle是window的属性,所以可以通过if来判断其存在不存在,如果存在则证明可以使用该方法,否则就调用IE浏览器的独有方法,来获取样式值
如果单纯地判断getComputedStyle,则是判断该变量是否存在,没有找到则会报错;如果是判断对象的属性,没有找到,则返回undefined,转换为Boolean类型就是false
11、DOM样式(二)
⑴ 获取DOM元素的可见高度和宽度(不包括外边距)
DOM对象.clientHeight DOM对象.clientWidth注意:这2个属性可以获取DOM元素的 高度/宽度 + 内边距,但是不会加上外边距
示例:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<style type="text/css">
div {
width:100px;
height:100px;
background-color:yellow;
}
#d2,#d3 {
padding:10px;
background-color:red;
}
#d3 {
border:10px solid yellow;
background-color:orange;
}
</style>
</head>
<body>
<div id="d"></div><br />
<div id="d2"></div><br />
<div id="d3"></div><br />
<script type="text/javascript">
var d = document.getElementById('d');
console.log(d.clientWidth); // 100
console.log(d.clientHeight); // 100
var d2 = document.getElementById('d2');
console.log(d2.clientWidth); // 120
console.log(d2.clientHeight); // 120
var d3 = document.getElementById('d3');
console.log(d3.clientWidth); // 120
console.log(d3.clientHeight); // 120
</script>
</body>
</html>
⑵ 获取元素的可见高度和宽度(包括外边距)
DOM对象.offsetHeight DOM对象.offsetWidth示例:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<style type="text/css">
#d {
width:100px;
height:100px;
background-color:yellow;
padding:10px;
border:10px solid red;
}
</style>
</head>
<body>
<div id="d"></div><br />
<script type="text/javascript">
var d = document.getElementById('d');
console.log(d.clientWidth); // 120
console.log(d.clientHeight); // 120
console.log(d.offsetWidth); // 140
console.log(d.offsetHeight); // 140
</script>
</body>
</html>
⑶ 获取元素的父元素
DOM对象.offsetParent注意:该属性获取的是距离最近的相对父元素对象
示例:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<style type="text/css">
div {
width:100px;
height:100px;
}
#d1 { background-color:yellow; }
#parent { position:relative; }
#d2 { background-color:red; }
</style>
</head>
<body id="body">
<div id="d1"></div><br />
<div id="parent">
<div id="d2"></div>
</div>
<script type="text/javascript">
var d1 = document.getElementById('d1');
console.log(d1.offsetParent.id); // body
var d2 = document.getElementById('d2');
console.log(d2.offsetParent.id); // parent
</script>
</body>
</html>
⑷ 获取元素的位置偏移量
DOM对象.offsetLeft DOM对象.offsetTop注意:获取到的偏移距离是,相对于偏移父元素的距离
示例:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<style type="text/css">
* {
padding:0;
margin:0;
}
div {
width:100px;
height:100px;
}
#d1 {
background-color:yellow;
margin:10px 15px;
}
#parent {
position:relative;
padding:20px 8px;
}
#d2 { background-color:red; }
</style>
</head>
<body id="body">
<div id="d1"></div><br />
<div id="parent">
<div id="d2"></div>
</div>
<script type="text/javascript">
var d1 = document.getElementById('d1');
console.log(d1.offsetTop); // 10
console.log(d1.offsetLeft); // 15
var d2 = document.getElementById('d2');
console.log(d2.offsetLeft); // 8
console.log(d2.offsetTop); // 20
</script>
</body>
</html>
⑸ 获取元素的滚动条偏移高度和偏移宽度及整体高度和整体宽度
DOM对象.scrollTop DOM对象.scrollLeft DOM对象.scrollHeight DOM对象.scrollWidth注意:元素滚动条的偏移高度和偏移宽度,会把滚动条的高度和宽度也计算在内,所以得到的值要小于元素的可见高度(clientHeight)和可见宽度(clientWidth)
Tips:
元素整体高度 = 元素偏移高度 + 元素可见高度
clientHeight = scrollTop + scrollHeight
元素整体宽度 = 元素偏移宽度 + 元素可见宽度
clientWidth = scrollLeft + scrollWidth
示例:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<style type="text/css">
#parent {
width:200px;
height:300px;
background-color:red;
overflow:auto;
}
#d1 {
width:175px;
height:500px;
background-color:yellow;
}
</style>
</head>
<body id="body">
<div id="parent">
<div id="d1"></div>
</div>
<script type="text/javascript">
var d1 = document.getElementById('d1');
console.log(d1.scrollWidth); // 175
console.log(d1.scrollHeight); // 500
var parent = document.getElementById('parent');
console.log(parent.scrollLeft); // 0
parent.onscroll = function() { // 滚动条滚动事件
console.log(parent.scrollTop); // 当滚动到底部时,输出值为200【scrollHeight - clientHeight】
};
</script>
</body>
</html>
12、class的操作
⑴ 概念
通过DOM元素的className属性,可以获取元素的class属性值通过修改style属性的方式,来修改DOM元素的内联样式,每次修改时,浏览器都需要重新渲染一次页面,执行性能较差,而且如果同时修改多个样式,也比较麻烦
所以通过修改元素的class属性,来间接修改元素的样式。这样只需修改一次,即可同时修改多个样式,而且浏览器只需渲染一次,性能较好,而且也将 表现(CSS) 和 行为(JS) 分离了开来
⑵ 示例函数
① 判断元素是否含有某一class属性
function hasClass(element, className) {
var regExp = new RegExp('\\b' + className + '\\b');
return regExp.test(element.className);
}
② 给元素添加某一class属性
function addClass(element, className) {
if (hasClass(element, className)) { return; }
var cn = element.className;
element.className = ('' === cn) ? className : cn + ' ' + className;
}
③ 移除元素的某一class属性
function removeClass(element, className) {
var regExp = new RegExp('\\b' + className + '\\b');
var cn = element.className;
cn = cn.replace(regExp, '').replace(/\s/g, '');
element.className = cn;
}
④ 如果存在(不存在)就删除(添加)某一class属性
function toggleClass(element, className) {
hasClass(element, className) ? removeClass(element, className) : addClass(element, className);
}
13、事件对象
⑴ 概念
当事件的响应函数被触发时,浏览器每次都会将一个事件对象作为实参传递给响应函数 在事件对象中封装了当前事件相关联的一切信息,例如 鼠标的坐标位置、键盘的哪个键被按下、鼠标的滚轮滚动的方向等注意:在IE8及以下版本中,event对象不会被浏览器作为实参传递给响应函数,而是被保存为window的属性。如果通过window获取event对象,在Firefox浏览器中,则不支持
⑵ 属性
鼠标的水平和垂直坐标 clientX 和 clientY示例:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<style type="text/css">
div {
width:400px;
border:1px solid black;
}
#area { height:100px; }
#dashboard {
height:30px;
text-align:center;
line-height:30px;
}
</style>
</head>
<body id="body">
<div id="area"></div><br />
<div id="dashboard"></div>
<script>
var area = document.getElementById('area');
var dashboard = document.getElementById('dashboard');
area.onmousemove = function(event) { // 鼠标移动响应函数
event = (event ?) event : window.event; // 判断event对象是否存在,并赋值,解决浏览器兼容问题
dashboard.innerHTML = 'X: ' + event.clientX + ', Y: ' + event.clientY;
};
</script>
</body>
</html>
14、元素跟随鼠标移动【应用】
⑴ 相关属性和事件
① 属性
clientX、clientY 可视区域的鼠标的X和Y坐标scrollLeft、scrollTop
元素的滚动条的左偏移距离和顶部偏移距离
② 方法
onmousemove 鼠标移动事件⑵ 示例
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>元素跟随鼠标移动</title>
<style type="text/css">
body { height:1000px; }
div {
width: 100px;
height: 100px;
background-color: red;
/* 开启相对定位 */
position: relative;
}
</style>
</head>
<body>
<div></div>
<script type="text/javascript">
var div = document.getElementsByTagName('div')[0];
var doc = document.documentElement; // Firefox和IE支持document
var body = document.body; // Chrome支持body
var dsl;
var dst;
doc.onmousemove = function(event) {
dsl = body.scrollLeft || doc.scrollLeft;
dst = body.scrollTop || doc.scrollTop;
event = event || window.event; // 兼容IE8及以下浏览器
div.style.left = (event.clientX + dsl) + 'px';
div.style.top = (event.clientY + dst) + 'px';
};
</script>
</body>
</html>
⑶ 注意事项
① 需要给要移动的元素,设置相对定位的样式② 鼠标移动事件需要绑定到document对象上
③ 因为网页的高度可能超过页面展示的高度,所以需要加上滚动条的偏移距离。但是要注意的是:Firefox和IE浏览器认为滚动条的偏移距离是document的;而Chrome认为滚动条的偏移距离是body的
④ 获取到的鼠标在页面上的可视坐标和滚动条的偏移距离只有数值,方便了计算,但是在给元素设置top和left样式时,需要再添加上单位(例如:px)
15、事件的冒泡
⑴ 概念
子元素事件的触发,也会冒泡触发到父元素上⑵ 示例
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>事件的冒泡</title>
<style type="text/css">
span {
width:100px;
height:100px;
background-color:yellow;
float:left;
}
div {
width:200px;
height:200px;
background-color:red;
}
</style>
</head>
<body>
<div>
<span></span>
</div>
<script type="text/javascript">
var span = queryElement('span');
var div = queryElement('div');
var body = queryElement('body');
span.onclick = function() {
alert('你点击了span!');
};
div.onclick = function() {
alert('你点击了div!');
};
body.onclick = function() {
alert('你点击了body!');
};
function queryElement(tagName) {
return document.getElementsByTagName(tagName)[0];
}
</script>
</body>
</html>
⑶ 阻止事件的冒泡
将event对象的cancelBubble属性设置为false即可 注意:这个属性是IE的,但是Chrome和Firefox也支持或者调用event对象的stopPropagation() 方法,但是要注意这个方法不支持IE8及以下版本
改进:
元素.onclick = function(event) {
event = event || window.event;
event.cancelBubble = true;
alert('你点击了[元素]!');
};
⑷ 事件的冒泡的应用
事件的冒泡在大部分场景下是有用的例如鼠标的移动事件,假如说子元素取消了移动事件的冒泡,则无法移动到父元素内部
示例:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>事件的冒泡</title>
<style type="text/css">
#div1 {
width:100px;
height:100px;
background-color:yellow;
position:relative;
}
#div2 {
width:500px;
height:500px;
background-color:red;
}
</style>
</head>
<body>
<div id="div2">
<div id="div1"></div>
</div>
<script type="text/javascript">
var div1 = document.getElementById('div1');
var div2 = document.getElementById('div2');
document.body.onmousemove = function(event) {
event = event || window.event;
div1.style.left = event.clientX + 'px';
div1.style.top = event.clientY + 'px';
};
div2.onmousemove = function(event) {
event = event || window.event;
event.cancelBubble = true;
};
</script>
</body>
</html>
16、事件的委派
⑴ 概念
通过给元素的父元素绑定事件,再在事件中判断元素是否为指定的子元素,并触发相应的逻辑⑵ 相关属性
event事件对象的target属性 返回触发此事件的元素(事件的目标节点),通过这个属性可以获取目标子元素⑶ 示例
<ul>
<li>无序列表-A</li>
<li>无序列表-B</li>
<li>无序列表-C</li>
</ul>
<script type="text/javascript">
var lis = document.getElementsByTagName('li');
for (var i = 0, len = lis.length; i < len; i++) {
lis[i].onclick = function() {
alert('你点击了【' + this.innerText + '】!');
};
}
</script>
循环给每个li添加点击事件,比较麻烦。可以给其父元素,ul添加点击事件,再判断是否是目标子元素即可
改进:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>事件的委派</title>
<style type="text/css">
li { background-color:#bfa; }
</style>
</head>
<body>
<ul>
<li><a href="javascript:;">链接A</a></li>
<li><a href="javascript:;" class="url">链接B</a></li>
<li><a href="javascript:;" class="url">链接C</a></li>
</ul>
<script type="text/javascript">
var ul = document.getElementsByTagName('ul')[0];
var target;
ul.onclick = function(event) {
event = event || window.event;
target = event.target;
if ('url' == target.className) {
alert('你触发了【' + target.innerText + '】的点击事件!');
}
};
</script>
</body>
</html>
17、事件的绑定
⑴ 相关函数
元素对象.addEventListener(事件名称[不带on前缀], 回调函数, 是否在捕获阶段触发事件【需要传入Boolean类型的值,一般传入false】); 注意:该方法不支持IE8及以下版本元素对象.attachEvent(事件名称[带on前缀], 回调函数);
注意:该方法仅IE浏览器支持。而且在IE8及以下版本,事件的触发和声明的顺序是相反的。同时回调函数中的this指向的是[object Window],这个可以使用call或apply来解决
Tips:和通过给对象的事件属性绑定函数不同的是,事件的绑定可以针对一个事件响应多次;而给属性版定函数,最终生效的是最后赋值的函数,因为它会覆盖之前的
⑵ 示例
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>事件的绑定</title>
</head>
<body>
<a href="javascript:;">超链接</a>
<script type="text/javascript">
var a = document.getElementsByTagName('a')[0];
a.onclick = function() {
alert('OK!');
};
a.onclick = function() {
alert('被点击');
};
a.addEventListener('click', function() {
alert('1');
}, false);
a.addEventListener('click', function() {
alert('2');
}, false);
// a.attachEvent('onclick', function() {
// alert('A');
// });
// a.attachEvent('onclick', function() {
// alert('B');
// });
</script>
</body>
</html>
⑶ 通用的事件绑定函数
function bind(element, eventName, fn) {
if (element.addEventListener) {
element.addEventListener(eventName, fn, false);
} else {
element.attachEvent('on' + eventName, function() {
fn.call(element); // 解决this指向[object Window]的问题
});
}
}
18、事件的传播
⑴ 概念
关于事件的传播,微软公司和网景公司有着不一样的理解:① 微软认为事件是由内向外传播,即先触发子元素的事件,再触发父元素的事件(事件应该在冒泡阶段被触发)
② 网景认为事件是由外向内传播,即先触发父元素的事件,再向内传播给后代元素
W3C综合了两家公司的方案,将事件的传播分为了3个阶段:
① 捕获阶段
从最外层的祖先元素,向内层的后代元素进行事件的触发和传播,但是默认此时不触发事件
② 目标阶段
在目标元素上触发相应的事件
③ 冒泡阶段
事件从子元素向父元素进行传播,并依次触发
可以通过将addEventListener的第3个参数设置为true,这样在捕获阶段就会触发事件
⑵ 示例
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>事件的传播</title>
<style type="text/css">
span {
width: 100px;
height: 100px;
background-color: yellow;
float: left;
}
div {
width: 200px;
height: 200px;
background-color: red;
}
</style>
</head>
<body>
<div>
<span></span>
</div>
<script type="text/javascript">
var span = document.querySelector('span');
var div = document.querySelector('div');
span.addEventListener('click', function() {
alert('你点击了span!');
}, true);
div.addEventListener('click', function() {
alert('你点击了div!');
}, true);
document.documentElement.addEventListener('click', function() {
alert('你点击了document!');
}, true);
</script>
</body>
</html>
可以看到点击事件从document传播给div,再传播到span
19、元素的拖拽
⑴ 注意事项
① 给待移动元素绑定鼠标按下事件,给document绑定鼠标移动事件和鼠标松开事件
要在待移动元素的鼠标按下事件中,给document绑定鼠标移动和鼠标松开事件
给document绑定鼠标移动事件,是因为鼠标是在整个文档活动的;而给document绑定鼠标抬起事件,是因为鼠标抬起时,是从document抬起,并且如果待移动元素移动到了兄弟元素的下面(文档层次结构),则鼠标松开时,触发的是兄弟元素的鼠标松开事件,而不是待移动元素的鼠标松开事件,会导致元素依然跟随鼠标移动
② 要在document的鼠标松开事件中,将document的鼠标移动和鼠标松开事件置为无效(置空)
③ 关于鼠标在待移动元素的内部的偏移位置的问题
④ 取消鼠标按下的默认行为。因为浏览器大都带有拖拽搜索功能,一旦选中的内容中包含文字,则再移动鼠标到地址栏,则会触发搜索功能
⑵ 示例
function drag(element) {
element.onmousedown = function(e) {
e = e || window.event;
element.setCapture && element.setCapture(); // 适配IE8
// 计算鼠标点击时的偏移距离
var ocl = e.clientX - element.offsetLeft;
var oct = e.clientY - element.offsetTop;
var doc = document.documentElement;
doc.onmousemove = function(event) {
event = event || window.event;
element.style.left = event.clientX - ocl + 'px';
element.style.top = event.clientY - oct +'px';
};
doc.onmouseup = function() {
doc.onmousemove = null;
doc.onmouseup = null;
element.releaseCapture && element.releaseCapture(); // 适配IE8
};
return false; // 取消默认行为
};
}
20、鼠标滚轮事件
DOM对象的onmousewheel函数,当鼠标的滚轮滚动时,会触发此函数。通过event事件对象的wheelDelta属性来判断滚轮是向上滚动还是向下滚动。如果是向上滚动,则该值是正数;如果是向下滚动,则该值是负数注意:Firefox浏览器不支持该事件属性,需要通过事件绑定的形式,将DOMMouseScroll绑定给DOM对象。同时还需要注意获取滚动的滚动方向,是通过event对象的detail属性来判断的,如果是向上滚动,则该值是负数;如果是向下滚动,则该值是正数
Tips:浏览器的兼容性问题,可以通过将滚轮滚动函数,当参数直接传递给addEventListener来解决
示例:
function wheel(element) {
element.onmousewheel = function(event) {
event = event || window.event;
if (0 < event.wheelDelta || 0 > event.detail) {
console.log('向上');
} else {
console.log('向下');
}
event.preventDefault && event.preventDefault(); // 取消事件的默认行为
return false;
};
element.addEventListener && element.addEventListener('DOMMouseScroll', element.onmousewheel);
}
21、键盘事件
onkeydown 键盘按键被按下onkeyup 键盘按键松开
通过event事件对象的keyCode属性,来获取某个按键所代表的数值
功能键有自己的属性,比如:ctrlKey、shiftKey和altKey。如果被按下,则返回1;否则返回0
Tips:
① 键盘事件一般都绑定给可以获取焦点的对象或者document
② 只要按键不松开,则键盘按键被按下的事件会一直被触发
示例:
document.onkeydown = function(event) {
event = event || window.event;
// console.log(event.keyCode);
if (65 === event.keyCode) { // 是否按下了'A'键
alert('你按下了“A”键');
}
if (event.ctrlKey && (13 === event.keyCode)) {
alert('control + enter');
}
if (event.shiftKey && (68 === event.keyCode)) {
alert('shift + d');
}
if (event.altKey && (40 === event.keyCode)) {
alert('alt + ↓');
}
};