笔记来源:拉勾教育 - 大前端就业集训营
文章内容:学习过程中的笔记、感悟、和经验
DOM节点操作
体验节点
我们可以使用dom内置方法,创建、删除、获取、和输出节点
<body>
<div id="box">111</div>
<div id="mod">
<p>222</p>
<p>333</p>
<p>444<span>我在里面</span></p>
</div>
<script>
//使用createElement新建一个div节点
var newNode = document.createElement('div');
//修改节点内容
newNode.innerHTML = '我是js创建的';
console.log(newNode);
//把节点插入body底部
document.body.appendChild(newNode);
//获取box节点
var box = my$('box');
//删除他
document.body.removeChild(box);
//获取mod节点
var mod = my$('mod');
//删除mod下面的所有子节点(只输出子节点)
console.log(mod.children);
</script>
</body>
节点属性
- 节点类型(只读):使用
nodeType方法获取某个节点的类型,属性值为数字,一共12种,其中三种比较重要- 1:元素节点
- 2:属性节点
- 3:文本节点
- 节点名称(只读):使用
nodeName获取节点的名称(标签名称) - 节点值:使用
nodeValue方法返回当前的节点值,元素节点的值为unll
<body>
<div class="box">我是文本</div>
<script>
// 获取元素
var box = document.getElementsByClassName('box')[0];
// 查看元素三个属性
console.dir(box); //nodeName: "DIV" nodeType: 1 nodeValue: null
// 获得属性节点getAttributeNode()
var idNode = box.getAttributeNode('class');
// 查看三个属性
console.dir(idNode); //nodeName: "class" nodeType: 2 nodeValue: "box"
// 我们可以在这里修改节点值
idNode.nodeValue = 'ddd';
// 获取文本节点
var chideNode = box.childNodes[0]; //获取所有子节点的第一个节点
console.log(chideNode); //输出一下
//修改这个文本节点的值
chideNode.nodeValue = '我修改了';
</script>
</body>
父子关系节点
- childNodes,只读属性,获取当前节点的所有子节点的实时集合,集合动态变化
- (常用)children,只读属性,返回当前节点所有的子元素节点,是一个动态的html元素集合
- firstChild,只读属性,返回该节点的第一个子节点,没有则返回unll
- lastChild,只读属性,返回该节点的最后一个子节点,没有则返回unll
- (常用)parentNode,返回当前节点的父节点,如果没有,返回null(比如树结构顶端或者该元素根本就没有插入到树中)
- parentelement,返回当前节点的父元素节点,如果没有或者福元素不是DOM元素,返回null
<body>
<div id="box">
<p>p元素</p>
<span>span元素</span>
<div>
123
<span id="span">我有父亲有祖先</span>
</div>
</div>
<script>
//获取元素
var box = document.getElementById('box');
//获取子节点
//获取元素的全部子节点
console.log(box.childNodes); //获取到5个,包含了两个元素之间的空白区域(换行和缩进)
//获取全部的元素子节点
console.log(box.children); //获取到2个---------------------常用
//获取第一个节点(可以不是元素节点)
console.log(box.firstChild);
//获取最后一个子节点(可以不是元素节点)
console.log(box.lastChild);
//获取第一个元素子节点
console.log(box.firstElementChild);
//获取最后一个元素子节点
console.log(box.lastElementChild);
//获取父节点,西面两个都可以获取父元素节点
var span = document.getElementById('span');
console.log(span.parentNode); // ------------------------常用
console.log(span.parentElement);
</script>
</body>
案例:隔行变色(重写)
<body>
<table border="1" style="border-collapse: collapse;">
<tbody id="tb">
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
<script>
//获取所有自元素节点,使用children
var trs = document.getElementById('tb').children;
//遍历设置属性
for (let i = 0; i < trs.length; i++) {
if (i % 2 === 1) {
trs[i].style.backgroundColor = 'pink';
}
}
</script>
</body>
兄弟节点
nextSibling:只读属性,获取当前节点的下一个节点,没有返回null
previouseSibling:只读属性,获取当前节点的上一个节点,没有返回null
nextElementSibling:只读属性,获取当前节点的下一个元素节点,没有返回null
previouseElementSibling:只读属性,获取当前节点的上一个元素节点,没有返回null
nextElementSibling和previouseElementSibling在IE9以上才支持
<body>
<div>
<p>我是第一个</p>
<p id="tow">我是第二个</p>
<p>我是第三个</p>
</div>
<script>
var tow = document.getElementById('tow');
//获取下一个兄弟节点
console.log(tow.nextSibling);
//获取上一个兄弟节点
console.log(tow.nextElementSibling);
//获取下一个元素兄弟节点
console.log(tow.previousSibling);
// 获取上一个元素兄弟节点
console.log(tow.previousElementSibling);
//获取元素节点比较常用
</script>
</body>
创建节点
document.createElement(''):创建元素节点document.createAttribute(''):创建属性节点document.cerateTextNode(''):创建文本节点
一般我们把节点存在变量中,方便调用
//创建新的元素节点div
var newdic = document.createElement('div');
newdic.innerHTML = '新的元素节点';
//新的属性节点id
var newatt = document.createAttribute('id');
newatt.nodeValue = 'aaa';
//新的文本节点
var newtext = document.createTextNode('我是新的文本节点');
添加节点
父节点.appendChild():将某个节点添加到父节点底部,不可添加属性节点
<body>
<div id="box">
<p>我是第一个</p>
<p id="p2">我是第二个</p>
<p>我是第三个</p>
<p>我是第四个</p>
</div>
<script>
//创建新的节点
// 注意:我们新创建的元素节点也属于一个对象名,可以设置其属性、方法、事件,当日后插入到DOM树中后会保留这些属性、方法、事件
var newdic = document.createElement('div');
newdic.innerHTML = '新的元素节点';
var newatt = document.createAttribute('id');
newatt.nodeValue = 'aaa';
var newtext = document.createTextNode('我是新的文本节点');
//获取父节点
var box = document.getElementById('box');
//把创建的元素和文本节点插入到最后
box.appendChild(newdic);
box.appendChild(newtext);
//我们不止可以操作新建的节点,也可以操作原有的节点
var p2 = document.getElementById('p2'); //获取p2
box.appendChild(p2); //把p2插入到最后,从原始位置移除,添加到新的指定位置
</script>
</body>
替换、删除、插入节点
parentNode.replaceChild(新节点,老节点)用新节点替换当前的老节点,并返回被替换的节点
parentNode.insertBefore(新节点,参考节点):在参考节点之前插入一个有指定父节点的子节点,参考节点必须设置,如果想插在尾部可以写null
parentNode.removeChild(子节点):移除父节点内部的某个子节点,这个子节点必须在父节点内部才可以
<body>
<div id="box">
<p>我是第一个</p>
<p id="p2">我是第二个</p>
<p>我是第三个</p>
<p>我是第四个</p>
</div>
<script>
//创建新的节点
// 注意:我们新创建的元素节点也属于一个对象名,可以设置其属性、方法、事件,当日后插入到DOM树中后会保留这些属性、方法、事件
var newdic = document.createElement('div');
newdic.innerHTML = '新的元素节点';
//获取父节点
var box = document.getElementById('box');
//获取需要替换的节点
var p2 = document.getElementById('p2');
//执行替换
// box.replaceChild(newdic, p2); //用newdic替换p2
//插入节点
// box.insertBefore(newdic, p2); //把newdic插入到p2前面
// box.insertBefore(newdic, null); //把newdic插入到结尾
//移除节点
box.removeChild(p2); //删除p2节点
box.removeChild(ppp); //如果不存在,会报错
</script>
</body>
克隆节点
克隆本体.colonNode(布尔值):克隆节点,根据参数决定是否克隆所有内容,默认true
如果参数为true:节点的所有后代都会被克隆
如果参数为flase:则只克隆节点本身
注意:克隆时,标签上的属性和事件都会保留,但是使用js绑定的事件不会被保留
<body>
<div id="box">11111
<p>我是第一个</p>
<p id="p2">我是第二个</p>
<p>我是第三个</p>
<p>我是第四个</p>
</div>
<script>
//创建新的节点
// 注意:我们新创建的元素节点也属于一个对象名,可以设置其属性、方法、事件,当日后插入到DOM树中后会保留这些属性、方法、事件
var newdic = document.createElement('div');
newdic.innerHTML = '新的元素节点';
// 获取父节点
var box = document.getElementById('box');
//获取需要替换的节点
var p2 = document.getElementById('p2');
//克隆节点
var kelong = box.cloneNode(false); //浅克隆:只克隆节点,不克隆内容
var kelong2 = box.cloneNode(true); //深克隆:全全部隆
console.log(kelong, kelong2);
</script>
</body>
判断节点
父节点.hasChildNodes():判断父节点里面是否包含任何子节点,返回布尔值
父节点.contains(子节点):判断子节点是否是父节点的后代节点(祖先元素也可以),返回布尔值
<body>
<div id="box">
<p>我是第一个</p>
<p id="p2">我是第二个</p>
<p>我是第三个</p>
<p>我是第四个</p>
</div>
<div id="box2"></div>
<script>
//获取元素
var box = document.getElementById('box'),
box2 = box.nextElementSibling,
p2 = document.getElementById('p2');
//判断box是否有子节点
console.log(box.hasChildNodes()); //true
//判断box2是否有子节点
console.log(box2.hasChildNodes()); // flase
//判断p2属不属于box
console.log(box.contains(p2)); //true
//判断p2属不属于box2
console.log(box2.contains(p2)); //flase
</script>
</body>
判断方法总结
三种方法可以判断一个节点是否有子节点
- 判断节点的第一个子节点是否是null:
node.firstChild != null - 判断子节点长度是否大于0:
node.childNodes.length > 0 - 直接判断是否有子节点:
node.hasChildNodes()
<body>
<div id="box">
<p>我是第一个</p>
<p id="p2">我是第二个</p>
<p>我是第三个</p>
<p>我是第四个</p>
</div>
<div id="box2"></div>
<script>
//获取元素
var box = document.getElementById('box'),
box2 = box.nextElementSibling;
//实验一下三种判断是否有子节点的方法
//方法1:判断第一个子节点是否存在
console.log(box.firstElementChild != null); //true
console.log(box2.firstElementChild != null); //flase
//方法2:判断子节点个数是否大于0
console.log(box.childNodes.length > 0); //true
console.log(box2.childNodes.length > 0); //flase
//直接调用hasChildNode()
console.log(box.hasChildNodes()); //true
console.log(box2.hasChildNodes()); //flase
//补充:判断是否有子元素
//判断第一个子元素节点是否存在
console.log(box.firstElementChild != null); //true
console.log(box2.firstElementChild != null); //flase
//判断元素内部子元素节点的数量是否大于0
console.log(box.children.length > 0) //true
console.log(box2.children.length > 0) //flase
</script>
</body>
案例:动态创建列表
<body>
<div id="box"></div>
<script>
//获取元素
var box = document.getElementById('box');
//创建名字数组用于遍历
var names = ['刘备', '关羽', '张飞', '赵云'];
//常见一个元素节点
var newul = document.createElement('ul');
//循环遍历数组,并生成新的节点插入到元素节点中
for (let i = 0; i < names.length; i++) {
//创建新节点
let el = document.createElement('li');
//修改节点文本
el.innerText = names[i];
//添加到ul中
newul.appendChild(el);
}
//将制作好的ul整体插入到页面中
box.appendChild(newul);
</script>
</body>
案例:动态生成表格
需求:根据数据动态生成表格,并且添加事件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
table {
border-collapse: collapse;
}
thead {
background-color: #ccc;
}
th,
td {
width: 100px;
height: 20px;
text-align: center;
}
</style>
<script>
// 功能:获取指定id名的元素
function $id(x) {
return document.getElementById(x);
}
</script>
</head>
<body>
<h1>动态创建表格</h1>
<table id="wrap" border="1">
<thead>
<tr>
<th>姓名</th>
<th>科目</th>
<th>成绩</th>
<th>操作</th>
</tr>
</thead>
<tbody id="tb">
</tbody>
</table>
<script>
// 数据数组
var data = [{
name: 'ls',
kemu: '数学',
num: 92
}, {
name: 'ww',
kemu: '英语',
num: 98
}, {
name: 'lb',
kemu: '生物',
num: 100
}, {
name: 'sq',
kemu: '化学',
num: 76
}, {
name: 'cc',
kemu: '物理',
num: 98
}];
// 获取tbody
var tb = $id('tb');
//获取全部的删除单元格
var del = document.getElementsByClassName('del');
// 循环遍历数组,添加tr行
for (let i = 0; i < data.length; i++) {
// 创建新标签tr
var tr = document.createElement('tr');
// 数据内部以对象形式存储数据,遍历对象
for (let j in data[i]) {
//对象每一对数据都单独创建一个td标签
var td = document.createElement('td');
// td标签添加内容
td.innerText = data[i][j];
//将创建的td添加到tr中
tr.appendChild(td);
}
// 最后添加最后一行的删除单元格,并给定类名,这里采用直接新增innerHTML的方式进行添加,也可以使用createElement新建标签方式
tr.innerHTML += '<td class="del"><a href="#">删除</a></td>';
// 将tr添加到tb中
tb.appendChild(tr);
}
//循环遍历数组给每个删除单元格添加click事件
for (let i = 0; i < del.length; i++) {
// 添加事件
del[i].onclick = function () {
// 获取父元素
var fater = this.parentNode;
// 删除父元素
tb.removeChild(fater);
}
}
// 这里使用遍历循环添加事件,如果在之前使用createElement方法新建删除标签,可以在标签内部直接添加onclick
</script>
</body>
</html>
案例:选择水果
需求:点选或者多选水果,实现全部或者批量移动到左侧或者右侧
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
select {
width: 200px;
height: 200px;
background-color: #33cccc;
font-size: 20px;
}
</style>
<script>
// 功能:获取指定id名的元素
function $id(x) {
return document.getElementById(x);
}
</script>
</head>
<body>
<select id="all" size="5" multiple="multiple">
<option>苹果</option>
<option>橘子</option>
<option>梨</option>
<option>西瓜</option>
<option>水蜜桃</option>
</select>
<input type="button" value=">>" id="btn1">
<input type="button" value="<<" id="btn2">
<input type="button" value=">" id="btn3">
<input type="button" value="<" id="btn4">
<select id="choose" multiple="multiple">
</select>
<script>
// 获取元素
var all = $id('all'),
choose = $id('choose'),
//4个按钮
btn1 = $id('btn1'),
btn2 = $id('btn2'),
btn3 = $id('btn3'),
btn4 = $id('btn4'),
allopts = all.children;
//第一个按钮点击事件
btn1.onclick = function () {
//获取左侧列表的所有子元素,注意,这里的opts数组是动态变化的,后面移动元素会导致这数组发生变化
var opts = all.children;
//循环遍历将子元素移动到右侧列表,因为opts长度是动态变化的,所以我们使用固定值size当作判断条件
for (let i = 0; i < all.size; i++) {
//把元素移动到右侧,appendChild属于移动元素方法
choose.appendChild(opts[0]);
}
//更改右侧列表的size属性,加上移动过来的个数
choose.size += all.size;
//重置左侧列表size属性为0
all.size = 0;
}
//第二个按钮点击事件,原理同上
btn2.onclick = function () {
var opts = choose.children;
for (let i = 0; i < choose.size; i++) {
all.appendChild(opts[0]);
}
all.size += choose.size;
choose.size = 0;
}
// 第三个按钮点击事件
btn3.onclick = function () {
// 获取左侧列表所有子元素,注意这里和上面一样,数组都是动态变化的
var childs = all.children;
//循环遍历每个子元素,一旦发现被选中移动到右边,因为数组是动态变化的,所以我们这里从后往前遍历,可以避免受到数组长度的影响(这里也可以先新建一个数组,把所有选中的元素显添加到数组中在移动的方式)
for (let i = all.size - 1; i >= 0; i--) {
//判断元素是否被选中,如果被选中,立即移动到左侧
if (childs[i].selected) {
//移动过去之后不需要选中了,所以先取消选中状态
childs[i].selected = false;
//移动
choose.appendChild(childs[i]);
//修改移动后两侧的size值
all.size--;
choose.size++;
}
}
}
// 第四个按钮的点击事件,原理同上
btn4.onclick = function () {
var childs = choose.children;
for (let i = choose.size - 1; i >= 0; i--) {
if (childs[i].selected) {
childs[i].selected = false;
all.appendChild(childs[i]);
choose.size--;
all.size++;
}
}
}
</script>
</body>
</html>
addEventListener绑定事件
on+事件名的方法无法多次绑定,多次绑定会被覆盖,addEventListener绑定可以多次添加事件,都可以执行
注册事件的其他方法1
元素.addEventListener(事件类型,事件函数)
注意:
- 第一个参数为字符串类型的事件类型,例如:
'ciick' - 同一个元素可以绑定多个事件,同一个事件类型可以注册多个事件函数
- IE9以下浏览器不支持
<body>
<button id="btn">点我</button>
<script>
//获取元素
var btn = $id('btn');
//注册第一个click事件
btn.addEventListener('click', function () {
alert('1');
})
//注册第二个click事件,这两个事件都可以执行
btn.addEventListener('click', function () {
alert('2');
})
// 注册第三个事件,事件函数使用外部函数,注意,函数不要加小括号,否则会立即执行
btn.addEventListener('click', btnClick);
//在外部定义一个函数,可以在注册函数的时候调用
function btnClick() {
alert('3')
}
</script>
</body>
注册事件的其他方法2
元素.attachEvent('on事件类型',事件函数)
注意事项
- 第一个参数为字符串类型的,需要加on
- 和上面一样,同一个元素可以绑定多事件,同一类型事件可以注册多个事件函
- 只支持IE10及以下浏览器(IE8及以下可能会出现事件顺序错乱的问题)
<body>
<button id="btn">点我</button>
<script>
//获取元素
var btn = $id('btn');
// 注册三个事件,但是由于attachEvent只支持IE10及以下浏览器,所以新浏览器可能不支持
btn.attachEvent('onclick', function () {
alert('1')
})
btn.attachEvent('onclick', function () {
alert('2')
})
// 事件函数也可以调用外部函数
btn.attachEvent('onclick', clixkevent)
function clixkevent() {
alert(3);
}
</script>
</body>
注册事件兼容写法
方案:自定义一个注册事件函数
注意事项:
- 参数:(事件源,事件类型(不加on),事件函数){}
- 方法:判断浏览器版本,IE9及以上使用addEventListener方法,以下使用attachEvent方法
- 判断方法:不直接判断版本,我们检测浏览器的能力(将某个方法的调用作为判断条件,浏览器认识该方法返回true,不认识返回flase)
<body>
<button id="btn">点我</button>
<script>
//获取元素
var btn = $id('btn');
// 给元素绑定事件,通过调用注册事件函数
addEvent(btn, 'click', ret);
//一个兼容所有浏览器的事件绑定函数
//参数:元素,事件类型字符串,事件函数
function addEvent(ys, lx, fn) {
//判断浏览器是否认识某个方法
//判断浏览器是否认识addEventListener方法,如果认识,使用addEventListener注册事件
if (ys.addEventListener) {
//注册事件
btn.addEventListener(lx, fn);
} else if (ys.attachEvent) { //如果浏览器不认识addEventListener,那么判断是否认识attachEvent
//如果认识,使用attachEvent注册事件
btn.attachEvent('on' + lx, fn);
}
}
//新建一个事件函数
function ret() {
alert('事件');
}
</script>
</body>
事件解绑
on开头的事件解除
直接使用on+事件类型 = null即可
移除其他事件方法1
使用元素.removeEventListener('事件类型',事件函数名)
注意事项
- 第一个参数和注册时候一样,使用字符串事件类型,不需要加on
- 第二个参数是事件函数的引用名(我们没有办法移除一个匿名函数,所以我们只能在注册事件的时候给一个有函数名的事件函数)
- 移除事件需要写在书写注册事件之后
- 不兼容IE9以下浏览器
<body>
<button id="btn">点我</button>
<script>
//获取元素
var btn = $id('btn');
// 注册事件
btn.addEventListener('click', clickbtn);
//移除事件
btn.removeEventListener('click', clickbtn);
//事件函数
function clickbtn() {
alert(1);
}
</script>
</body>
移除其他事件的方法2
使用元素.detachEvent('事件类型',事件函数名)
注意事项
- 第一个参数字符串类型的事件类型,需要加on
- 第二个参数同上个方法一样,只能移除有名字的事件函数
- 只兼容IE10及以下浏览器
<body>
<button id="btn">点我</button>
<script>
//获取元素
var btn = $id('btn');
// 注册事件
btn.attachEvent('click', clickbtn);
//移除事件
btn.detachEvent('click', clickbtn);
//事件函数
function clickbtn() {
alert(1);
}
</script>
</body>
解除事件兼容写法
和绑定事件一样,我们可以自己自定义一个解除事件函数
注意事项
参数:(事件源,事件类型(不加on),事件函数)
方法:判断浏览器能力(IE9及以上判断removeEventListener方法,以下判断detachEvent方法)
// 自定义解绑事件函数
//参数:事件源,事件类型,事件函数
function removeClick(ele, lx, fn) {
//判断浏览器能力,来判断浏览器版本
// 判断是否认识removeEventListener方法
if (btn.removeEventListener) {
btn.removeEventListener(lx, fn);
} else if (btn.detachEvent) {
//如果不认识removeEventListener那么判断是否认识detachEvent方法
btn.detachEvent('on' + lx, fn);
}
}
建议:建议把我们自己封装的一些函数放在单独的js文件内
DOM事件流
小案例:体验事件流向
<body>
<!-- 三个依次包含的div -->
<div id="box1">
<div id="box2">
<div id="box3"></div>
</div>
</div>
<script>
//获取元素
var box1 = document.getElementById('box1'),
box2 = document.getElementById('box2'),
box3 = document.getElementById('box3');
//给三个元素添加事件
box1.addEventListener('click', function () {
console.log(this.id);
})
box2.addEventListener('click', function () {
console.log(this.id);
})
box3.addEventListener('click', function () {
console.log(this.id);
})
</script>
</body>
我们点击box3可以发现,控制台依次输出了box3、box2、box1,可以发现,事件的流向是从小到大的(box3➡️box2➡️box1)我们管这种方式叫做事件冒泡
事件冒泡和时间捕获过程
- 事件冒泡:事件触发从子元素向上(父元素)执行的过程
- 事件捕获:和事件冒泡刚好相反,事件从外向内依次执行
控制事件流方法:addEventListener方法有第三个参数,默认为flase(事件冒泡),如果设置为true则流向为事件捕获
<body>
<!-- 三个依次包含的div -->
<div id="box1">
<div id="box2">
<div id="box3"></div>
</div>
</div>
<script>
//获取元素
var box1 = document.getElementById('box1'),
box2 = document.getElementById('box2'),
box3 = document.getElementById('box3');
//给三个元素添加事件,默认事件冒泡,默认第三个参数为flase
box1.addEventListener('click', function () {
console.log(this.id);
})
box2.addEventListener('click', function () {
console.log(this.id);
})
box3.addEventListener('click', function () {
console.log(this.id);
})
//三个元素再添加事件捕获流程,添加第三个参数,把事件流改为时间捕获
box1.addEventListener('click', function () {
console.log(1);
}, true)
box2.addEventListener('click', function () {
console.log(2);
}, true)
box3.addEventListener('click', function () {
console.log(3);
}, true)
//当我们点击box3可以发现,控制台打印的效果依次是1、2、box3、3、box2、box1,可以发现整体上来看捕获比冒泡先执行了,但是box3的两个事件却是冒泡先执行,这是因为对于box3自身来说,同时存在冒泡和捕获哪个在前哪个先执行
</script>
</body>
如果两者同时存在:先执行事件捕获,再执行事件冒泡
事件流的三个阶段
在整个的事件流过程中,我们触发事件可能经历下面的三个阶段
- 第一阶段:事件捕获
- 第二阶段:事件执行
- 第三阶段:事件冒泡
addEventListener方法第三个参数作用:(事件一定会执行,但是执行的顺序可以有不同)
- 默认为flase:会按照冒泡的过程执行事件(从向外执行)
- 设置值为true:会按照事件捕获的过程执行(从外向内执行)
- 如果两者都设置了,优先执行事件捕获过程
注意:on+事件类型和attachEvent()方法只能进行事件冒泡过程,没有捕获阶段
事件委托
利用事件冒泡的特性,将子元素的公共事件委托给父元素加载,同时利用事件函数的一个e参数,内部存储事件对象
<body>
<ul id="ul">
<li>1111</li>
<li>1111</li>
<li>1111</li>
<li>1111</li>
<li>1111</li>
<li>1111</li>
</ul>
<script>
//需求:点击li标签高亮背景色
//获取外层ul
var ul = document.getElementById('ul');
//获取内部li标签
var lis = ul.children;
//事件委托:把子元素公共的事件委托给父元素,在父元素中借用参数e获取触发事件的子元素
ul.onclick = function (e) {
//我们需要在内部寻找到真正触发事件的子元素
//参数e为事件对象,只要触发事件,函数内部都会产生一个事件对象
//排他思想,清除全部的样式
for (let i = 0; i < lis.length; i++) {
lis[i].style.backgroundColor = '';
}
//e.target就记录的真正触发事件的事件源头
e.target.style.backgroundColor = 'pink';
}
</script>
</body>
事件对象
只要触发事件,就会有一个事件对象,内部存储了与事件相关的数据信息
e在低版本浏览器中有兼容问题,低版本浏览器中需要使用window.event
事件对象常用属性
| 属性 | 说明 |
|---|---|
| e.eventPhase | 插件事件触发所处阶段 |
| e.target | 获取真正触发事件的元素 |
| e.srcElement | 获取真正触发事件的元素(低版本用法) |
| e.currentTarget | 用于获取绑定事件的事件源 |
| e.type | 获取事件类型 |
| e.clientX / e.clientY | 鼠标距离浏览器窗口左上角的距离(坐标) |
| e.pageX / e.pageY | 鼠标距离整个HTML页面坐上顶点距离(IE8-不支持) |
<body>
<!-- 三个依次包含的div -->
<div id="box1">
<div id="box2">
<div id="box3"></div>
</div>
</div>
<script>
//获取元素
var box1 = document.getElementById('box1'),
box2 = document.getElementById('box2'),
box3 = document.getElementById('box3');
box1.onclick = function (e) {
//兼容写法,低版本浏览器使用window.event
var e = e || window.event;
// 查看事件触发阶段
console.log(e.eventPhase);
//兼容写法,低版本浏览器使用e.srcElement获取真正触发事件的元素
var target = e.target || e.srcElement;
//查看真正触发事件的事件元素
console.log(target);
//查看绑定事件的事件元素(返回的是绑定的事件源,不会根据点击的元素不同发生改变),另外this指向绑定的事件源,和上面相等
console.log(e.currentTarget);
console.log(this);
//查看触发的事件类型
console.log(e.type);
//查看鼠标距离浏览器窗口左上角的距离
console.log(e.clientX, e.clientY);
//查看鼠标距离HTML页面左上角的距离
console.log(e.pageX, e.pageY);
}
// 给box1添加事件
box1.onmouseover = fn;
box1.onmouseout = fn;
// 扩展:利用e.type动态添加事件
//事件动态添加函数
//参数e为事件对象
function fn(e) {
//兼容写法
var e = e || window.event;
//条件分支语句,根据e.type判断
switch (e.type) {
//如果是鼠标移入
case 'mouseover':
//执行代码
this.style.backgroundColor = 'skyblue';
break;
//如果是鼠标移出
case 'mouseout':
//执行代码
this.style.backgroundColor = 'pink';
break;
}
}
</script>
</body>
案例:图片随鼠标移动
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
/* 设置图片样式,固定定位 */
img {
position: fixed;
width: 30px;
height: 30px;
}
</style>
<script>
// 功能:获取指定id名的元素
function $id(x) {
return document.getElementById(x);
}
</script>
</head>
<body>
<!-- //图片 -->
<img id="m" src="./images/tianshi.gif" alt="">
<script>
// 获取图片元素
var m = document.getElementById('m');
//直接给document添加鼠标移动事件,这里也可以给window
document.addEventListener('mousemove', function () {
//兼容写法
var e = e || window.event;
//把e.client值给图片的left和top值
m.style.left = e.clientX + 'px';
m.style.top = e.clientY + 'px';
});
</script>
</body>
</html>
取消默认行为和阻止冒泡
取消默认行为
e.preventDefault():取消默认行为e.returnValue = flase:取消默认行为(低版本浏览器使用)
<body>
<!-- //图片 -->
<a id="a" href="./images/tianshi.gif">点我点我</a>
<script>
// 获取元素
var a = $id('a');
// 点击事件
a.onclick = function (e) {
// 兼容写法
e = e || window.event;
//事件执行体
alert(1);
// 取消默认行为(这里取消了a标签的默认跳转行为)
e.preventDefault();
// 低版本浏览器使用
// e.returnvalue = false;
// 当然我们也可以使用之前的return flase方法阻止a标签的跳转
}
</script>
</body>
阻止冒泡
e.stopPropagation():阻止冒泡e.cancelBubble = true:阻止冒泡,IE低版本浏览器使用,标准中已放弃
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
#box1 {
width: 300px;
height: 300px;
background: pink;
}
#box2 {
width: 200px;
height: 200px;
background: skyblue;
}
#box3 {
width: 100px;
height: 100px;
background: #ccc;
}
</style>
<script>
// 功能:获取指定id名的元素
function $id(x) {
return document.getElementById(x);
}
</script>
</head>
<body>
<div id="box1">
<div id="box2">
<div id="box3"></div>
</div>
</div>
<script>
// 获取元素
var box1 = $id('box1'),
box2 = $id('box2'),
box3 = $id('box3');
//添加事件
box3.onclick = function (e) {
console.log(this.id);
//阻止冒泡,点击box3时就不会触发外层元素的事件
e.stopPropagation();
//低版本浏览器使用
// e.cancelBubble = true;
}
box2.onclick = function () {
console.log(this.id);
}
box1.onclick = function () {
console.log(this.id);
}
</script>
</body>
</html>
DOM特效属性
偏移量属性
offsetParent:偏移参考父级--距离自己最近的有定位的父级(类似于css绝对定位),没有则参考body(html)offsetLeft/offsetTop:偏移位置 -- 距离偏移父级元素左上顶点距离(类似于css绝对定位left和top)offsetWidth/offsetHeight:偏移大小 -- 元素大小(除margin以外)
<body>
<div id="box1">
<div id="box3">
</div>
</div>
<script>
// 获取元素
var box3 = $id('box3');
// 偏移父元素(因为外部box1没有设置定位,所以就参考了body),元素天生就认识
console.log(box3.offsetParent);
// 偏移距离,参考点位body左上顶点
console.log(box3.offsetLeft);
console.log(box3.offsetTop);
// 偏移大小,就是元素盒子大小(不算margin)
console.log(box3.offsetWidth);
console.log(box3.offsetHeight);
</script>
</body>
客户端大小相关属性
client没有参考父级元素
clientLeft / clienTop: 左边框、上边框大小(不常用)
clientWidth / clientHeight:边框内部大小(padding + 内容)
<body>
<div id="box1">
<div id="box3">
</div>
</div>
<script>
// 获取元素
var box3 = $id('box3');
//客户端大小有关属性
//获取左边和上边框大小
console.log(box3.clientLeft);
console.log(box3.clientTop);
// 获取元素内部大小(内容区域+padding)
console.log(box3.clientWidth);
console.log(box3.clientHeight);
</script>
</body>
滚动偏移相关属性
scrollLeft / scrollTop :盒子内部滚动出去的尺寸 -- 已滚动的,未滚动为0
scrollWidth / scrollHeight:盒子内部的宽度和高度 -- 实际上就是盒子内部可供滚动的像素值
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
#box1 {
width: 300px;
height: 300px;
padding: 50px;
border: 20px solid green;
background: pink;
overflow: auto;
}
#box3 {
width: 500px;
height: 500px;
background: skyblue;
}
</style>
<script>
// 功能:获取指定id名的元素
function $id(x) {
return document.getElementById(x);
}
</script>
</head>
<body>
<div id="box1">
<div id="box3">
</div>
</div>
<script>
var box1 = $id('box1');
var box3 = $id('box3');
// 获取元素的滚动偏移属性
//滚动偏移量:指的是滚动出去的距离,如果没有滚动两者都是0
console.log(box1.scrollLeft);
console.log(box1.scrollTop);
//元素滚动偏移大小:实际上就是盒子内部可以滚动的距离
console.log(box1.scrollWidth); //550(box1左内边距 + box3盒子宽度)
console.log(box1.scrollHeight); //600(box1上内边距 + 盒子高度)
//这里不同是因为浏览器加载机制的问题,纵向滚动会多加载一个父元素的下内边距
//这两个属性都不会因为溢出而发生影响
console.log(box1.clientWidth); //385(本应该是400,但是滚动条占用了15px)
console.log(box1.offsetWidth); //440
//添加事件查看一下滚动偏移,onscroll事件为滚动事件
box1.onscroll = function () {
//滚动的话输出偏移量
console.log(box1.scrollLeft, box1.scrollTop);
}
</script>
</body>
</html>
案例:拖拽
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
<style>
* {
margin: 0;
padding: 0;
}
.nav {
height: 30px;
background: #036663;
border-bottom: 1px solid #369;
line-height: 30px;
padding-left: 30px;
}
.nav a {
color: #fff;
text-align: center;
font-size: 14px;
text-decoration: none;
}
.d-box {
width: 400px;
height: 300px;
border: 5px solid #eee;
box-shadow: 2px 2px 2px 2px #666;
position: absolute;
top: 40%;
left: 40%;
background-color: white;
/* 不让文字被选中 */
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.hd {
width: 100%;
height: 25px;
background-color: #7c9299;
border-bottom: 1px solid #369;
line-height: 25px;
color: white;
cursor: move;
}
#box_close {
float: right;
cursor: pointer;
}
</style>
</head>
<body>
<div class="nav">
<a href="javascript:;" id="register">注册信息</a>
</div>
<div class="d-box" id="d_box">
<div class="hd" id="drop">注册信息 (可以拖拽)
<span id="box_close">【关闭】</span>
</div>
<div class="bd"></div>
</div>
<script src="common.js"></script>
<script>
// 获取元素
var d_box = document.getElementById('d_box'),
drop = document.getElementById('drop'),
box_close = document.getElementById('box_close');
//点击关闭按钮关闭,添加click事件
box_close.onclick = function () {
//修改css样式
d_box.style.display = 'none';
}
//鼠标按下事件
//思路:
//需要先获取鼠标位置和box左上顶点的坐标距离
//鼠标移动的时候使用鼠标位置减掉这两个距离就可以算出box应该在的实际位置
//再把这个定位赋值给box的css定位即可
drop.onmousedown = function (e) {
//兼容写法
var e = e || window.event,
//获取鼠标位置和box左上顶点的差值
x = e.pageX - d_box.offsetLeft,
y = e.pageY - d_box.offsetTop;
// var me = true;
//嵌套鼠标移动事件
drop.onmousemove = function (e) {
//兼容写法
var e = e || window.event;
// if (me) {
//将鼠标位置减掉与左上顶点的差值赋值给css定位属性
d_box.style.top = e.clientY - y + 'px';
d_box.style.left = e.clientX - x + 'px';
// }
}
// drop.onmouseup = function () {
// me = false;
// }
}
// 当鼠标抬起后,我们清空鼠标移动事件,让box不再跟着鼠标移动
drop.onmouseup = function () {
this.onmousemove = null;
}
// 思路2:(上面注释掉的部分)
//前面同思路1,但是我们可以把onmouseup事件写在onmousedown事件内部,我们可以在onmousedown中创造一个值,当这个值为true是才能执行鼠标移动事件,为flase就让鼠标移动事件不触发,我们只需要在鼠标抬起事件内部把这个值设置为flase即可
</script>
</body>
</html>
案例:点击弹层(遮罩)
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
<style>
.login-header {
width: 100%;
text-align: center;
height: 30px;
font-size: 24px;
line-height: 30px;
}
ul,
li,
ol,
dl,
dt,
dd,
div,
p,
span,
h1,
h2,
h3,
h4,
h5,
h6,
a {
padding: 0px;
margin: 0px;
}
.login {
width: 512px;
position: absolute;
border: #ebebeb solid 1px;
height: 280px;
left: 50%;
right: 50%;
background: #ffffff;
box-shadow: 0px 0px 20px #ddd;
z-index: 9999;
margin-left: -256px;
margin-top: 140px;
display: none;
}
.login-title {
width: 100%;
margin: 10px 0px 0px 0px;
text-align: center;
line-height: 40px;
height: 40px;
font-size: 18px;
position: relative;
cursor: move;
-moz-user-select: none;
/*火狐*/
-webkit-user-select: none;
/*webkit浏览器*/
-ms-user-select: none;
/*IE10*/
-khtml-user-select: none;
/*早期浏览器*/
user-select: none;
}
.login-input-content {
margin-top: 20px;
}
.login-button {
width: 50%;
margin: 30px auto 0px auto;
line-height: 40px;
font-size: 14px;
border: #ebebeb 1px solid;
text-align: center;
}
.login-bg {
width: 100%;
height: 100%;
position: fixed;
top: 0px;
left: 0px;
background: #000000;
filter: alpha(opacity=30);
-moz-opacity: 0.3;
-khtml-opacity: 0.3;
opacity: 0.3;
display: none;
}
a {
text-decoration: none;
color: #000000;
}
.login-button a {
display: block;
}
.login-input input.list-input {
float: left;
line-height: 35px;
height: 35px;
width: 350px;
border: #ebebeb 1px solid;
text-indent: 5px;
}
.login-input {
overflow: hidden;
margin: 0px 0px 20px 0px;
}
.login-input label {
float: left;
width: 90px;
padding-right: 10px;
text-align: right;
line-height: 35px;
height: 35px;
font-size: 14px;
}
.login-title span {
position: absolute;
font-size: 12px;
right: -20px;
top: -30px;
background: #ffffff;
border: #ebebeb solid 1px;
width: 40px;
height: 40px;
border-radius: 20px;
}
</style>
</head>
<body>
<div class="login-header"><a id="link" href="javascript:void(0);">点击,弹出登录框</a></div>
<div id="login" class="login">
<div id="title" class="login-title">登录会员
<span><a id="closeBtn" href="javascript:void(0);" class="close-login">关闭</a></span>
</div>
<div class="login-input-content">
<div class="login-input">
<label>用户名:</label>
<input type="text" placeholder="请输入用户名" name="info[username]" id="username" class="list-input">
</div>
<div class="login-input">
<label>登录密码:</label>
<input type="password" placeholder="请输入登录密码" name="info[password]" id="password" class="list-input">
</div>
</div>
<div id="loginBtn" class="login-button"><a href="javascript:void(0);" id="login-button-submit">登录会员</a></div>
</div>
<!-- 遮盖层 -->
<div id="bg" class="login-bg"></div>
<script>
// 获取元素
var link = document.getElementById('link'),
login = document.getElementById('login'),
closeBtn = document.getElementById('closeBtn'),
bg = document.getElementById('bg');
//a标签点击事件,点击后显示表单和背景遮罩
//修改行内css样式,因为行内会覆盖内嵌样式,所以会显示出来
link.onclick = function () {
login.style.display = 'block';
bg.style.display = 'block';
}
//点击关闭按钮修改行内的display为空,这样css样式覆盖就消失了,就会显示默认内嵌式样式
closeBtn.onclick = function () {
login.style.display = '';
bg.style.display = '';
}
</script>
</body>
</html>