DOM节点操作 重绘与回流

1. DOM节点

  1. DOM节点: DOM树里每一个内容都称之为节点
1. 节点类型:
  1. 元素节点 • 所有的标签 比如 body、 div • html 是根节点
  2. 属性节点 • 所有的属性 比如 href
  3. 文本节点 • 所有的文本
  4. 重点记住元素节点, 可以更好的让我们理清标签元素之间的关系

1. 查找节点

1.查找父节点:

  1. parentNode 属性, 返回最近一级的父节点 找不到返回为null
1
2
3
4
5
6
<div class="box">
<div class="box1">你好</div>
</div>
// 1. parentNode 查找父节点
let num = document.querySelector('.box1')
num.parentNode.style.display = 'none'
  1. 关闭二维码案例:
1
2
3
4
5
6
7
8
9
10
<div class="box">
<img src="images/1.png">
<i class="box1">x</i>
</div>
//点击关闭按钮, 关闭的是二维码的盒子, 还要获取erweima盒子
//点击关闭按钮, 直接关闭它的爸爸,就无需获取erweima元素了
let num = document.querySelector('.box1')
num.addEventListener('click', function () {
this.parentNode.style.display = 'none'
})
  1. 关闭多个二维码案例
1
2
3
4
5
6
7
8
9
10
11
12
<div class="box1">
<span class="box2"></span>
</div>
//多个二维码,点击谁,谁关闭
let num1 = document.querySelectorAll('.box2')
// 绑定多个事件给box3
for (let num2 = 0; num2 < num1.length; num2++) {
num1[num2].addEventListener('click', function () {
// 关闭当前二维码 点击谁 就关闭父元素
this.parentNode.style.display = 'none'
})
}

2. 查找子节点

  1. childNodes: 获得所有子节点、包括文本节点(空格、换行)、注释节点等
  2. children[重点]: 仅获得所有元素节点, 返回的还是一个伪数组
1
2
3
4
5
6
7
8
// 1. 查找子节点 children 伪元素
let num1 = document.querySelector('button')
let num2 = document.querySelector('ul')
num1.addEventListener('click', function () {
for (let num3 = 0; num3 < num2.children.length; num3++) {
num2.children[num3].style.color = 'green'
}
})

3. 兄弟关系查找

1. 下一个兄弟节点
  1. nextElementSibling 属性
1
2
3
4
5
let num1 = document.querySelector('.btn')
let num2 = document.querySelector('.two')
num1.addEventListener('click', function () {
// 1.查找下一个节点
num2.nextElementSibling.style.color = 'red'
2. 上一个兄弟节点
  1. previousElementSibling 属性
1
2
// 2 查找上一个节点
num2.previousElementSibling.style.color = 'pink'

2. 增加节点

1. 创建节点

  1. 即创造出一个新的网页元素,再添加到网页内,一般先创建节点,然后插入节点
1
2
let num2 = document.createElement('li')
num2.innerHTML = '我是创建新节点/追加节点'

2. 追加节点

  1. 要想在界面看到, 还得插入到某个父元素中
1. 插入到父元素的最后一个子元素:
1
2
let num1 = document.querySelector('ul')
num1.appendChild(num2)
2. 插入到父元素中某个子元素的前面:
1
num1.insertBefore(num2, num1.children[0])
3. 学成在线案例渲染
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let data = [
{
src: 'images/course01.png',
title: 'Think PHP 5.0 博客系统实战项目演练',
num: 1125
}
let num2 = document.querySelector('ul')
for (let num1 = 0; num1 < data.length; num1++) {
let num3 = document.createElement('li')
num3.innerHTML = `
<img src=${data[num1].src} alt="">
<h4>
${data[num1].title}
</h4>
<div class="info">
<span>高级</span> • <span> ${data[num1].num}</span>人在学习
</div>
`
num2.appendChild(num3)
}

3. 克隆节点

  1. cloneNode会克隆出一个跟原标签一样的元素,括号内传入布尔值
  2. 若为true,则代表克隆时会包含后代节点一起克隆
  3. 若为false,则代表克隆时不包含后代节点, 默认为false
1
2
3
4
5
let num1 = document.querySelector('ul')
// 括号默认为false 则不克隆后代节点
// 若是true 则克隆后代
let num2 = num1.cloneNode(true)
document.body.appendChild(num2)

4. 删除节点

  1. 若一个节点在页面中已不需要时,可以删除它
  2. 在 JavaScript 原生DOM操作中,要删除元素必须通过父元素删除
  3. 如不存在父子关系则删除不成功
  4. 删除节点和隐藏节点(display:none) 有区别的: 隐藏节点还是存在的,但是删除,则从html中删除节点
1
2
3
4
5
6
let num3 = document.querySelector('button')
let num4 = document.querySelector('ol')
num3.addEventListener('click', function () {
// 语法: 父元素.removeChild(子元素)
num4.removeChild(num4.children[0])
})

3. 时间对象

  1. 时间对象:用来表示时间的对象, 作用:可以得到当前系统时间

1. 实例化

  1. 在代码中发现了 new 关键字时,一般将这个操作称为实例化
  2. 创建一个时间对象并获取时间
1
2
3
4
5
6
// 小括号为空 可获得当前时间
let num1 = new Date()
document.write(num1)
// 小括号写时间 可返回指定时间
let num2 = new Date('2023-3-31 00:00')
document.write(num2)

2. 时间对象方法

  1. 因为时间对象返回的数据我们不能直接使用,所以需要转换为实际开发中常用的格式
方法 作用 说明
getFullYear() 获得年份 获取四位年份
getMonth() 获得月份 取值为0~11
getDate() 获得月份中的每一天 不同月份取值也不同
getDay() 获取星期 取值为0~6
getHours() 获取小时 取值为0~23
getMinutes() 获取分钟 取值为0~59
getSeconds() 获取秒 取值为0~59
1
2
3
4
5
6
7
8
9
// 年月日
console.log(num1.getFullYear())
console.log(num1.getMonth() + 1)
console.log(num1.getDate())
console.log(num1.getDay())
// 时分秒
console.log(num1.getHours())
console.log(num1.getMinutes())
console.log(num1.getSeconds())
页面显示时间:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 1. 显示时间案例
fn() // 先调用函数 省去1秒空白期
setInterval(fn, 1000)
function fn() {
// 实例化时间对象 写到定时器里才行
let arr = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六']
let num3 = new Date()
let n1 = num3.getFullYear()
let n2 = num3.getMonth() + 1
let n3 = num3.getDate()
let n4 = num3.getDay()
let n5 = num3.getHours()
let n6 = num3.getMinutes()
let n7 = num3.getSeconds()
let n8 = document.querySelector('.box')
n8.innerHTML = `今天是${n1}${n2}${n3}${arr[n4]} ${n5}:${n6}:${n7}`
}

3. 时间戳

  1. 时间戳: 是指1970年1月1日0时0分0秒起至现在的毫秒数, 它是一种特殊的计量时间的方式
  2. 时间对象里面的方法转换实际所用
  3. 重点记住 +new Date() 因为可以返回当前时间戳或者指定的时间戳

1. 三种方式获取时间戳:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 无需实例化
// 但是只能得到当前的时间戳, 而前面两种可以返回指定时间的时间戳
// 利用将来时间减 现在时间戳 = 剩余时间毫秒
// 1.getTime() 方法
let num1 = new Date()
document.write(num1.getTime())

// 2.new +Date()
document.write(+new Date()) // 当前时间戳
document.write(+new Date('2023-4-1 00:00:00')) // 指定时间

// 3.Date.now 只能得到当前
document.write(Date.now())

2. 毕业倒计时效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
fn()
setInterval(fn, 1000)
function fn() {
// 1.获取当前时间戳
let n1 = +new Date()

// 2.获取指定时间戳
let n2 = +new Date('2023-4-1 00:00:00')

// 3.计算剩余毫秒 / 1000 = 剩余秒数
let n3 = (n2 - n1) / 1000
// console.log(n3)

// 4.转换时分秒
let h = parseInt(n3 / 60 / 60 % 24)
h = h < 10 ? '0' + h : h
let m = parseInt(n3 / 60 % 60)
m = m < 10 ? '0' + m : m
let s = parseInt(n3 % 60)
s = s < 10 ? '0' + s : s
// console.log(h, m, s)

let m1 = document.querySelector('#hour')
let m2 = document.querySelector('#minutes')
let m3 = document.querySelector('#scond')
m1.innerHTML = h
m2.innerHTML = m
m3.innerHTML = s

let m4 = document.querySelector('.next')
let m5 = document.querySelector('.tips')
let m6 = new Date()
let m7 = m6.getFullYear()
let m8 = m6.getMonth() + 1
let m9 = m6.getDate()
let m10 = m6.getHours()
let m11 = m6.getMinutes()
let m12 = m6.getSeconds()
m4.innerHTML = `今天是${m7}${m8}${m9}日`
m5.innerHTML = `现在是${m10}:${m11}:${m12}`
}

4. 重绘和回流

浏览器是如何进行界面渲染的:

  1. 解析(Parser)HTML,生成DOM树(DOM Tree)
  2. 同时解析(Parser) CSS,生成样式规则 (Style Rules)
  3. 根据DOM树和样式规则,生成渲染树(Render Tree)
  4. 进行布局 Layout(回流/重排):根据生成的渲染树,得到节点的几何信息(位置,大小)
  5. 进行绘制 Painting(重绘): 根据计算和获取的信息进行整个页面的绘制
  6. Display: 展示在页面上

1. 回流(重排)

  1. 当 Render Tree 中部分或者全部元素的尺寸、结构、布局等发生改变时,浏览器就会重新渲染部分或全部文档的过 程称为 回流

2. 重绘

  1. 由于节点(元素)的样式的改变并不影响它在文档流中的位置和文档布局时(比如:color、background-color、 outline等), 称为重绘

3. 重绘不一定引起回流, 而回流一定会引起重绘

4. 重绘和回流(重排) 会导致回流的操作:

  1. 页面的首次刷新
  2. 浏览器的窗口大小发生改变
  3. 元素的大小或位置发生改变
  4. 改变字体的大小
  5. 内容的变化(如:input框的输入,图片的大小)
  6. 激活css伪类(如::hover)
  7. 脚本操作DOM(添加或者删除可见的DOM元素)
  8. 简单理解影响到布局了,就会有回流
1
2
3
4
5
6
let s = document.body.style
s.padding = '1px' // 重排 + 重绘
s.border = '1px solid red' // 重排 + 重绘
s.color = 'red' // 重绘
s.backgroundColor = '#000' // 重绘
s.fontSize = '14px' // 重排 + 重绘

5. 发布微博案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
let data = [
{ uname: '鲁班', imgSrc: './images/9.5/01.jpg' },
{ uname: '李白', imgSrc: './images/9.5/02.jpg' }
]
// 1. 检测用户输入字数
let text = document.querySelector('textarea')
let useCount = document.querySelector('.useCount')
let ul = document.querySelector('#list')
text.addEventListener('input', function () {
useCount.innerHTML = this.value.length
})

// 2. 发布内容不能为空
// 点击button后判断 内容为空 则提示不能输入为空 并直接return 不能为空
// 使用字符串.trim()去掉首尾空格
// 并将表单value值设置为空字符串 同时字数设置为0
let send = document.querySelector('#send')
send.addEventListener('click', function () {
if (text.value.trim() === '') {
text.value = ''
useCount.innerHTML = 0
return alert('内容不能为空')
}

// 4. 发布随机数
function fn(min, max) {
return Math.floor(Math.random() * (max - min + min) + min)
}
let fn1 = fn(0, data.length)

// 3. 新增留言
// 创建小li 通过innerHTML追加数据
// 随机获取数据数组内容 替换图片名字及留言内容
// 利用时间对象将时间动态化 new Date().toLocaleString()
let li = document.createElement('li')
li.innerHTML = `
<div class="info">
<img class="userpic" src=${data[fn1].imgSrc} />
<span class="username">${data[fn1].uname}</span>
<p class="send-time">发布于 ${new Date().toLocaleString()}</p>
</div>
<div class="content">${text.value}</div>
<span class="the_del">X</span>
`

// 5. 删除留言操作 放到追加的前面
// 在事件处理函数里获取点击按钮 注册点击事件
// 易错点: 必须在事件里获取 外面获取不到
// 放到追加ul的前面 创建元素同时顺便绑定了事件
let del = li.querySelector('.the_del')
del.addEventListener('click', function () {
ul.removeChild(li)
})

// 追加给ul 用父元素.insertBefore(子元素, 元素前面)
ul.insertBefore(li, ul.children[0])

// 6. 重置表单域内容为空
text.value = ''
useCount.innerHTML = 0
})

本节单词:

  1. parentNode
  2. children
  3. nextElementSibling
  4. previousElementSibling
  5. createElement
  6. appendChild
  7. insertBefore
  8. cloneNode
  9. removeChild
  10. new Date
  11. getTime
  12. now
  13. FullYear
  14. Month
  15. Day
  16. Hours
  17. Minutes
  18. Seconds
  19. trim
  20. toLocaleString