参考文献
- 后盾人编程doc.houdunren.com/
- JavaScript权威指南
基础知识
操作文档 HTML 的 JS 处理方式为 DOM 即 Document Object Model 文档对象模型。如果对 HTML 很了解使用 DOM 并不复杂。
浏览器在加载页面是会生成 DOM 对象,以供我们使用 JS 控制页面元素。
文档渲染
浏览器会将 HTML 文本内容进行渲染,并生成相应的 JS 对象,同时会对不符规则的标签进行处理。
- 浏览器会将标签规范后渲染页面
- 目的一让页面可以正确呈现
- 目的二可以生成统一的 JS 可操作对象
标签修复
在 html 中只有内容houdunren.com 而没有任何标签时,通过浏览器的 检查>元素 标签查看会自动修复成以下格式的内容
下面 H1 标签结束错误并且属性也没有引号,浏览器在渲染中会进行修复
<body>
<h1 id=houdunren>后盾人<h1>
</body>
处理后的结果
<html>
<head></head>
<body>
<h1 id="houdunren">后盾人</h1>
</body>
</html>
表格处理
表格 tabel 中不允许有内容,浏览器在渲染过程中会进行处理
<table>
houdunren.com
<tr>
<td>houdunwang.com</td>
</tr>
</table>
渲染后会添加 tbody 标签并将 table 中的字符移出
houdunren.com
<table>
<tbody>
<tr>
<td>houdunwang.com</td>
</tr>
</tbody>
</table>
标签移动
所有内容要写在 BODY 标签中,下面的 SCRIPT 标签写在了 BODY 后面,浏览器渲染后也会进行处理
<body></body>
<script>
console.dir('houdunren.com')
</script>
渲染后处理的结果
<body>
<script>
console.dir('houdunren.com')
</script>
</body>
操作时机
需要保证浏览器已经渲染了内容才可以读取的节点对象,下例将无法读取到节点对象
<script>
const node = document.getElementById('houdunwang')
console.log(node) //null
</script>
<h1 id="houdunwang">houdunren.com</h1>
不过我们可以将脚本通过事件放在页面渲染完执行
<script>
window.onload = () => {
const node = document.getElementById('houdunwang')
console.log(node)
}
</script>
<h1 id="houdunwang">houdunren.com</h1>
或使用定时器将脚本设置为异步执行
<script>
setTimeout(() => {
const node = document.getElementById('houdunwang')
console.log(node)
})
</script>
<h1 id="houdunwang">houdunren.com</h1>
也可以放在文档加载后的事件处理函数中
<script>
window.onload = function () {
let hd = document.getElementById('hd')
console.log(hd)
}
</script>
<div id="hd">houdunren</div>
或将脚本设置在外部文件并使用 defer 属性加载,defer 即会等到 DOM 解析后迟延执行
<script defer="defer" src="3.js"></script>
<div id="houdunwang"></div>
节点对象
JS 中操作 DOM 的内容称为节点对象(node),即然是对象就包括操作 NODE 的属性和方法
- 包括 12 种类型的节点对象
- 常用的节点为 document、标签元素节点、文本节点、注释节点
- 节点均继承自 Node 类型,所以拥有相同的属性或方法
- document 是 DOM 操作的起始节点
<body id="houdunwang">
<!-- 后盾人 -->
</body>
<script>
// document节点 noteType为9
console.log(document.nodeType)
// 第一个子节点为<!DOCTYPE html>,且nodetype为10
console.log(document.childNodes.item(0).nodeType)
// body 是标签节点 nodeType为1
console.log(document.body.nodeType)
// body的属性节点 nodeType 为2
console.log(document.body.attributes[0].nodeType)
// body的第一个节点为文本节点,nodeType为3
console.log(document.body.childNodes.item(0).nodeType)
// body的第二个节点为注释,nodeType类型为8
console.log(document.body.childNodes[1].nodeType)
</script>
原型链
在浏览器渲染过程中会将文档内容生成为不同的对象,我伙来对下例中的 h1 标签进行讨论,其他节点情况相似
- 不同类型节点由专有的构造函数创建对象
- 使用 console.dir 可以打印出 DOM 节点对象结构
- 节点也是对象所以也具有 JS 对象的特征
<h1 id="houdunwang">houdunren.com</h1>
<script>
function prototype(el) {
console.dir(el.__proto__)
el.__proto__ ? prototype(el.__proto__) : ''
}
const node = document.getElementById('houdunwang')
prototype(node)
</script>
最终得到的节点的原型链为
| 原型 | 说明 |
|---|---|
| Object | 根对象,提供 hasOwnProperty 等基本对象操作支持 |
| EventTarget | 提供 addEventListener、removeEventListener 等事件支持方法 |
| Node | 提供 firstChild、parentNode 等节点操作方法 |
| Element | 提供 getElementsByTagName、querySelector 等方法 |
| HTMLElement | 所有元素的基础类,提供 childNodes、nodeType、nodeName、className、nodeName 等方法 |
| HTMLHeadingElement | Head 标题元素类 |
我们将上面的方法优化一下,实现提取节点原型链的数组
<h2 id="h2 value">houdunren.com</h2>
<input type="text" id="inputId" value="后盾人" />
<script>
function prototype(el) {
const prototypes = []
prototypes.push(el.__proto__)
prototypes.push(...(el.__proto__ ? prototype(el.__proto__) : []))
return prototypes
}
const h2 = document.querySelector('h2')
const input = document.querySelector('input')
console.log(prototype(input))
</script>
下面为标题元素增加两个原型方法,改变颜色与隐藏元素
<h2 onclick="this.color('red')">houdunren.com</h2>
<script>
const h2 = document.querySelector('h2')
HTMLHeadingElement.prototype = Object.assign(HTMLHeadingElement.prototype, {
color(color) {
this.style.color = color
},
hide() {
this.style.display = 'none'
},
})
</script>
对象特征
即然 DOM 与我们其他 JS 创建的对象特征相仿,所以也可以为 DOM 对象添加属性或方法。
对于系统应用的属性,应该明确含义不应该随意使用,比如 ID 是用于标识元素唯一属性,不能用于其他目地
- 后面会讲到其他解决方案,来自定义属性,ID 属性可以直接修改但是不建议这么做
let hd = document.getElementById('hd')
hd.id = 'houdunren.com'
console.log(hd)
- title 用于显示提示文档也不应该用于其他目地
<div id="hd">houdunren.com</div>
<script>
let hd = document.getElementById('hd')
hd.title = 'houdunren.com'
console.log(hd)
</script>
下面是为对象合并属性的示例
<div id="hd">houdunren.com</div>
<script>
let hd = document.getElementById('hd')
Object.assign(hd, {
//设置标签内容
innerHTML: '向军大叔',
color: 'red',
change() {
this.innerHTML = '后盾人'
this.style.color = this.color
},
onclick() {
this.change()
},
})
</script>
使用对象特性更改样式属性
<div id="hd">houdunren.com</div>
<script>
let hd = document.getElementById('hd')
Object.assign(hd.style, {
color: 'white',
backgroundColor: 'red',
})
</script>
选择Document元素
每个Window对象都有一个document属性,引用一个Document对象,这个Document对象是操作文档的核心对象
客户端的js程序经常需要操作文档中一个或多个元素。全局的document属性引用Document对象,而Document对象有head和body属性分别引用和标签对应的Element对象。但一个程序想要操作文档中嵌入层级更多的元素,必须先通过某种方式获取或选择表示该元素的Element对象
通过CSS选择元素
在讲解css选择元素前需要补充一些选择符的知识,就是css选择符就是通过元素类型(标签)、ID、类名、属性以及元素在文档中的位置来引用元素的一种标记,通过这种标记能很好的定位元素
| 方法名 | 参数 | 功能描述 |
|---|---|---|
| querySelector | 包含一个或多个要匹配的选择器的 DOM 字符串DOMString。该字符串必须是有效的 CSS 选择器字符串;如果不是,则引发SYNTAX_ERR异常。 | querySelector方法接受一个css选择符字符串作为参数返回它在文档中找到的第一个匹配的元素。如果没有找到返回null |
| querySelectorAll | 一个包含一个或多个匹配的选择器的字符串。其必须是一个有效的 CSS 选择器字符串,如果不是,会抛出 SyntaxError 异常。 | querySelectorAll方法接受一个css选择符字符串作为参数并返回所有与该选择符匹配的元素,返回的元素是一个类似数组的NodeList对象,可以对该对象进行迭代 |
| closest | 一个包含一个或多个匹配的选择器的字符串。 | 若调用它的元素不是选择符对应的元素,该方法会向上进行查找匹配元素。也就是与querySelector查找方向相反 |
| matches | 一个包含一个或多个匹配的选择器的字符串。 | 该方法不会返回祖先,也不会返回后代,只会检查是否与选择符匹配,如果匹配返回true;否则,返回false |
🔔
注意querySelector不接受伪元素选择器
querySelectorAll
使用 querySelectorAll 根据 CSS 选择器获取 Nodelist 节点列表
- 获取的 NodeList 节点列表是静态的,添加或删除元素后不变
获取所有 div 元素
<div class="xiangjun">向军大叔</div>
<div id="app">
<div class="houdunren houdunwang">houdunren.com</div>
<div class="houdunwang">houdunwang.com</div>
</div>
<script>
const app = document.getElementById('app')
const nodes = app.querySelectorAll('div')
console.log(nodes.length) //2
</script>
获取 id 为 app 元素的,class 为 houdunren 的后代元素
<div class="xiangjun">向军大叔</div>
<div id="app">
<div class="houdunren houdunwang">houdunren.com</div>
<div class="houdunwang">houdunwang.com</div>
</div>
<script>
const nodes = document.querySelectorAll('#app .houdunren')
console.log(nodes.length) //2
</script>
根据元素属性值获取元素集合
<div id="app">
<div class="houdunren houdunwang" data="hd">houdunren.com</div>
<div class="houdunwang">houdunwang.com</div>
</div>
<script>
const nodes = document.querySelectorAll(`#app .houdunren[data='hd']`)
console.log(nodes.length) //2
</script>
再来看一些通过样式选择器查找元素
<div id="app">
<div class="houdunren">houdunren.com</div>
<div class="houdunwang">houdunwang.com</div>
<span>后盾人</span>
</div>
<script>
//查找紧临兄弟元素
console.log(document.querySelectorAll('.houdunren+div.houdunwang'))
//查找最后一个 div 子元素
console.log(document.querySelector('#app div:last-of-type'))
//查找第二个 div 元素
console.log(document.querySelector('#app div:nth-of-type(2)').innerHTML)
</script>
querySelector
querySelector 使用 CSS 选择器获取一个元素,下面是根据属性获取单个元素
<div id="app">
<div class="houdunren houdunwang" data="hd">houdunren.com</div>
<div class="houdunwang">houdunwang.com</div>
</div>
<script>
const node = app.querySelector(`#app .houdunren[data='hd']`)
console.log(node)
</script>
matches
用于检测元素是否是指定的样式选择器匹配,下面过滤掉所有 name 属性的 LI 元素
<div id="app">
<li>houdunren</li>
<li>向军大叔</li>
<li name="houdunwang">houdunwang.com</li>
</div>
<script>
const nodes = [...document.querySelectorAll('li')].filter(node => {
return !node.matches(`[name]`)
})
console.log(nodes)
</script>
closest
查找最近的符合选择器的祖先元素(包括自身),下例查找父级拥有 .comment类的元素
<div class="comment">
<ul class="comment">
<li>houdunren.com</li>
</ul>
</div>
<script>
const li = document.getElementsByTagName('li')[0]
const node = li.closest(`.comment`)
//结果为 ul.comment
console.log(node)
</script>
其它选择属性的方法
除了querySelector和querySelectorAll方法以外,DOM也定义了一些老式的元素选择方法。
| 方法 | 参数 | 功能描述 |
|---|---|---|
| document.getElementById(id) | id属性的值,不用加# | 通过元素 id 来查找元素 |
| document.getElementsByTagName(name) | 标签名 | 通过标签名来查找元素 |
| document.getElementsByClassName(name) | 元素类名 | 通过类名来查找元素 |
系统提供了丰富的选择节点(NODE)的操作方法,下面我们来一一说明。
getElementById
使用 ID 选择是非常方便的选择具有 ID 值的节点元素,但注意 ID 应该是唯一的
只能通过 document 对象上使用
<div id="houdunren">houdunren.com</div>
<script>
const node = document.getElementById('houdunren')
console.dir(node)
</script>
getElementById 只能通过 document 访问,不能通过元素读取拥有 ID 的子元素,下面的操作将产生错误
<div id="app">
houdunren.com
<div id="houdunwang">houdunwang.com</div>
</div>
<script>
const app = document.getElementById('app')
const node = app.getElementById('houdunwang') //app.getElementById is not a function
console.log(node)
</script>
下面自定义函数来支持批量按 ID 选择元素
<div id="houdunren">houdunren.com</div>
<div id="app"></div>
<script>
function getByElementIds(ids) {
return ids.map((id) => document.getElementById(id))
}
let nodes = getByElementIds(['houdunren', 'app'])
console.dir(nodes)
</script>
拥有 ID 的元素可做为 WINDOW 的属性进行访问
<div id="app">
houdunren.com
</div>
<script>
console.log(app.innerHTML)
</script>
如果声明了变量这种访问方式将无效,所以并不建议使用这种方式访问对象
<div id="app">
houdunren.com
</div>
<script>
let app = 'houdunwang'
console.log(app.innerHTML)
</script>
getElementsByName
使用 getElementByName 获取设置了 name 属性的元素,虽然在 DIV 等元素上同样有效,但一般用来对表单元素进行操作时使用。
- 返回 NodeList 节点列表对象
- NodeList 顺序为元素在文档中的顺序
- 需要在 document 对象上使用
<div name="houdunren">houdunren.com</div>
<input type="text" name="username" />
<script>
const div = document.getElementsByName('houdunren')
console.dir(div)
const input = document.getElementsByName('username')
console.dir(input)
</script>
getElementsByTagName
使用 getElementsByTagName 用于按标签名获取元素
- 返回 HTMLCollection 节点列表对象
- 是不区分大小的获取
<div name="houdunren">houdunren.com</div>
<div id="app"></div>
<script>
const divs = document.getElementsByTagName('div')
console.dir(divs)
</script>
通配符
可以使用通配符 ***** 获取所有元素
<div name="houdunren">houdunren.com</div>
<div id="app"></div>
<script>
const nodes = document.getElementsByTagName('*')
console.dir(nodes)
</script>
某个元素也可以使用通配置符 ***** 获取后代元素,下面获取 id 为 houdunren 的所有后代元素
<div id="houdunren">
<span>houdunren.com</span>
<span>houdunwang.com</span>
</div>
<script>
const nodes = document.getElementsByTagName('*').namedItem('houdunren').getElementsByTagName('*')
console.dir(nodes)
</script>
getElementsByClassName
getElementsByClassName 用于按 class 样式属性值获取元素集合
- 设置多个值时顺序无关,指包含这些 class 属性的元素
<div class="houdunren houdunwang xiangjun">houdunren.com</div>
<div class="houdunwang">houdunwang.com</div>
<script>
const nodes = document.getElementsByClassName('houdunwang')
console.log(nodes.length) //2
//查找同时具有 houdunwang 与 houdunren 两个class属性的元素
const tags = document.body.getElementsByClassName('houdunwang houdunren ')
console.log(tags.length) //1
</script>
下面我们来自己开发一个与 getElementsByClassName 相同的功能函数
<div class="houdunren houdunwang xiangjun">houdunren.com</div>
<div class="houdunwang">houdunwang.com</div>
<script>
function getByClassName(names) {
//将用户参数转为数组,并过滤掉空值
const classNames = names.split(/\s+/).filter(t => t)
return Array.from(document.getElementsByTagName('*')).filter(tag => {
// 提取标签的所有 class 值为数组
return classNames.every(className => {
const names = tag.className
.toUpperCase()
.split(/\s+/)
.filter(t => t)
//检索标签是否存在class
return names.some(name => name == className.toUpperCase())
})
})
}
console.log(getByClassName('houdunwang houdunren '))
</script>
预选择元素
JS 提供了访问常用节点的 api
| 方法 | 说明 |
|---|---|
| document | document 是 DOM 操作的起始节点 |
| document.documentElement | 文档节点即 html 标签节点 |
| document.body | body 标签节点 |
| document.head | head 标签节点 |
| document.links | 超链接集合 |
| document.anchors | 所有锚点集合 |
| document.forms | form 表单集合 |
| document.images | 图片集合 |
当然js还支持直接通过id和名字来索引元素
<img id="title1"></img>
<img id="title2"></img>
<a name="name1"></a>
<a name="name2"></a>
<script>
console.log(console.log(document.images.title1))
console.log(console.log(document.anchors.name1))
console.log(console.log(document.anchors.name2))
</script>
节点集合
两种节点集合
Nodelist 与 HTMLCollection 都是包含多个节点标签的集合,大部分功能也是相同的。
- getElementsByClassName 等方法返回的是 NodeList
- querySelectorAll 返回的是 HTMLCollection
- NodeList 节点列表是动态的,即内容添加后会动态更新
<div></div>
<div></div>
<script>
//结果为NodeList
console.log(document.querySelectorAll('div'))
//结果为HTMLCollection
console.log(document.getElementsByTagName('div'))
</script>
length
Nodelist 与 HTMLCollection 包含 length 属性,记录了节点元素的数量
<div name="app">
<div id="houdunren">houdunren.com</div>
<div name="houdunwang">houdunwang.com</div>
</div>
<script>
const nodes = document.getElementsByTagName('div')
for (let i = 0; i < nodes.length; i++) {
console.log(nodes[i])
}
</script>
item
Nodelist 与 HTMLCollection 提供了 item()方法来根据索引获取元素
<div name="app">
<div id="houdunren">houdunren.com</div>
<div name="houdunwang">houdunwang.com</div>
</div>
<script>
const nodes = document.getElementsByTagName('div')
console.dir(nodes.item(0))
</script>
使用数组索引获取更方便
<div name="app">
<div id="houdunren">houdunren.com</div>
<div name="houdunwang">houdunwang.com</div>
</div>
<script>
const nodes = document.getElementsByTagName('div')
console.dir(nodes[0])
</script>
namedItem
HTMLCollection 具有 namedItem 方法可以按 name 或 id 属性来获取元素
<div name="app">
<div id="houdunren">houdunren.com</div>
<div name="houdunwang">houdunwang.com</div>
</div>
<script>
const nodes = document.getElementsByTagName('div')
console.dir(nodes.namedItem('houdunwang'))
console.dir(nodes.namedItem('houdunren'))
</script>
也可以使用数组或属性方式获取
<div name="app">
<div id="houdunren">houdunren.com</div>
<div name="houdunwang">houdunwang.com</div>
</div>
<script>
const nodes = document.getElementsByTagName('div')
console.dir(nodes['houdunwang']);
console.dir(nodes.houdunren)
</script>
数字索引时使用 item 方法,字符串索引时使用 namedItem 或 items 方法
<h1 id="hd">houdunren.com</h1>
<h1 name="xj">向军大叔</h1>
<script>
let items = document.getElementsByTagName('h1')
console.log(items[0])
console.log(items['xj'])
</script>
动态与静态
通过 getElementsByTagname 等 getElementsBy... 函数获取的 Nodelist 与 HTMLCollection 集合是动态的,即有元素添加或移动操作将实时反映最新状态。
- 使用 getElement...返回的都是动态的集合
- 使用 querySelectorAll 返回的是静态集合
比如下面的案例
下例中通过按钮动态添加元素后,获取的元素集合是动态的,而不是上次获取的固定快照。
<h1>houdunren.com</h1>
<h1>houdunwang.com</h1>
<button id="add">添加元素</button>
<script>
let elements = document.getElementsByTagName('h1')
console.log(elements)
let button = document.querySelector('#add')
button.addEventListener('click', () => {
document.querySelector('body').insertAdjacentHTML('beforeend', '<h1>向军大叔</h1>')
console.log(elements.length) // 随元素添加而增大
})
</script>
document.querySelectorAll 获取的集合是静态的
<h1>houdunren.com</h1>
<h1>houdunwang.com</h1>
<button id="add">添加元素</button>
<script>
let elements = document.querySelectorAll('h1')
console.log(elements.length)
let button = document.querySelector('#add')
button.addEventListener('click', () => {
document.querySelector('body').insertAdjacentHTML('beforeend', '<h1>向军大叔</h1>')
console.log(elements.length) // 2
})
</script>
如果需要保存静态集合,则需要对集合进行复制
<div id="houdunren">houdunren.com</div>
<div name="houdunwang">houdunwang.com</div>
<script>
const nodes = document.getElementsByTagName('div')
const clone = Array.prototype.slice.call(nodes)
console.log(nodes.length);//2
document.body.appendChild(document.createElement('div'))
console.log(nodes.length);//3
console.log(clone.length);//2
</script>
遍历节点集合
forOf
Nodelist 与 HTMLCollection 是类数组的可迭代对象所以可以使用 for...of 进行遍历
<div id="houdunren">houdunren.com</div>
<div name="houdunwang">houdunwang.com</div>
<script>
const nodes = document.getElementsByTagName('div')
for (const item of nodes) {
console.log(item)
}
</script>
forEach
Nodelist 节点列表也可以使用 forEach 来进行遍历,但 HTMLCollection 则不可以
<div id="houdunren">houdunren.com</div>
<div name="houdunwang">houdunwang.com</div>
<script>
const nodes = document.querySelectorAll('div')
nodes.forEach((node, key) => {
console.log(node)
})
</script>
call/apply
节点集合对象原型中不存在 map 方法,但可以借用 Array 的原型 map 方法实现遍历
<div id="houdunren">houdunren.com</div>
<div name="houdunwang">houdunwang.com</div>
<script>
const nodes = document.querySelectorAll('div')
Array.prototype.map.call(nodes, (node, index) => {
console.log(node, index)
})
</script>
当然也可以使用以下方式操作
;[].filter.call(nodes, node => {
console.log(node)
})
Array.from
Array.from 用于将类数组转为组件,并提供第二个迭代函数。所以可以借用 Array.from 实现遍历
<div id="houdunren">houdunren.com</div>
<div name="houdunwang">houdunwang.com</div>
<script>
const nodes = document.getElementsByTagName('div')
Array.from(nodes, (node, index) => {
console.log(node, index)
})
</script>
展开语法
下面使用点语法转换节点为数组
<h1>houdunren.com</h1>
<h1>houdunwang.com</h1>
<script>
let elements = document.getElementsByTagName('h1')
console.log(elements)
;[...elements].map((item) => {
item.addEventListener('click', function () {
this.style.textTransform = 'uppercase'
})
})
</script>
文档结构与遍历
不包含文本节点和注释结点的API
下面的api默认忽视文本节点和注释结点
| 节点属性 | 说明 |
|---|---|
| parentElement | 获取父元素 |
| children | 获取所有子元素 |
| childElementCount | 子标签元素的数量 |
| firstElementChild | 第一个子标签 |
| lastElementChild | 最后一个子标签 |
| previousElementSibling | 上一个兄弟标签 |
| nextElementSibling | 下一个兄弟标签 |
| contains | 返回布尔值,判断传入的节点是否为该节点的后代节点 |
包含文本节点和注释结点的API
下面的api不会忽视文本节点和注释结点
| 节点属性 | 说明 |
|---|---|
| parentNode | 获取父结点 |
| childNodes | 获取所有子结点 |
| childElementCount | 子标签元素的数量 |
| firstElementChild | 第一个子标签 |
| lastElementChild | 最后一个子标签 |
| previousElementSibling | 上一个兄弟标签 |
| nextElementSibling | 下一个兄弟标签 |
| contains | 返回布尔值,判断传入的节点是否为该节点的后代节点 |
属性
节点属性
不同类型的节点拥有不同属性,下面是节点属性的说明与示例
nodeType
nodeType 指以数值返回节点类型
| nodeType | 说明 |
|---|---|
| 1 | 元素节点 |
| 2 | 属性节点 |
| 3 | 文本节点 |
| 8 | 注释节点 |
| 9 | document 对象 |
<div id="app">
<div class="houdunren" data="hd">houdunren.com</div>
<div class="houdunwang">houdunwang.com</div>
<div class="xiangjun"><!-- 向军大叔 --></div>
</div>
<script>
const node = document.querySelector(`#app`)
console.log(node.nodeType) //1
console.log(node.firstChild.nodeType) //3
console.log(node.attributes.id.nodeType) //2
const xj = document.querySelector('.xiangjun')
console.log(xj.childNodes[0].nodeType) //8
</script>
下面是根据指定的 nodeType 递归获取节点元素的示例
可获取文本、注释、标签等节点元素
<!-- 后盾人 -->
后盾人 houdunren.com
<div id="app">
<ul>
<li>
<span></span>
<span>
<!-- 向军 -->
</span>
</li>
<li><span></span><span></span></li>
<li><span></span><span></span></li>
</ul>
</div>
<script>
function all(el, nodeType = 1) {
const nodes = []
Array.from(el.childNodes).map(node => {
if (node.nodeType == nodeType) nodes.push(node)
if (node.nodeType == 1) nodes.push(...all(node, nodeType))
})
return nodes
}
console.log(all(document.body))
</script>
Prototype
当然也可以使用对象的原型进行检测
- section 、main、aslide 标签的原型对象为 HTMLElement
- 其他非系统标签的原型对象为 HTMLUnknownElement
let h1 = document.querySelector('h1')
let p = document.querySelector('p')
console.log(h1 instanceof HTMLHeadingElement) //true
console.log(p instanceof HTMLHeadingElement) //false
console.log(p instanceof Element) //true
下例是通过构建函数获取节点的示例
<!-- 后盾人 -->
后盾人 houdunren.com
<div id="app">
<ul>
<li>
<span></span>
<span>
<!-- 向军 -->
</span>
</li>
<li><span></span><span></span></li>
<li><span></span><span></span></li>
</ul>
</div>
<script>
function all(el, prototype) {
const nodes = []
Array.from(el.childNodes).map(node => {
if (node instanceof prototype) nodes.push(node)
if (node.nodeType == 1) nodes.push(...all(node, prototype))
})
return nodes
}
console.log(all(document.body, HTMLSpanElement))
</script>
nodeName
nodeName 指定节点的名称
获取值为大写形式
| nodeType | nodeName |
|---|---|
| 1 | 元素名称如 DIV |
| 2 | 属性名称 |
| 3 | #text |
| 8 | #comment |
下面来操作 nodeName
<div id="app">
<div class="houdunren" data="hd">houdunren.com</div>
<div class="houdunwang">houdunwang.com</div>
<div class="xiangjun"><!-- 向军大叔 --></div>
<span> 后盾人</span>
</div>
<script>
const div = document.querySelector(`#app`)
const span = document.querySelector('span')
// 标签节点为大写的标签名DIV
console.log(div.nodeName)
console.log(span.nodeName)
// 文本节点为 #text
console.log(div.firstChild.nodeName)
//属性节点为属性名
console.log(div.attributes.id.nodeName)
// 注释节点为#comment
const xj = document.querySelector('.xiangjun')
console.log(xj.childNodes[0].nodeName)
</script>
tagName
nodeName 可以获取不限于元素的节点名,tagName 仅能用于获取标签节点的名称
- tagName 存在于 Element 类的原型中
- 文本、注释节点值为 undefined
- 获取的值为大写的标签名
<div id="app">
<div class="houdunren" data="hd">houdunren.com</div>
<div class="houdunwang">houdunwang.com</div>
<div class="xiangjun"><!-- 向军大叔 --></div>
<span> 后盾人</span>
</div>
<script>
const div = document.querySelector(`#app`)
const span = document.querySelector('span')
// 标签节点为大写的标签名 如DIV、SPAN
console.log(div.tagName)
console.log(span.tagName)
// 文本节点为undefined
console.log(div.firstChild.tagName)
//属性节点为undefined
console.log(div.attributes.id.tagName)
// 注释节点为 undefined
const xj = document.querySelector('.xiangjun')
console.log(xj.childNodes[0].tagName)
</script>
nodeValue
使用 nodeValue 或 data 函数获取节点值,也可以使用节点的 data 属性获取节点内容
| nodeType | nodeValue |
|---|---|
| 1 | null |
| 2 | 属性值 |
| 3 | 文本内容 |
| 8 | 注释内容 |
下面来看 nodeValue 的示例
<div id="app">
<div class="houdunren">houdunren.com</div>
<div class="houdunwang">houdunwang.com</div>
<div class="xiangjun"><!-- 向军大叔 --></div>
</div>
<script>
const node = document.querySelector(`#app`)
//标签的 nodeValue 值为 null
console.log(node.nodeValue)
//属性的 nodeVale 值为属性值
console.log(node.attributes.id.nodeValue)
//文本的 nodeValue 值为文本内容
const houdunwang = document.querySelector('.houdunwang')
console.log(houdunwang.firstChild.nodeValue)
//注释的 nodeValue 值为注释内容
const xj = document.querySelector('.xiangjun')
console.log(xj.childNodes[0].nodeValue)
</script>
使用 data 属性可以获取文本与注释内容
<div id="app">
houdunren.com
<!-- 后盾人 注释内容-->
</div>
<script>
const app = document.querySelector('#app')
console.log(app.childNodes[0].data)
console.log(app.childNodes[1].data)
</script>
树状节点
下面获取标签树状结构即多级标签结构,来加深一下节点的使用
<div id="app">
<ul>
<li><span></span><span></span></li>
<li><span></span><span></span></li>
<li><span></span><span></span></li>
</ul>
</div>
<script>
function tree(el) {
return Array.from(el.childNodes)
.filter(node =>node.tagName)
.map(node => ({
name: node.nodeName,
children: tree(node),
}))
}
console.log(tree(document.getElementById('app')))
上例结果如下
Array(2)
0: {name: 'HEAD', children: Array(4)}
1: {name: 'BODY', children: Array(2)}
标准属性
元素的标准属性具有相对应的 DOM 对象属性
- 操作属性区分大小写
- 多个单词属性命名规则为第一个单词小写,其他单词大写
- 属性值是多类型并不全是字符串,也可能是对象等
- 事件处理程序属性值为函数
- style 属性为 CSSStyleDeclaration 对象
- DOM 对象不同生成的属性也不同
属性别名
有些H属性名与 JS 关键词冲突,系统已经起了别名
| 属性 | 别名 |
|---|---|
| class | className |
| for | htmlFor |
操作属性
元素的标准属性可以直接进行操作,下面是直接设置元素的 className
<div id="app">
<div class="houdunren" data="hd">houdunren.com</div>
<div class="houdunwang">houdunwang.com</div>
</div>
<script>
const app = document.querySelector(`#app`)
app.className = 'houdunren houdunwang'
</script>
下面设置图像元素的标准属性
<img src="" alt="" />
<script>
let img = document.images[0]
img.src = 'https://www.houdurnen.com/avatar.jpg'
img.alt = '后盾人'
</script>
使用 hidden 隐藏元素
<div id="app">houdunren.com</div>
<script>
const app = document.querySelector('#app')
app.addEventListener('click', function () {
this.hidden = true
})
</script>
多类型
大部分属性值是都是字符串,但并不是全部,下例中需要转换为数值后进行数据运算
<input type="number" name="age" value="88" />
<script>
let input = document.getElementsByName('age').item(0)
input.value = parseInt(input.value) + 100
</script>
下面表单 checked 属性值为 Boolean 类型
<label for="hot"> <input id="hot" type="checkbox" name="hot" />热门 </label>
<script>
const node = document.querySelector(`[name='hot']`)
node.addEventListener('change', function () {
console.log(this.checked)
})
</script>
属性值并都与 HTML 定义的值一样,下面返回的 href 属性值是完整链接
<a href="#houdunren" id="home">后盾人</a>
<script>
const node = document.querySelector(`#home`)
console.log(node.href)
</script>
操作属性
Element类定义了下面四种方法用于增删改查结点特征,同时HTML元素的属性(标准属性)也在表示这些元素的HTMLElement对象上具有相应的属性。
对于标准的属性可以使用 DOM 属性的方式进行操作,但对于标签的非标准的定制属性则不可以。但 JS 提供了方法来控制标准或非标准的属性
可以理解为元素的属性分两个地方保存,DOM 属性中记录标准属性,特征中记录标准和定制属性
- 使用特征操作时属性名称不区分大小写
- 特征值都为字符串类型
| 方法 | 说明 |
|---|---|
| getAttribute | 获取属性 |
| setAttribute | 设置属性 |
| removeAttribute | 删除属性 |
| hasAttribute | 属性检测 |
特征是可迭代对象,下面使用 for...of 来进行遍历操作
<div id="app" content="后盾人" color="red">houdunwang.com</div>
<script>
const app = document.querySelector('#app')
for (const { name, value } of app.attributes) {
console.log(name, value)
}
</script>
属性值都为字符串,所以数值类型需要进行转换
<input type="number" name="age" value="88" />
<script>
let input = document.getElementsByName('age').item(0)
let value = input.getAttribute('value') * 1 + 100
input.setAttribute('value', value)
</script>
使用 removeAttribute 删除元素的 class 属性,并通过 hasAttribute 进行检测删除结果
<div class="houdunwang">houdunwang.com</div>
<script>
let houdunwang = document.querySelector('.houdunwang')
houdunwang.removeAttribute('class')
console.log(houdunwang.hasAttribute('class')) //false
</script>
特征值与 HTML 定义是一致的,这和属性值是不同的
<a href="#houdunren" id="home">后盾人</a>
<script>
const node = document.querySelector(`#home`)
// http://127.0.0.1:5500/test.html#houdunren
console.log(node.href)
// #houdunren
console.log(node.getAttribute('href'))
</script>
attributes
元素提供了 attributes 属性可以只读的获取元素的属性
<div class="houdunwang" data-content="后盾人">houdunwang.com</div>
<script>
let houdunwang = document.querySelector('.houdunwang')
console.dir(houdunwang.attributes['class'].nodeValue) //houdunwang
console.dir(houdunwang.attributes['data-content'].nodeValue) //后盾人
</script>
自定义特征
虽然可以随意定义特征并使用 getAttribute 等方法管理,但很容易造成与标签的现在或未来属性重名。建议使用以 data-为前缀的自定义特征处理,针对这种定义方式 JS 也提供了接口方便操作。
- 元素中以 data-为前缀的属性会添加到属性集中
- 使用元素的 dataset 可获取属性集中的属性
- 改变 dataset 的值也会影响到元素上
下面演示使用属性集设置 DIV 标签内容
<div class="houdunwang" data-content="后盾人" data-color="red">houdunwang.com</div>
<script>
let houdunwang = document.querySelector('.houdunwang')
let content = houdunwang.dataset.content
console.log(content) //后盾人
houdunwang.innerHTML = `<span style="color:${houdunwang.dataset.color}">${content}</span>`
</script>
多个单词的特征使用驼峰命名方式读取
<div class="houdunwang" data-title-color="red">houdunwang.com</div>
<script>
let houdunwang = document.querySelector('.houdunwang')
houdunwang.innerHTML = `
<span style="color:${houdunwang.dataset.titleColor}">${houdunwang.innerHTML}</span>
`
</script>
改变 dataset 值也会影响到页面元素上
<div class="houdunwang" data-title-color="red">houdunwang.com</div>
<script>
let houdunwang = document.querySelector('.houdunwang')
houdunwang.addEventListener('click', function () {
this.dataset.titleColor = ['red', 'green', 'blue'][Math.floor(Math.random() * 3)]
this.style.color = this.dataset.titleColor
})
</script>
属性同步
特征和属性是记录元素属性的两个不同场所,大部分更改会进行同步操作。
下面使用属性更改了 className,会自动同步到了特征集中,反之亦然
<div id="app" class="red">houdunren.com</div>
<script>
const app = document.querySelector('#app')
app.className = 'houdunwang'
console.log(app.getAttribute('class')) //houdunwang
app.setAttribute('class', 'blue')
console.log(app.className) //blue
</script>
下面对 input 值使用属性设置,但并没有同步到特征
<input type="text" name="package" value="houdunren.com" />
<script>
const package = document.querySelector(`[name='package']`)
package.value = 'houdunwang.com'
console.log(package.getAttribute('value'))//houdunren.com
</script>
但改变 input 的特征 value 会同步到 DOM 对象属性
<input type="text" name="package" value="houdunren.com" />
<script>
const package = document.querySelector(`[name='package']`)
package.setAttribute('value', 'houdunwang.com')
console.log(package.value) //houdunwang.com
</script>
元素内容
作为HTML的内容
innerHTML
inneHTML 用于向标签中添加 html 内容,设置InnerHTML通常效率很高。不过要注意,通过+=操作符给innerHTML追加文本的效率不高。因为这个操作既会涉及序列化操作,也会涉及解析操作:先把元素内容转换为字符串,然后再把新字符串转换回元素内容,也就是浏览器的解析器会重绘DOM
使用 innertHTML 操作会重绘元素,下面在点击第二次就没有效果了
- 因为对#app 内容进行了重绘,即删除原内容然后设置新内容
- 重绘后产生的 button 对象没有事件
- 重绘后又产生了新 img 对象,所以在控制台中可看到新图片在加载
<div id="app">
<button>houdunren.com</button>
<img src="1.jpg" alt="" />
</div>
<script>
const app = document.querySelector('#app')
app.querySelector('button').addEventListener('click', function () {
alert(this.innerHTML)
this.parentElement.innerHTML += '<hr/>向军大叔'
})
</script>
outerHTML
outerHTML 与 innerHTML 的区别是包含父标签
- outerHTML 不会删除原来的旧元素
- 只是用新内容替换替换旧内容,旧内容(元素)依然存在
下面将 div#app 替换为新内容
<div id="app">
<div class="houdunren" data="hd">houdunren.com</div>
<div class="houdunwang">houdunwang.com</div>
</div>
<script>
let app = document.querySelector('#app')
console.log(app.outerHTML)
app.outerHTML = '<h1>后盾人</h1>'
</script>
使用 innerHTML 内容是被删除然后使用新内容
<div id="app">
houdunren.com
</div>
<script>
const app = document.querySelector('#app')
console.log(app)
app.innerHTML = 'houdunwang.com'
console.log(app)
</script>
而使用 outerHTML 是保留旧内容,页面中使用新内容
<div id="app">
houdunren.com
</div>
<script>
const app = document.querySelector('#app')
console.log(app)
app.outerHTML = 'houdunwang.com'
console.log(app)
</script>
insertAdjacentHTHL
另一个相关的Element方法是insertAdjacentHTHL,用于插入与指定元素“相邻”(adjacent)的任意HTML标记字符串。要插入的标签作为第二个参数传入,而“相邻”的精确含义取决于第一个参数的值。第一个参数可以是以下字符串值中的一个:“beforebegin" "afterbegin" "beforeend" "afterend"。
作为纯文本的内容
textConten和innerText
textContent 与 innerText 是访问或添加文本内容到元素中
- textContent 部分 IE 浏览器版本不支持
- innerText 部分 FireFox 浏览器版本不支持
- 获取时忽略所有标签,只获取文本内容
- 设置时将内容中的标签当文本对待不进行标签解析
获取时忽略内容中的所有标签
<div id="app">
<h1>houdunren.com</h1>
</div>
<script>
let app = document.querySelector('#app')
console.log(app.textContent)
</script>
设置时将标签当文本对待,即转为 HTML 实体内容
<div id="app">
<div class="houdunren" data="hd">houdunren.com</div>
<div class="houdunwang">houdunwang.com</div>
</div>
<script>
let app = document.querySelector('#app')
app.textContent="<h1>后盾人</h1>"
</script>
insertAdjacentText
将文本插入到元素指定位置,不会对文本中的标签进行解析,包括以下位置
| 选项 | 说明 |
|---|---|
| beforebegin | 元素本身前面 |
| afterend | 元素本身后面 |
| afterbegin | 元素内部前面 |
| beforeend | 元素内部后面 |
添加文本内容到 div#app 前面
<div id="app">
<div class="houdunren" data="hd">houdunren.com</div>
<div class="houdunwang">houdunwang.com</div>
</div>
<script>
let app = document.querySelector('#app')
let span = document.createElement('span')
app.insertAdjacentText('beforebegin', '<h1>后盾人</h1>')
</script>
<script>元素中的文本
行内(即那些没有src 属性的)<script>元素有一个text属性,可以用于获取它们的文本。浏览器永远不会显示<script>元素的内容,HTML解析器会忽略脚本中的尖括号和&字符。这就让<script>元素成为在Web应用中嵌入任意文本数据的理想场所。只要把这个元素的type属性设置为某个值(如text/x-custom-data),明确它不是可执行的JavaScript代码即可。这样,JavaScript解释器将会忽略这个脚本,但该元素还会出现在文档树中,它的text属性可以返回你在其中保存的数据。
创建、插入和删除节点
我们已经知道了如何获取及使用HTML和纯文本字符串修改文档内容。也知道了可以遍历Document,查找构成文档的个别Element和Text节点。当然,在个别节点的层级修改文档也是可能的。Document类定义了创建Element对象的方法,而Element和Text对象拥有在树中插入、删除和替换节点的方法
推荐方法
| 方法 | 说明 |
|---|---|
| append | 节点尾部添加新节点或字符串 |
| prepend | 节点开始添加新节点或字符串 |
| before | 节点前面添加新节点或字符串 |
| after | 节点后面添加新节点或字符串 |
| replaceWith | 将节点替换为新节点或字符串 |
| cloneNode | 拷贝结点,因为如果某个元素已经在文档中了,你有把它插入到其他地方,那它会转移到新位置,而不会复制到一个新的过去。所以可以使用cloneNode来复制一个节点 |
| remove | remove方法可以将结点或文本删除 |
在标签内容后面添加新内容
<div id="app">
houdunren.com
</div>
<script>
let app = document.querySelector('#app')
app.append('-houdunwang.com')
</script>
同时添加多个内容,包括字符串与元素标签
<div id="app">
houdunren.com
</div>
<script>
let app = document.querySelector('#app')
let h1 = document.createElement('h1')
h1.append('后盾人')
app.append('@', h1)
</script>
将标签替换为新内容
<div id="app">
houdunren.com
</div>
<script>
let app = document.querySelector('#app')
let h1 = document.createElement('h1')
h1.append('houdunwang.com')
app.replaceWith(h1)
</script>
将 h2 移动到 h1 之前
<h1>houdunren.com@h1</h1>
<h2>houdunwang@h2</h2>
<script>
let h1 = document.querySelector('h1')
let h2 = document.querySelector('h2')
h1.before(h2)
</script>
使用 remove 方法可以删除节点
<div id="app">
houdunren.com
</div>
<script>
let app = document.querySelector('#app')
app.remove()
</script>
insertAdjacentHTML
将 html 文本插入到元素指定位置,浏览器会对文本进行标签解析,包括以下位置
| 选项 | 说明 |
|---|---|
| beforebegin | 元素本身前面 |
| afterend | 元素本身后面 |
| afterbegin | 元素内部前面 |
| beforeend | 元素内部后面 |
在 div#app 前添加 HTML 文本
<div id="app">
<div class="houdunren" data="hd">houdunren.com</div>
<div class="houdunwang">houdunwang.com</div>
</div>
<script>
let app = document.querySelector('#app')
let span = document.createElement('span')
app.insertAdjacentHTML('beforebegin', '<h1>后盾人</h1>')
</script>
insertAdjacentElement
insertAdjacentElement() 方法将指定元素插入到元素的指定位置,包括以下位置
- 第一个参数是位置
- 第二个参数为新元素节点
| 选项 | 说明 |
|---|---|
| beforebegin | 元素本身前面 |
| afterend | 元素本身后面 |
| afterbegin | 元素内部前面 |
| beforeend | 元素内部后面 |
在 div#app 前插入 span 标签
<div id="app">
<div class="houdunren" data="hd">houdunren.com</div>
<div class="houdunwang">houdunwang.com</div>
</div>
<script>
let app = document.querySelector('#app')
let span = document.createElement('span')
span.innerHTML = '后盾人'
app.insertAdjacentElement('beforebegin', span)
</script>
古老方法
下面列表过去使用的操作节点的方法,现在不建议使用了。但在阅读老代码时可来此查看语法
| 方法 | 说明 |
|---|---|
| appendChild | 添加节点 |
| insertBefore | 用于插入元素到另一个元素的前面 |
| removeChild | 删除节点 |
| replaceChild | 进行节点的替换操作 |
DocumentFragment
当对节点进行添加、删除等操作时,都会引起页面回流来重新渲染页面,即重新渲染颜色,尺寸,大小、位置等等。所以会带来对性能的影响。
解决以上问题可以使用以下几种方式
- 可以将 DOM 写成 html 字符串,然后使用 innerHTML 添加到页面中,但这种操作会比较麻烦,且不方便使用节点操作的相关方法。
- 使用 createDocumentFragment 来管理节点时,此时节点都在内存中,而不是 DOM 树中。对节点的操作不会引发页面回流,带来比较好的性能体验。
DocumentFragment 特点
- createDocumentFragment 父节点为 null
- 继承自 node 所以可以使用 NODE 的属性和方法
- createDocumentFragment 创建的是文档碎片,节点类型 nodeType 为 11。因为不在 DOM 树中所以只能通过 JS 进行操作
- 添加 createDocumentFragment 添加到 DOM 后,就不可以再操作 createDocumentFragment 元素了,这与 DOM 操作是不同的
- 将文档 DOM 添加到 createDocumentFragment 时,会移除文档中的 DOM 元素
- createDocumentFragment 创建的节点添加到其他节点上时,会将子节点一并添加
- createDocumentFragment 是虚拟节点对象,不直接操作 DOM 所以性能更好
- 在排序/移动等大量 DOM 操作时建议使用 createDocumentFragment
操作 CSS
通过 DOM 修改样式可以通过更改元素的 class 属性或通过 style 对象设置行样式来完成。
🔔
建议使用 class 控制样式,将任务交给 CSS 处理,更简单高效
批量设置
使用 JS 的 className 可以批量设置样式
<div id="app" class="d-flex container">后盾人</div>
<script>
let app = document.getElementById('app')
app.className = 'houdunwang'
</script>
也可以通过特征的方式来更改
<div id="app" class="d-flex container">后盾人</div>
<script>
let app = document.getElementById('app')
app.setAttribute('class', 'houdunwang')
</script>
classList
如果对类单独进行控制使用 classList 属性操作
| 方法 | 说明 |
|---|---|
| node.classList.add | 添加类名 |
| node.classList.remove | 删除类名 |
| node.classList.toggle | 切换类名 |
| node.classList.contains | 类名检测 |
在元素的原有 class 上添加新 class
<div id="app" class="d-flex container">后盾人</div>
<script>
let app = document.getElementById('app')
app.classList.add('houdunwang')
</script>
使用 classList 也可以移除 class 列表中的部分 class
<div id="app" class="d-flex container">后盾人</div>
<script>
let app = document.getElementById('app')
app.classList.remove('container')
</script>
使用 toggle 切换类,即类已经存在时删除,不存在时添加
<div id="app" class="d-flex container">后盾人</div>
<script>
let app = document.getElementById('app')
app.addEventListener('click', function () {
this.classList.toggle('houdunwang')
})
</script>
使用 contains 检查 class 是否存在
<div id="app" class="d-flex container">后盾人</div>
<script>
let app = document.getElementById('app')
console.log(app.classList.contains('container')) //true
console.log(app.classList.contains('houdunwang')) //false
</script>
设置行样式
行样式对象
每个节点都有style属性,这个style属性是一个对象(CSSStyleDeclaration)。是对元素上的style属性的抽象。但是其属性都是驼峰命名法(因为-连字符会被JavaScript视为减号)
样式属性设置
使用节点的 style 对象来设置行样式
多个单词的属性使用驼峰进行命名
<div id="app" class="d-flex container">后盾人</div>
<script>
let app = document.getElementById('app')
app.style.backgroundColor = 'red'
app.style.color = 'yellow'
</script>
批量设置行样式
使用 cssText 属性可以批量设置行样式,属性名和写 CSS 一样不需要考虑驼峰命名
<div id="app" class="d-flex container">后盾人</div>
<script>
let app = document.getElementById('app')
app.style.cssText = `background-color:red;color:yellow`
</script>
也可以通过 setAttribute 改变 style 特征来批量设置样式
<div id="app" class="d-flex container">后盾人</div>
<script>
let app = document.getElementById('app')
app.setAttribute('style', `background-color:red;color:yellow;`)
</script>
计算样式
通过style属性获取的CSSStyleDeclaration获取的样式仅仅包含元素标签中的style中的样式,而不能获取样式表中的样式。所以这时候就需要用到计算样式了。通过getComputedStyle方法可以获得包含style中样式和样式表中的样式在内的所有样式,并且这个方法调用后也返回的也是CSSStyleDeclaration对象。只不过这个对象是只读的,并且还有一个缺陷就是它不能返回简写属性,比如margin。
使用 window.getComputedStyle 可获取所有应用在元素上的样式属性
- 函数第一个参数为元素
- 第二个参数为伪类
这是计算后的样式属性,所以取得的单位和定义时的可能会有不同
<style>
div {
font-size: 35px;
color: yellow;
}
</style>
<div id="app" style="background-color: red; margin: 20px;">后盾人</div>
<script>
let app = document.getElementById('app')
let fontSize = window.getComputedStyle(app).fontSize
console.log(fontSize.slice(0, -2))
console.log(parseInt(fontSize))
</script>
操作样式表
除了上面介绍的通过class和style来操作样式以外,还可以通过操作style标签和link标签来操作样式,比如下面实现了对主题的切换
<!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 id="theme">
body{
width:100vw;
height: 100vh;
background-color: black;
}
</style>
</head>
<body>
<button onclick="changeTheme()">切换主题</button>
<script>
let st = document.querySelector("#theme")
console.log("a");
function changeTheme(){
console.log("a");
st.disabled = !st.disabled
}
</script>
</body>
</html>
CSS动画与事件
使用JavaScript还可以通过为元素添加移除class来完成过渡或事件。并且动画和过渡都有对应的事件派发,可以通过它们来获取事件对象进而完成更多的操作
<!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>
.transparent{
opacity: 0;
}
.fadeable{
transition: opacity .5s ease-in;
}
#box{
width:100px;
height: 300px;
background-color: aquamarine;
}
</style>
</head>
<body>
<div id="box" class="fadeable"></div>
<button onclick="change()">显隐</button>
<script>
let box = document.querySelector("#box")
function change(){
box.classList.toggle('transparent')
}
box.addEventListener('transitionstart',(TransitionEvent)=>{
console.log(TransitionEvent);
console.log("动画开始了");
})
box.addEventListener('transitionend',()=>{
console.log("动画结束了");
})
</script>
</body>
</html>
对于animation也与transition类似,具体可见MDN