JS基础 - DOM(二)

193 阅读10分钟

动态修改样式

有时候我们会通过JavaScript来动态修改样式,这个时候我们有两个选择:

  • 在CSS中编写好对应的样式,动态的添加class
  • 动态的修改style属性
// 修改和设置内联style
const boxEl = document.getElementById('box')

const fontSize = '25px'

// 元素.style 获取到的是一个对象
// 可以通过该对象来修改和设置元素对应的内联样式

// 1. 使用style设置的样式需要一个个进行添加,所以如果需要设置的样式比较多的情况下,推荐动态修改class
// 2. 使用style设置的样式的属性值可以是变量或者某个可能会改变的值,这个时候就推荐操作style,因为class只能绑定样式,无法动态修改样式
boxEl.onclick = () => {
  boxEl.style.color = 'red'
  boxEl.style.backgroundColor = 'skyblue'
  // 这里font-size的值是一个变量,所以这里只能通过style的方式来进行设置
  boxEl.style.fontSize = fontSize
}
const boxEl = document.getElementById('box')

// class attribute对应的 property属性的名称为className
// 这是因为JavaScript早期是不允许使用class这种关键字来作为对象的属性,所以DOM规范使用了className
// 虽然现在已经没有这类限制,但是毕竟DOM规范使用了className,所以推荐使用className,而不是class

// boxEl.className = 'active' 这种方式设置的class会将之前设置的class给覆盖掉
boxEl.onclick = () => boxEl.className = 'active'

classList

根据之前的示例我们可以看到,对className进行赋值,它会替换整个类

如果我们需要添加或者移除单个的class,可以使用classList属性

classList是一个存储了元素样式的可迭代对象,可以使用for-of遍历

注意: 默认情况下,classList添加的都是类选择器,所以可以省略类选择器前边的点

方法说明
elem.classList.add (class)添加一个类
elem.classList.remove(class)移除类
elem.classList.toggle(class)如果类不存在就添加类,存在就移除它
elem.classList.contains(class)检查给定类是否存在,返回 true/false
// add方法在添加类的时候,会自动在类之前加上对应的空格
// 例如原本的类有title 即class=“title”
// add('active')后,会变为class=“title active”
boxEl.classList.add('active')

// 判断一个元素上是否存在某个类
// 参数为box 不是 .box
boxEl.classList.contains('box')
const boxEl = document.getElementById('box')

for (const styleName of boxEl.classList) {
  console.log(styleName)
  /*
    =>
      box
      active
  */
}

style

设置

如果需要单独修改某一个CSS属性,那么可以通过style来操作

对于多词(multi-word)属性,推荐使用驼峰式 camelCase替换多词写法

const boxEl = document.getElementById('box')

boxEl.style.color = 'red'
// 多值属性在设置的时候,可以使用多值写法,也可以使用小驼峰写法
// 推荐使用小驼峰写法
boxEl.style.fontSize = '30px'
boxEl.style['background-color'] = 'skyblue'
// 如果我们将值设置为空字符串,就相当于在内联样式中,移除了对应的样式设置
// 此时其就会使用对应class中设置的样式,如果都没有找到,那么就会使用浏览器默认样式
boxEl.style.display = ''
// 使用style来设置样式的时候,只能一个个进行设置
// 直接组合成一个对象后再进行赋值 是不起作用的,也就是无效的
boxEl.style = {
  color: red,
  fontSize: '30px',
  backgroundColor:  'skyblue'
}
const boxEl = document.getElementById('box')

// boxEl.style.color = 'red'
// boxEl.style.fontSize = '30px'
// boxEl.style['background-color'] = 'skyblue'


// 我们可以使用cssText属性来为元素同时设置多个style属性
// 但是它和classname一样,会覆盖原本设置的所有style样式,不推荐
boxEl.style.cssText = `
  font-size: 20px;
  color: red;
  background-color: skyblue
`

获取

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    .active {
      color: red;
      font-size: 24px;
    }
  </style>
</head>
<body>
  <div class="box active" id="box" style="background-color: skyblue;">
    Lorem ipsum dolor sit amet consectetur adipisicing elit. Ipsum, est!
  </div>

  <script src="./index.js"></script>
</body>
</html>
const boxEl = document.getElementById('box')

// 对于内联样式,是可以通过style.*的方式读取到的
//  --- 如果读取的元素不存在于style中,默认的返回值是空字符串
// 对于通过style或css文件设置到元素上的样式,是读取不到的
console.log(boxEl.style.backgroundColor) // => skyblue
console.log(boxEl.style.color) // => ''
console.log(boxEl.style.fontSize) // => ''

// 如果我们需要获取style或css文件中设置到元素上的样式,可以使用全局函数getComputedStyle
// getComputedStyle方法有两个参数,参数1为元素对象,参数2为对应元素的某个伪元素选择器
console.log(getComputedStyle(boxEl)) // => getComputedStyle方法的返回值是一个对象

// 获取到的是计算后的样式值,而不是设置值
// 如font-size是16px,设置的值为2em --- 得到的结果为 32px 而不是2em
console.log(getComputedStyle(boxEl).fontSize) // => 24px

元素操作

创建元素

我们可以使用document.write 方法写入一个元素

  • 这种方式写起来非常便捷,但是对于复杂的内容、元素关系拼接并不方便
  • 它是在早期没有DOM的时候使用的方案,目前依然被保留了下来,但是已经不常使用

目前我们最为常见的创建元素流程为:

  • 使用document.createElement(tag)创建一个元素

    或者使用document.createDocumentFragment来创建对应的代码片段

  • 将新建的元素或代码片段插入到DOM的某一个位置

const ulEl = document.getElementById('ul')

// documentFragment相对于dom中的一个临时子树,可以视为轻量级的document来进行使用
// 我们可以将多个元素先挂载到documentFragment下,然后在统一一起挂载到我们所需要挂载的那个元素下(在这里就是ulEl)
// 这样我们实际只在浏览器中操作了一次DOM,而其余元素的挂载是在内存中完成的,从而减少了页面的回流,提高了性能
const fragmentEl = document.createDocumentFragment()

for (let i = 1; i <= 3; i++) {
  const liEl = document.createElement('li')
  liEl.textContent = i
  fragmentEl.append(liEl)
}

ulEl.append(fragmentEl)

元素的CRUD

对元素的操作有新方法和旧方法,

对于旧方法,操作元素必须要找到其对应的父节点,并且一次只能操作一个元素

对于新方法,其参数是可变参数,也就是其可以插入任意个元素或字符串

这些字符串和元素在进行操作的时候,会被视为兄弟节点

方法说明对应的旧方法旧方法说明
node.append(...nodes or strings)在 node 末尾 插入节点或字符串parentElem.appendChild(node)在parentElem的父元素最后位置添加一个子元素
node.prepend(...nodes or strings)在 node 开头 插入节点或字符串parentElem.insertBefore(node, nextSibling)在parentElem的nextSibling前面插入一个子元素
node.before(...nodes or strings)在 node 前面 插入节点或字符串parentElem.insertBefore(node, nextSibling)
node.after(...nodes or strings)在 node 后面 插入节点或字符串parentElem.appendChild(node) 或 parentElem.insertBefore(node, nextSibling)
node.replaceWith(...nodes or strings)使用给定的节点或字符串替换 nodeparentElem.replaceChild(node, oldChild)在parentElem中,新元素替换之前的oldChild元素
node.remove()移除元素自身及对应子元素parentElem.removeChild(node)在parentElem中,移除某一个元素

image.png

插入元素和替换元素

const boxEl = document.querySelector('.box')
// 查找div.box 下的第一个span元素
const boxSpanEl = boxEl.querySelector('span')

const spanEl = document.createElement('p')
spanEl.textContent = 'hello world'

boxSpanEl.replaceWith(spanEl, 'lorem')

移除元素

移除元素我们可以调用元素本身的remove方法

const boxEl = document.querySelector('.box')

// 移除自身元素 以及 自身的所有子元素
boxEl.remove()

克隆元素

如果我们想要复制一个现有的元素,可以通过cloneNode方法

  • 可以传入一个Boolean类型的值,来决定是否是深度克隆
  • 深度克隆会克隆对应元素的子元素,否则不会
const boxEl = document.querySelector('.box')

const cloneEl = boxEl.cloneNode()
const deepCloneEl = boxEl.cloneNode(true)

console.log(cloneEl) // => <div class="box"></div>

console.log(deepCloneEl)
/*
  =>
    <div class="box">
      <h2 class="title">hello world</h2>
      <span>Lorem, ipsum.</span>
    </div>
*/

元素操作综合练习 --- 倒计时

const countDownEl = document.getElementById('countDown')

let hour = 1
let minute = 0
let second = 0

function calcTimerStr(str, length = 2, symbol = '0') {
  if (typeof str !== 'string') {
    str = str + ''
  }

  return str.padStart(length, symbol)
}

countDownEl.textContent = `${calcTimerStr(hour)} : ${calcTimerStr(minute)} : ${calcTimerStr(second)}`

const timer = setInterval(() => {
  second -= 1

  if (second < 0 && minute !== 0) {
    minute -= 1
    second = 59
  }

  if (second < 0 && minute < 0 && hour !== 0) {
    hour -= 1
    minute = 59
    second = 59
  }

  if (second === 0 && minute === 0 && hour === 0) {
    clearInterval(timer)
  }

  countDownEl.textContent = `${calcTimerStr(hour)} : ${calcTimerStr(minute)} : ${calcTimerStr(second)}`
}, 1000)

大小,位置和滚动

我们要获取元素的样式可以使用getComputedStyle方法,但是这个方法只能获取我们所设置的样式的计算值或默认的样式值

对于元素的某些属性,如整个页面的高度(包括溢出的部分),页面滑动了多少,使用getComputedStyle方法是无法获取的

为此JavaScript为我们提供了client系列offset系列scroll系列属性来帮助我们获取对应的值

元素的大小,位置,滚动

image.png

client系列

clientWidthclientHeight是不计算滚动条的 (在chrome中,垂直滚动条的宽度为15px)

属性说明
clientWidthcontentWith + padding * 2
clientHeightcontentHeight + padding * 2
clientTopborder-top的宽度
clientLeftborder-left的宽度

offset系列

属性说明
offsetWidth元素完整的宽度
offsetHeight元素完整的高度
offsetLeft当前元素相对于包含块的offsetX
offsetHeight当前元素相对于包含块的offsetY

scroll系列

属性说明
scrollWidth整个可滚动的区域宽度
scrollHeight整个可滚动的区域高度
scrollLeft向左卷曲出去的距离 + borderLeft
scrollTop向上卷曲出去的距离 + borderTop

无论是client系列,offset系列还是scroll系列,其获取的结果都是Number类型的值, 也就是说获取的结果的默认单位都是px

window的大小,位置

window即是浏览器中的全局对象(GO), 其在语义上其实代表着浏览器窗口

如果网页有滚动条,该滚动条属于window而不是html元素

属性说明
innerWidth、innerHeight获取window窗口的宽度和高度(包含滚动条)
outerWidth、outerHeight获取window窗口的整个宽度和高度(包括调试工具、工具栏)
documentElement.clientHeight、documentElement.clientWidth获取html的内容区域的高度或宽度
只包含content区域,不包含border和padding
documentElement.offsetHeight、documentElement.offsetWidth获取整个文档的高度或宽度
1. 整个文档的宽度或高度包括content,border和padding
2. 滚动条部分的宽度或高度不统计在内
screenX返回浏览器左边界到操作系统桌面左边界的水平距离
screenY返回浏览器顶部距离系统桌面顶部的垂直距离
scrollXX轴滚动的位置 (别名pageXOffset)
scrollYY轴滚动的位置 (别名pageYOffset)

scrollTo/scrollBy

scrollTo/scrollBy即可以运行在Element上,也可以运行在Window上

方法说明
scrollBy(x,y)将页面或元素中的内容滚动至 相对于当前位置的 (x, y) 位置
scrollTo(pageX,pageY)将页面或元素中的内容滚动至 (pageX, pageY)
scroll方法是scrollTo方法的别名
// scrollBy和scrollTo传递两个参数的时候,分别表示的是x轴的距离和y轴的距离
// 如果x和y超出了元素可以滚动的最大距离,那么元素只会滚动其能滚动的最大距离
window.scrollBy(0, 100)
divEl.scrollBy(0, 100)
// 其除了可以传入两个参数外,其还可以传递一个参数,即为配置对象
scrollBy({
    // x轴需要滚动的距离
    left: 0,
    // y轴需要滚动的距离
    top: 1000,
    // 设置滚动行为,默认情况下,会向锚点一样直接跳过去
  	// 设置了behavior为smooth后,浏览器会有一个比较平滑的过渡动画效果
    behavior: 'smooth'
  })

Element.getBoundingClientRect()

Element.getBoundingClientRect() 方法返回元素的大小及其相对于视口的位置,也就是返回"元素边界矩形"对应的值

使用该方法计算的时候,浏览器需要对页面进行重绘,以便于获取最新的结果,所以该方法是比较损耗性能的

getBoundingClientReact方法的返回结果对象存在如下属性

属性
left元素左上角距离视口左边的距离
top元素左上角距离视口顶部的距离
right元素右下角距离视口左边的距离
bottom元素右下角距离视口顶部的距离
width元素的宽度
对于contentBox -> width + padding + border
对于borderBox -> width
height元素的高度
对于contentBox -> height + padding + border
对于borderBox -> height
x元素左上角距离视口左边的距离
不是标准中的属性,浏览器可能不支持,不推荐使用
y元素左上角距离视口顶部的距离
不是标准中的属性,浏览器可能不支持,不推荐使用