DOM
课程目标
- 理解DOM基础概念及浏览器的工作原理(*)
- 熟练使用document对象的属性和API获取、修改和删除元素(及其内容和属性)(***)
- 理解DOM级别,熟练掌握事件的添加和删除,及常用的事件类型(***)
- 掌握事件对象的使用,理解事件流及DOM事件的传播阶段,掌握浏览器默认行为的阻止方式,能够利用事件委托提高程序可维护性(***)
- 理解数据驱动的概念及其应用方式(**)
- 掌握JS操作CSS样式的方式(**)
网页交互流程
DOM基础概念
DOM(document object model),即文档对象模型。HTML文档是由标签构成的,浏览器解析网页文档时,会将页面的元素整合为document对象,通过这个对象提供的一系列方法可以允许我们操作HTML元素。比如元素的增删改查,CSS样式的修改.
浏览器工作原理(重点)
-
用户输入URL,按下回车
-
域名解析(通过域名获取目标服务器的IP地址)
-
TCP连接(与目标服务器建立连接)
-
浏览器向服务器发送HTTP请求(对应一个网站来说一般就是一个HTML文件)
-
服务器向浏览器返回HTTP响应(HTML文件)
-
浏览器解析HTML文档(重点)
- 处理HTML标签,生成DOM树。浏览器会将HTML文档中的标签整合为document对象,这个对象作为网页内容的入口,也就是DOM树。文档中的每一个元素都是DOM树上的一个节点。
-
处理CSS,构建CSSOM树。与DOM树类似,浏览器遍历CSS规则,创建CSSOM节点树。任何一个元素的最终样式,都是从浏览器的默认样式和通用规则开始,到这个节点的最上层元素,一层一层合并具体规则,得出最终样式(就是级联)。
-
JS脚本的解析、编译和执行
-
浏览器渲染
-
布局:解析生成的DOM树和CSSOM树会组合称为渲染树(Render树),所有拥有内容和计算样式后可见的节点都保存在渲染树上面,渲染树会计算和确定每个节点的大小和位置,这个过程叫做布局(Layout)。
- PS:第一次确定节点的大小和位置称为布局,随后重新计算节点大小位置称为回流
-
绘制:将渲染树上面的各个节点绘制在屏幕上(将元素的可见部分转换为屏幕的实际像素)
-
-
用户和渲染的页面进行交互
获取元素
基础使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>获取元素(基础使用)</title>
</head>
<body>
<p>hello world</p>
<script>
// document属性:获取document对象
console.log(document);
// document.documentElement: 获取html元素
console.log(document.documentElement);
// document.body: 获取body元素
console.log(document.body);
// document.title: 获取页面标题
console.log(document.title);
// document.write(内容): 向当前的HTML文档写入指定内容
document.write('哇牛');
</script>
</body>
</html>
API
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>获取元素(API)</title>
</head>
<body>
<div id="container">
<h2>任务清单</h2>
<ul>
<li>项目名称</li>
<li class="item">起床</li>
<li class="item">洗漱</li>
<li class="item">吃饭</li>
<li class="item">上学</li>
<li class="item important">睡觉</li>
</ul>
</div>
<script>
// document.getElementById(id值): 获取指定id值的元素
let element = document.getElementById('container');
console.log(element);
// document.getElementsByTagName(标签名): 获取页面指定标签的所有元素
let list = document.getElementsByTagName('li');
console.log(list); // 输出所有找到的元素
for (let i=0; i<list.length; i++) {
console.log(list[i]);
}
// list.splice(0, 0, 'hello'); // 类数组对象不能直接调用数组的API
// document.getElementsByClassName(类名): 获取页面包含指定类名的所有元素
list = document.getElementsByClassName('item');
console.log(list);
list = document.getElementsByClassName('item important');
console.log(list);
// CSS选择元素
// 父元素.querySelector(选择器): 返回满足指定选择器条件的第一个元素
let divElement = document.querySelector('#container'); // 找document下面id为container的第一个元素
console.log(divElement);
let lastLiElement = divElement.querySelector('li:last-child'); // 找div元素下面最后一个li的元素(或者.item.important)
console.log(lastLiElement);
// 父元素.querySelectorAll(选择器): 返回满足指定选择器条件的所有元素
let liList = document.querySelectorAll('li'); // 返回document下面所有的li元素(NodeList类型)
// HTMLCollection: 即时更新的(live);当其所包含的文档结构发生改变时,它会自动更新
// NodeList: 一个静态集合,也就意味着随后对文档对象模型的任何改动都不会影响集合的内容
console.log(liList);
liList = document.querySelectorAll('.item'); // 返回document下面所有包含item类名的元素
for (let li of liList) { // 遍历元素列表,并输出
console.log(li);
}
</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>元素内容</title>
</head>
<body>
<div id="container">
<h2>任务清单</h2>
<ul>
<li>项目名称</li>
<li class="item">起床</li>
<li class="item">洗漱</li>
<li class="item">吃饭</li>
<li class="item">上学</li>
<li class="item important">睡觉</li>
</ul>
</div>
<script>
// 元素.innerText 获取元素的文本内容
let elem = document.querySelector('h2');
console.log(elem.innerText); // 任务清单
elem = document.querySelector('ul');
console.log(elem.innerText); // 一般不会这么做,这样会输出ul下面所有子元素的文本内容
// 元素.innerText = '内容' 更新元素的文本内容
elem = document.querySelector('.important');
elem.innerText = '打游戏';
// 元素.innerHTML 获取元素下面的子元素及其内容
elem = document.querySelector('ul');
console.log(elem.innerHTML);
// 元素.innerHTML = '内容';
elem.innerText = '<li>hehe</li>'; // 不解析标签,只当作普通文本
elem.innerHTML = '<li>hehe</li>'; // 可以解析HTML标签
</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>元素属性</title>
</head>
<body>
<img src="test.jpg" alt="test" class="cover">
<input type="text" placeholder="请输入姓名" id="username">
<script>
// 元素.属性 获取元素的属性值
let imgElement = document.querySelector('img');
let inputElement = document.querySelector('#username');
console.log(imgElement.src); // file:///C:/Users/he/Desktop/web16/web16_20230626/test.jpg
console.log(imgElement.alt); // test
console.log(inputElement.placeholder); // 请输入姓名
// 元素.属性 = 值
imgElement.src = 'woniu.png';
imgElement.alt = '蜗牛';
// 元素.getAttribute(属性名) 返回对应的属性值
console.log(imgElement.getAttribute('src')); // woniu.png
console.log(inputElement.getAttribute('type')); // text
// 元素.setAttribute(属性名, 属性值);
// 有则更新,无则添加
inputElement.setAttribute('type', 'password');
imgElement.setAttribute('class', 'avatar');
</script>
</body>
</html>
事件基础
事件:用户跟网页之间产生的交互行为就是事件。我们可以通过JS让某些元素监听特定的事件,当事件发生的时候,就会执行预设的代码
事件三要素:
- 事件源:触发事件的元素(让哪个元素监听)
- 事件类型:在事件源上监听哪种交互(让元素监听什么)
- 事件处理程序:触发事件时的处理函数(事件发生了做什么)
DOM级别
-
DOM0级:早期还未形成标准的试验性的DOM,不同浏览器之间存在操作的兼容性问题
-
DOM1级:定义了HTML文档的底层结构,添加了针对HTML对象的操作方法,即document对象,可以允许我们进行方便的增删改查
-
DOM2级:增加了更多的方法接口:事件监听和处理、CSS样式、遍历操作等
-
DOM3级:统一了加载和保存文档的方法,提供了XML文档的支持
目前使用最广泛的还是DOM0级和DOM2级
事件属性
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>事件属性</title>
<style>
div {
width: 400px;
height: 200px;
background-color: red;
color: white;
font-size: 100px;
text-align: center;
line-height: 200px;
margin: 50px auto;
cursor: pointer;
border-radius: 100px;
}
</style>
</head>
<body>
<div onclick="show()">点我</div>
<script>
/*
通过HTML事件属性设置事件
语法:
- 添加: <元素 onclick="函数名()"></元素>
- 移除: element.removeAttribute(attrName);element.removeAttribute(attrName);
元素.removeAttribute(事件属性)
注意:
- 不推荐使用:将HTML和JS混到一起,维护复杂
*/
function show() {
alert('hello world');
}
let div = document.querySelector('div');
div.removeAttribute('onclick');
</script>
</body>
</html>
DOM0级
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DOM0级</title>
<style>
div {
width: 400px;
height: 200px;
background-color: red;
color: white;
font-size: 100px;
text-align: center;
line-height: 200px;
margin: 50px auto;
cursor: pointer;
border-radius: 100px;
}
</style>
</head>
<body>
<div>点我</div>
<script>
/*
DOM0级
语法:
- 添加: 元素.on事件类型 = function() {}
- 移除: 元素.on事件类型 = null;
注意:
- 解决了事件属性将HTML和JS绑定的问题,提高了可维护性
- 不足:无法给同一个元素的同一事件添加多个事件处理程序(新的会覆盖旧的)
*/
let div = document.querySelector('div');
// click: 鼠标单击事件
div.onclick = function() {
alert('葵花点穴手');
}
div.onclick = () => {
alert('排山倒海');
}
// 移除单击事件
div.onclick = null;
</script>
</body>
</html>
DOM2级
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DOM2级</title>
<style>
div {
width: 400px;
height: 200px;
background-color: red;
color: white;
font-size: 100px;
text-align: center;
line-height: 200px;
margin: 50px auto;
cursor: pointer;
border-radius: 100px;
}
</style>
</head>
<body>
<div>点我</div>
<button>我的事件可以移除</button>
<script>
/*
DOM2级
语法:
- 添加:元素.addEventListener(事件类型, 处理函数)
- 移除:元素.removeEventListener(事件类型, 处理函数)
注意:
- DOM2级语法可以为同一个元素,同一事件类型添加多个事件处理程序
- 移除事件处理程序的函数必须和添加的函数是同一个,否则无法移除
*/
let div = document.querySelector('div');
// 事件类型不要加on
div.addEventListener('click', function() {
alert('我被点了');
});
div.addEventListener('click', function() {
alert('我还能显示吗')
});
div.addEventListener('click', () => {
alert('我是第三个弹窗');
});
// 移除事件处理程序(此处无法移除,因为传递的第二个函数参数是一个新的函数)
div.removeEventListener('click', function() {
alert('我被点了');
});
// 标准移除事件处理函数的方式
function hello() {
alert('hello world');
}
let btn = document.querySelector('button');
// 如果希望移除事件处理程序,需要使用具名函数,参数传递的是函数的名字,而不是函数调用的结果
btn.addEventListener('click', hello);
// 移除事件
btn.removeEventListener('click', hello);
</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>诗词展示</title>
</head>
<body>
<div class="container">
</div>
<script>
let data = {
title: '静夜思-李白',
sentence: [
'床前明月光',
'疑是地上霜',
'举头望明月',
'低头思故乡'
]
};
// 死数据
let str = '';
str += '<h2>静夜思</h2>';
str += '<p>床前明月光</p>';
str += '<p>疑是地上霜</p>';
str += '<p>举头望明月</p>';
str += '<p>低头思故乡</p>';
let div = document.querySelector('div');
div.innerHTML = str;
// 对象数据
let str = '';
str += `<h2>${data.title}</h2>`;
for (let item of data.sentence) {
str += `<p>${item}</p>`;
}
console.log(str);
let div = document.querySelector('div');
div.innerHTML = str;
</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>多首诗词</title>
</head>
<body>
<div class="container"></div>
<script>
let data = [
{
title: '无题·昨夜星辰昨夜风',
sentence: [
'昨夜星辰昨夜风',
'画楼西畔桂堂东',
'身无彩凤双飞翼',
'心有灵犀一点通',
'隔座送钩春酒暖',
'分曹射覆蜡灯红',
'嗟余听鼓应官去',
'走马兰台类转蓬'
]
},
{
title: '夜雨寄北',
sentence: [
'君问归期未有期',
'巴山夜雨涨秋池',
'何当共剪西窗烛',
'却话巴山夜雨时'
]
},
{
title: '琵琶行',
sentence: [
'我闻琵琶已叹息',
'又闻此语重唧唧',
'同是天涯沦落人',
'相逢何必曾相识'
]
},
{
title: '别董大',
sentence: [
'千里黄云白日曛',
'北风吹雁雪纷纷',
'莫愁前路无知己',
'天下谁人不识君',
'六翮飘飖私自怜',
'一离京洛十馀年',
'丈夫贫践应未足',
'今日相逢无酒钱'
]
},
{
title: '渡汉江',
sentence: [
'岭外音书断',
'经冬复历春',
'近乡情更怯',
'不敢问来人'
]
}
];
let str = '';
for (let item of data) {
// 通过每首诗的数据构建字符串,拼接到str上
str += `<h2>${item.title}</h2>`;
for (let i of item.sentence) {
// 将每一句诗放在p元素中,拼接到str上
str += `<p>${i}</p>`;
}
}
console.log(str);
let container = document.querySelector('.container');
container.innerHTML = str;
</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>诗词切换</title>
<style>
* {
padding: 0;
margin: 0;
}
.container {
width: 500px;
margin: 50px auto;
text-align: center;
}
.content {
background-color: antiquewhite;
color: black;
text-align: center;
padding: 10px;
}
p {
margin-top: 10px;
}
button {
margin-top: 20px;
font-size: 25px;
padding: 5px;
}
</style>
</head>
<body>
<div class="container">
<div class="content"></div>
<button>切换下一首</button>
</div>
<script>
let data = [
{
title: '无题·昨夜星辰昨夜风',
sentence: [
'昨夜星辰昨夜风',
'画楼西畔桂堂东',
'身无彩凤双飞翼',
'心有灵犀一点通',
'隔座送钩春酒暖',
'分曹射覆蜡灯红',
'嗟余听鼓应官去',
'走马兰台类转蓬'
]
},
{
title: '夜雨寄北',
sentence: [
'君问归期未有期',
'巴山夜雨涨秋池',
'何当共剪西窗烛',
'却话巴山夜雨时'
]
},
{
title: '琵琶行',
sentence: [
'我闻琵琶已叹息',
'又闻此语重唧唧',
'同是天涯沦落人',
'相逢何必曾相识'
]
},
{
title: '别董大',
sentence: [
'千里黄云白日曛',
'北风吹雁雪纷纷',
'莫愁前路无知己',
'天下谁人不识君',
'六翮飘飖私自怜',
'一离京洛十馀年',
'丈夫贫践应未足',
'今日相逢无酒钱'
]
},
{
title: '渡汉江',
sentence: [
'岭外音书断',
'经冬复历春',
'近乡情更怯',
'不敢问来人'
]
}
];
/*
- 进入页面默认显示第一首诗
- 点击下一首按钮,显示下一首诗
- 如果是最后一首,弹窗提示“今天的诗词已经学完了”
*/
let index = 0;
function render() {
let item = data[index];
let str = '';
str += `<h2>${item.title}</h2>`;
for (let i of item.sentence) {
str += `<p>${i}</p>`;
}
let content = document.querySelector('.content');
content.innerHTML = str;
}
render();
let btn = document.querySelector('button');
btn.addEventListener('click', () => {
index++;
if (index >= data.length) {
alert('今天的诗词已经学完了');
return;
}
render();
// if (index < data.length) {
// render();
// } else {
// alert('今天的诗词已经学完了');
// return;
// }
})
</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>商品渲染</title>
<style>
* {
padding: 0;
margin: 0;
list-style: none;
}
.container {
width: 600px;
margin: 50px auto;
text-align: center;
}
p {
margin: 20px 0;
color: gold;
}
div>img {
border: 1px solid black;
height: 400px;
}
ul {
display: flex;
justify-content: center;
}
ul>li>img {
width: 80px;
}
.active {
border: 1px solid black;
}
</style>
</head>
<body>
<div class="container">
<h3></h3>
<p></p>
<img src="" alt="">
<ul></ul>
</div>
<script>
let data = {
id: 1,
name: '小米13 ultra',
price: 5999.00,
images: ['1.png', '2.png', '3.png', '4.png']
};
// 更新名称
let h3 = document.querySelector('h3');
h3.innerText = data.name;
// 更新价格
let p = document.querySelector('p');
p.innerText = data.price.toFixed(2);
// 更新大图
let cover = document.querySelector('div>img');
cover.src = data.images[0];
cover.alt = data.images[0];
// 更新小图
let str = '';
data.images.forEach((img, index) => {
if (index === 0) {
str += `<li><img src="${img}" alt="${img}" class="active"></li>`;
} else {
str += `<li><img src="${img}" alt="${img}"></li>`;
}
})
let ul = document.querySelector('ul');
ul.innerHTML = str;
// 找到所有li中的img,添加点击事件
let imgs = ul.querySelectorAll('img');
imgs.forEach(img => {
img.addEventListener('click', () => {
// 找到所有的小图元素将class属性移除(去掉所有的黑框)
for (let item of imgs) {
item.removeAttribute('class');
}
// 将当前被点击元素的src和alt更新到cover大图上
cover.src = img.src;
cover.alt = img.alt;
// 给当前元素添加class为active的类,让当前元素显示黑框
img.setAttribute('class', 'active');
})
});
</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>数据更新后的渲染</title>
<style>
* {
padding: 0;
margin: 0;
}
table {
width: 600px;
margin: 50px auto;
border: 1px solid black;
border-collapse: collapse;
text-align: center;
}
th,
td {
border: 1px solid black;
padding: 5px;
}
div {
text-align: center;
margin-top: 20px;
}
button {
padding: 5px 15px;
font-size: 16px;
margin: 0 10px;
border: none;
cursor: pointer;
border: 1px solid white;
}
button:hover {
border: 1px solid black;
}
button:first-child {
background-color: green;
color: white;
}
button:nth-child(2) {
background-color: yellow;
color: black;
}
button:last-child {
background-color: red;
color: white;
}
</style>
</head>
<body>
<div>
<button id="create">添加</button>
<button id="update">更新</button>
<button id="delete">删除</button>
</div>
<table>
<thead>
<tr>
<th>编号</th>
<th>名称</th>
<th>价格</th>
</tr>
</thead>
<tbody></tbody>
</table>
<script>
/*
使用初始数据渲染页面,页面提供三个功能
- {id: 1, name: 'iphone 14 plus', 'price': 6999.00}
- 添加:接收用户输入编号,名称和价格,将数据保存到对象数组中
- 更新:
- 接收用户输入编号,如果编号不存在,就弹窗提示“编号错误”
- 编号正确,就接收用户输入的名称和价格,更新到对应编号的数据中
- 删除:
- 接收用户输入的编号,将对应编号的数据删除
*/
let data = [
{ id: 1, name: 'iphone 14 plus', price: 6999.00 },
{ id: 2, name: '富光水杯', price: 29.99 },
{ id: 3, name: '紫米充电头', price: 129.00 },
{ id: 4, name: 'thinkpad x13', price: 5999.00 },
{ id: 5, name: '小米手环8', price: 239.00 }
];
function render() {
let content = '';
data.forEach(item => {
content += `<tr>
<td>${item.id}</td>
<td>${item.name}</td>
<td>${item.price.toFixed(2)}</td>
</tr>`
})
let tbody = document.querySelector('tbody');
tbody.innerHTML = content;
}
render();
document.querySelector('#create').addEventListener('click', () => {
// 接收用户输入的编号
let id = +prompt('请输入编号');
// 使用find方法找对应id的数据,如果返回的不是undefined,证明数据存在,弹窗提示
let existsItem = data.find(item => item.id === id);
if (existsItem) {
alert('编号重复');
return;
}
// 数据没有重复,就继续输入名称和价格
let name = prompt('请输入名称');
let price = +prompt('请输入价格');
// 将数据保存到对象数组中
data.push({
id,
name,
price
});
render();
});
document.querySelector('#update').addEventListener('click', () => {
// 接收用户输入的编号
let id = +prompt('请输入编号');
// 使用find方法找对应id的数据,如果返回的是undefined,证明数据不存在,弹窗提示并退出
let existsItem = data.find(item => item.id === id);
if (!existsItem) {
alert('编号不存在');
return;
}
let name = prompt('请输入名称');
let price = +prompt('请输入价格');
existsItem.name = name;
existsItem.price = price;
render();
});
document.querySelector('#delete').addEventListener('click', () => {
// 接收用户输入的编号,将对应编号的数据删除
let id = +prompt('请输入要删除的编号');
// 找到对应id的数据
let target = data.find(item => item.id === id);
// 在data数组中找到这个target对象的下标
let index = data.indexOf(target);
if (index !== -1) {
// 通过splice方法将数组中target数据删除
data.splice(index, 1);
render();
}
// data = data.filter(item => item.id !== id)
// render()
});
</script>
</body>
</html>
操作CSS样式
setAttribute和getAttribute
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Attribute</title>
<style>
.red {
color: red;
}
.large {
font-size: 50px;
}
</style>
</head>
<body>
<p>我是一段文字,没撒意义</p>
<script>
/*
setAttribute和getAttribute
*/
let p = document.querySelector('p');
p.setAttribute('class', 'red'); // 字体变红
p.setAttribute('class', 'large'); // 字体变大
p.setAttribute('class', 'red large'); // 字体变红变大
console.log(p.getAttribute('class')); // 获取class属性值并输出
</script>
</body>
</html>
className属性
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>className</title>
<style>
.red {
color: red;
}
.large {
font-size: 50px;
}
</style>
</head>
<body>
<p>我是一段文字,没撒意义</p>
<script>
/*
className
*/
let p = document.querySelector('p');
console.log(p.className); // 获取className
p.className = 'red';
p.className = 'large';
p.className = 'large red'; // 同时拥有large和red两个类
console.log(p.className);
</script>
</body>
</html>
classList属性
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>classList</title>
<style>
.red {
color: red;
}
.large {
font-size: 50px;
}
</style>
</head>
<body>
<p class="large">我是一段文字,没撒意义</p>
<button>切换红色</button>
<script>
/*
classList属性:类数组对象保存class值
add(类名1, 类名2,...): 添加类
remove(类名1, 类名2, ...): 删除类
toggle(类名): 有就移除,没有就添加
*/
let p = document.querySelector('p');
console.log(p.classList); // 类数组数据
p.classList.remove('red', 'large'); // 同时删除red和large两个类
p.classList.add('large', 'nothing'); // 同时添加large和nothing两个类
document.querySelector('button').onclick = () => {
p.classList.toggle('red'); // 每次点击动态的切换red类
}
</script>
</body>
</html>
style属性
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>style属性</title>
<style>
.red {
color: red;
}
.large {
font-size: 50px;
}
</style>
</head>
<body>
<p class="red large">我是一段文字,没撒意义</p>
<script>
/*
style属性:设置和获取内联样式,不能获取内部样式和外部样式
提示:
- 多个单词构成的CSS属性最好改为小驼峰的形式设置
*/
let p = document.querySelector('p');
// p.style = "color:blue;"; 相当于 p.className = 'red';
p.style.color = 'blue'; // 设置内联样式颜色为蓝色
p.style.fontWeight = '900'; // 设置内联样式font-weight为900
console.log(p.style.fontWeight); // 900
p.style = 'color: black; font-weight: 100; background-color: red;'; // 同时设置三个内联样式
</script>
</body>
</html>
getComputedStyle方法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>getComputedStyle</title>
<style>
.red {
color: red;
}
.large {
font-size: 50px;
}
</style>
</head>
<body>
<p class="large red" style="color: blue; font-weight: 900;">我是一段文字,没撒意义</p>
<script>
/*
getComputedStyle: 获取元素最终的样式
*/
let p = document.querySelector('p');
let value = getComputedStyle(p);
console.log(value.color); // rgb(0,0,255)
console.log(value.fontWeight); // 900
console.log(value.fontSize); // 50px
</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>添加元素</title>
</head>
<body>
<ul>
<li>画楼西畔桂堂东</li>
<li>身无彩凤双飞翼</li>
</ul>
<script>
/*
添加元素步骤:
1. createElement创建新元素
2. 为元素设置属性和内容
3. appendChild或insertBefore添加元素到DOM树中
语法:
document.createElement(标签名) 返回对应标签的新元素,没有内容和属性
父元素.appendChild(子元素) 将子元素放到父元素的子元素列表的最后一个位置
父元素.insertBefore(子元素,参考元素) 将子元素插入到父元素中参考元素之前
注意:
- 如果appendChild或者insertBefore要插入的元素已经在DOM中,这个操作实际就是将元素移动到新的位置
*/
// 创建“心有灵犀一点通”的li元素,放到ul的最后一位
let newLi = document.createElement('li');
newLi.innerText = '心有灵犀一点通'; // 因为没有添加新元素到DOM树中,所以看不见
let ul = document.querySelector('ul');
// appendChild将newLi放到ul子元素的最后
ul.appendChild(newLi);
// 创建“昨夜星辰昨夜风”到ul的第一位
let firstLi = document.createElement('li');
firstLi.innerText = '昨夜星辰昨夜风';
// 获取目标参考元素<li>画楼西畔桂堂东</li>
let target = ul.querySelector('li');
// insertBefore将firstLi放在target之前
ul.insertBefore(firstLi, target);
// 移动已存在的DOM元素
ul.insertBefore(firstLi, newLi);
</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>删除元素</title>
</head>
<body>
<ul>
<li>昨夜星辰昨夜风</li>
<li>画楼西畔桂堂东</li>
<li>身无彩凤双飞翼</li>
<li>心有灵犀一点通</li>
</ul>
<script>
/*
删除元素语法
1. 元素.remove() 删除当前的元素
2. 父元素.removeChild(子元素) 父元素删除指定的子元素
*/
// 使用remove删除<li>画楼西畔桂堂东</li>
let li = document.querySelector('li:nth-child(2)');
li.remove();
// 使用removeChild删除心有灵犀一点通
let ul = document.querySelector('ul');
let li2 = document.querySelector('li:last-child');
ul.removeChild(li2);
// 遍历删除所有剩余的li
for (let item of document.querySelectorAll('li')) {
item.remove();
}
// 清空子元素:innerHTML = '';
</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>父子兄弟元素</title>
</head>
<body>
<div>
<h2>标题</h2>
<p>内容</p>
<ul>
<li>点赞</li>
<li>投币</li>
<li>收藏</li>
</ul>
</div>
<script>
/*
父子兄弟元素:通过一个元素,快速的获取关联的元素,不需要一个一个querySeletor获取
元素.parentElement 获取该元素的父元素
元素.firstElementChild 获取该元素的第一个子元素
元素.lastElementChild 获取该元素的最后一个子元素
元素.children 获取该元素所有的子元素
元素.previousElementSibling 获取该元素前面的兄弟元素
元素.nextElementSibling 获取该元素后面的兄弟元素
*/
let ul = document.querySelector('ul');
// parentElement
console.log(ul.parentElement); // div
console.log(ul.parentElement.parentElement); // body
// firstElementChild
console.log(ul.firstElementChild); // <li>点赞</li>
// lastElementChild
console.log(ul.lastElementChild); // <li>收藏</li>
// children
console.log(ul.children);
let p = document.querySelector('p');
// previousElementSibling
console.log(p.previousElementSibling); // <h2>标题</h2>
// nextElementSibling
console.log(p.nextElementSibling); // ul
console.log(p.nextElementSibling.nextElementSibling); // null
console.log(p.nextElementSibling.previousElementSibling); // p
</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>复制元素</title>
</head>
<body>
<div>
<img src="2.jpg" alt="花千骨">
<ul>
<li>hehe</li>
<li>haha</li>
</ul>
</div>
<script>
/*
cloneNode 复制节点元素
语法:
待克隆元素.cloneNode(deep) 返回被克隆的元素
注意:
- deep默认为false,只克隆当前的元素(不包括内容)
- deep设置为true,克隆该元素的所有内容
*/
// 克隆img元素到div的末尾
let img = document.querySelector('img');
let newImg = img.cloneNode();
let div = document.querySelector('div');
div.appendChild(newImg);
// 克隆ul(deep设置为false)
let ul = document.querySelector('ul');
let newUl = ul.cloneNode();
div.appendChild(newUl); // 添加了一个空的ul
// deep设置true
let doubleNewUl = ul.cloneNode(true);
div.appendChild(doubleNewUl); // 添加的ul包括了下面所有的内容
</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>新式方法</title>
</head>
<body>
<div>
<h2>不知道</h2>
<ul>
<li>生当作人杰</li>
<li>死亦为鬼雄</li>
</ul>
</div>
<script>
/*
语法
元素.append(新元素) 该元素将新元素,添加到子元素列表末尾
元素.prepend(新元素) 该元素将新元素,添加到子元素列表开头
元素.before(新元素) 将新元素放到该元素的前面
元素.after(新元素) 将新元素放到该元素的后面
*/
let ul = document.querySelector('ul');
// 添加新的li到ul的末尾
let lastLi = document.createElement('li');
lastLi.innerText = '李清照';
ul.append(lastLi);
// 添加新的li到ul的开头
let firstLi = document.createElement('li');
firstLi.innerText = '锄禾日当午';
ul.prepend(firstLi);
// 添加新的p元素到ul的前面
let title = document.createElement('p');
title.innerText = '夏日绝句';
ul.before(title);
// 添加新的p元素到ul的后面
let end = document.createElement('p');
end.innerText = '到此结束';
ul.after(end);
</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>输入框事件</title>
</head>
<body>
<input type="text" id="username" name="username" value="hello">
<script>
let input = document.querySelector('input');
// value属性可以获取input值
console.log(input.value);
// value属性也可以赋值
input.value = 'world';
// 获取焦点事件 focus : 输入前对准备数据(状态)进行验证,获取焦点以后修改样式等
input.addEventListener('focus', () => {
console.log('战斗已经开始');
})
// 失去焦点事件 blur : 对输入完成的数据进行验证,修改样式
input.addEventListener('blur', () => {
console.log('战斗已经结束');
})
// 输入内容触发input事件 : 输入过程中一直对数据进行验证,只要数据修改不满足要求,就可以立即给出提示
input.addEventListener('input', () => {
console.log(input.value);
})
</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>单选复选框事件</title>
</head>
<body>
<div>
<input type="radio" name="gender" id="male">男
<input type="radio" name="gender" id="female">女
</div>
<div>
<input type="checkbox" name="language" id="js">JavaScript
<input type="checkbox" name="language" id="java">Java
<input type="checkbox" name="language" id="py">Python
</div>
<script>
// 单选、复选元素可以通过click或change事件触发改动
let radios = document.querySelectorAll('[name="gender"]');
// 为所有单选按钮绑定click事件,触发时打印元素的id值
radios.forEach(radio => {
radio.addEventListener('click', () => {
// 可以获取当前选中元素的id值
console.log(radio.id);
});
});
let checkboxes = document.querySelectorAll('[name="language"]');
// 为所有的复选框绑定change事件,触发时打印元素id
checkboxes.forEach(box => {
box.addEventListener('change', () => {
// 复选框checked属性(布尔值)记录当前元素的选中状态
console.log(`${box.id}状态为:${box.checked}`);
})
});
</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>下拉列表事件</title>
</head>
<body>
<select name="hobby" id="hobby">
<option value="sing">唱</option>
<option value="dance">跳</option>
<option value="rap">Rap</option>
</select>
<script>
let select = document.querySelector('select');
// selected属性:默认选中
select.querySelector('option:last-child').selected = true;
// 为select添加change事件,当用户操作option选项时,会将这个option的value值更新到select的value属性上
select.addEventListener('change', () => {
console.log(select.value);
})
</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>框架事件</title>
</head>
<body>
<h2>框架事件</h2>
<img src="" alt="花千骨" width="300">
<script>
/*
load事件:页面全部加载完成时触发(包括CSS,图片等资源)
语法: 为window对象添加load事件
DOMContentloaded事件: 页面HTML全部解析加载完成
语法:为document对象添加DOMContentloaded事件
*/
window.addEventListener('load', () => {
console.log('此处的代码在整个页面加载完成以后才会触发');
});
document.addEventListener('DOMContentLoaded', () => {
console.log('此处的代码在DOM加载完成后就会执行');
})
// 之后为了避免因DOM加载问题,网络问题,或者资源问题导致代码执行出错,将页面初始化相关代码放到load或者
// DOMContentLoaded事件处理函数中
window.onload = () => {
// render();
}
document.addEventListener('DOMContentLoaded', () => {
// querySelector;
// render();
});
// resize事件:在窗口大小发生变化时触发,能够通过window.innerWidth和window.innerHeight获取窗口可视区域的大小
// 语法:为window添加resize事件
window.addEventListener('resize', () => {
console.log(`宽度${window.innerWidth},高度${window.innerHeight}`);
})
</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>鼠标事件</title>
</head>
<body>
<div style="width: 300px; height: 200px; border: 1px solid black;">
<button>点我</button>
</div>
<script>
let btn = document.querySelector('button');
// click 单击事件
btn.onclick = () => {
console.log('我被点了');
}
// mousedown 鼠标按下
btn.onmousedown = () => {
console.log('这个人开始点了');
}
// mouseup 鼠标松开
btn.onmouseup = () => {
console.log('这个人松开了');
}
// dblclick 双击事件
btn.ondblclick = () => {
console.log('连点两次');
}
// 触发顺序:mousedown-mouseup-click-dblclick
// contextmenu 鼠标右键点击
let div = document.querySelector('div');
div.oncontextmenu = () => {
console.log('用户右键查看功能列表');
}
// 鼠标移入移出
// 移入:mouseover mouseenter
// 移出: mouseout mouseleave
// 注意: mouseover和mouseout在进入子元素时,也会触发;mouseenter和mouseleave不会
div.onmouseover = () => {
console.log('over进入');
}
div.onmouseenter = () => {
console.log('enter进入');
}
div.onmouseout = () => {
console.log('out离开');
}
div.onmouseleave = () => {
console.log('leave离开');
}
</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>
</head>
<body>
<script>
// keydown 键盘按下
// keypress 键盘按下并松开
// keyup 键盘松开
// 触发顺序: keydown-keypress-keyup
document.addEventListener('keydown', () => {
console.log('有一个按键被按下了');
})
document.addEventListener('keypress', () => {
console.log('有一个按键被按下并松开了');
})
document.addEventListener('keyup', () => {
console.log('终于松开了');
})
</script>
</body>
</html>
事件对象
事件对象:每次事件发生的时候,浏览器会自动创建event事件对象,把触发事件的信息放到event对象上,将这个对象作为参数传递给事件处理函数,供函数使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>事件对象</title>
</head>
<body>
<button style="height: 2000px;">点我</button>
<script>
let btn = document.querySelector('button');
btn.addEventListener('click', (e) => {
// type属性:触发实现的类型
console.log(e.type);
// currentTarget属性 正在处理事件的元素
console.log(e.currentTarget);
// clientX, clientY 触发事件时鼠标相对于窗口的位置
console.log(e.clientX, e.clientY);
// pageX, pageY 触发事件时鼠标相对于整个页面的位置
console.log(e.pageX, e.pageY);
});
document.addEventListener('keyup', (e) => {
// key属性:保存触发键盘事件的按键字符
console.log(e.key);
// keyCode: 键盘事件的按键码
console.log(e.keyCode);
})
</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>事件流</title>
</head>
<body>
<div style="width: 300px; height: 200px; border: 1px solid black;">
<button>点我</button>
</div>
<script>
/*
事件流:
当某个事件触发时,这个事件在嵌套标签之间的触发顺序
根据触发顺序不同分成事件冒泡和事件捕获
*/
document.querySelector('button').onclick = () => {
alert('按钮被点击');
}
document.querySelector('div').onclick = () => {
alert('容器被点击');
}
</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>事件冒泡</title>
</head>
<body>
<div style="width: 300px; height: 200px; border: 1px solid black;">
<button>点我</button>
</div>
<script>
/*
事件冒泡:
当事件发生在一个元素上,会先执行这个元素的事件处理函数
然后执行这个元素的父元素的事件处理函数,再找这个元素的父
级的父级,一直执行到根元素的事件处理函数
*/
document.querySelector('button').onclick = () => {
alert('button触发');
}
document.querySelector('div').onclick = () => {
alert('div触发');
}
document.body.onclick = () => {
alert('body触发');
}
</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>事件冒泡</title>
</head>
<body>
<div style="width: 300px; height: 200px; border: 1px solid black;">
<button>点我</button>
</div>
<script>
/*
currentTarget属性:当前正在处理事件的元素
target属性:始终指向嵌套最深的,引发事件的元素
*/
// 如果点击的是button
document.querySelector('button').onclick = (e) => {
console.log(e.currentTarget); // button
console.log(e.target); // button
}
document.querySelector('div').onclick = (e) => {
console.log(e.currentTarget); // div
console.log(e.target); // button
}
document.body.onclick = (e) => {
console.log(e.currentTarget); // body
console.log(e.target); //button
}
</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>阻止事件冒泡</title>
</head>
<body>
<div style="width: 300px; height: 200px; border: 1px solid black;">
<button>点我</button>
</div>
<script>
/*
阻止事件冒泡
默认事件冒泡流中,即使内层的元素可以保证事件处理完成,依旧会将事件
传播到外层嵌套元素。如果内层处理函数可以将事件处理完成,可以使用
event对象的stopPropagation方法阻止这个行为
语法:
event对象.stopPropagation();
注意:
- 除非在实际代码中明确需要阻止冒泡,否则不要随便这样做
*/
document.querySelector('button').onclick = (e) => {
e.stopPropagation(); // 阻止事件冒泡到外层元素
alert('我自己就可以完全处理');
}
document.querySelector('div').onclick = (e) => {
alert('我也能处理');
}
</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>事件捕获</title>
</head>
<body>
<div style="width: 300px; height: 200px; border: 1px solid black;">
<button>点我</button>
</div>
<script>
/*
事件捕获:
与事件冒泡相反,从根元素开始出发,一直走到目标(最深层嵌套)元素
默认事件流是事件冒泡的机制,可以在绑定事件时设置事件捕获模式
语法:
元素.addEventListener(事件类型,事件处理函数,是否启用捕获模式)
注意:
- DOM0级只支持事件冒泡,DOM2级支持冒泡和捕获两种机制
- 移除事件处理函数时,其中的启用捕获模式值也必须完全一致
- 事件流传播在嵌套元素中触发相同类型的事件处理函数,如果类型不同不会触发
DOM事件传播阶段
- 捕获阶段:从根元素走到目标元素
- 目标阶段:到达了目标元素
- 冒泡阶段:从目标元素走到根元素
*/
document.querySelector('button').addEventListener('click', () => {
console.log('button触发');
})
document.querySelector('div').addEventListener('click', () => {
console.log('div触发');
}, true)
document.body.addEventListener('click', () => {
console.log('body触发');
})
document.documentElement.addEventListener('click', () => {
console.log('html触发');
}, true);
</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>默认行为</title>
</head>
<body>
<a href="http://www.baidu.com">百度</a>
<form action="3-事件流.html" method="get">
<input type="text" id="username" name="username">
<input type="submit" value="提交">
</form>
<input type="checkbox">看看能不能勾选
<script>
/*
不同元素触发事件会有不同的默认行为
- 链接点击会跳转
- 表单点击提交按钮会将数据提交到指定的位置
- 点击复选框选中元素
- 右键出现上下文菜单
阻止默认行为
1. 标准方式:event对象.preventDefault();
2. DOM0级:return false;
*/
// 阻止超链接的跳转
document.querySelector('a').addEventListener('click', (e) => {
e.preventDefault(); // 可以阻止
return false; // 不能阻止,只有DOM0级可以这么用
})
document.querySelector('a').onclick = (e) => {
return false;
}
// 阻止表单提交
document.querySelector('[type="submit"]').addEventListener('click', (e) => {
e.preventDefault();
})
// 阻止复选框选中
document.querySelector('[type="checkbox"]').addEventListener('click', (e) => {
e.preventDefault();
})
</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>事件委托</title>
</head>
<body>
<ul>
<li>天王盖地虎</li>
<li>宝塔镇河妖</li>
<li>谁知盘中餐</li>
<li>粒粒皆辛苦</li>
</ul>
<script>
/*
事件委托:
利用事件冒泡机制和event对象,将原本需要添加到每个子元素的事件,
交给父元素执行,这样可以减少事件处理程序,提高代码可维护性
步骤:
1. 给父元素绑定事件处理程序
2. 触发事件时,通过event对象的target属性可以获得触发事件的目标元素
3. 根据目标元素的属性和内容执行相应的代码
*/
let list = document.querySelectorAll('li');
list.forEach(li => {
li.addEventListener('click', (e) => {
alert(e.target.innerHTML);
})
})
// 事件委托
let ul = document.querySelector('ul');
ul.addEventListener('click', (e) => {
// 元素nodeName属性记录当前元素的标签名(大写)
console.log(e.target.nodeName);
// 如果触发事件的是li,在执行对应的代码;否则不执行
if (e.target.nodeName === 'LI') {
alert(e.target.innerHTML);
}
})
</script>
</body>
</html>