内容回顾
当一切看起来都不起作用的时候, 我就会像个石匠一样去敲打石头.
可能敲100次, 石头不会有任何反应.
但是101次, 石头可能就会裂为两半, 我知道并不是第101次起了作用, 而是前面积累所致.
士兵突击 - 许三多
一. 元素操作
1.1. 创建/插入/移除/克隆
- document.createElement()
- append
- prepend
- before
- after
- repaceWith
- remove
- cloneNode
-
前面我们使用过 document.write 方法写入一个元素:
-
这种方式写起来非常便捷,但是对于复杂的内容、元素关系拼接并不方便;
-
它是在早期没有DOM的时候使用的方案,目前依然被保留了下来;
-
-
那么目前我们想要插入一个元素,通常会按照如下步骤:
-
步骤一:创建一个元素;
-
步骤二:插入元素到DOM的某一个位置;
-
-
创建元素: document.createElement(tag)
// 真实创建一个DOM
var h2El = document.createElement("h2")
h2El.className = "tiile"
h2El.classList.add("active")
h2El.textContent = "我是标题"
-
插入元素的方式如下:
-
node.append(…nodes or strings) —— 在 node 末尾 插入节点或字符串,
-
node.prepend(…nodes or strings) —— 在 node 开头 插入节点或字符串,
-
node.before(…nodes or strings) —— 在 node 前面 插入节点或字符串,
-
node.after(…nodes or strings) —— 在 node 后面 插入节点或字符串,
-
node.replaceWith(…nodes or strings) —— 将 node 替换为给定的节点或字符串
-
// 真实创建一个DOM
var h2El = document.createElement("h2")
h2El.className = "tiile"
h2El.classList.add("active")
h2El.textContent = "我是标题"
// 将元素插入到boxEl里面
// append追加 添加(添加到最后面)
boxEl.append(h2El)
// 添加到前面
boxEl.prepend(h2El)
// 在box前面添加 兄弟元素
boxEl.before(h2El)
// 在box后面添加 兄弟元素
boxEl.after(h2El)
// 替换 box就消失了
boxEl.replaceWith(h2El, "abc")
// 插入到span和p元素之间
var spanEl = document.querySelector("span")
// 方法一: 节点
var spanEl = boxEl.children[0]
// 方法二: 加上标识
var spanEl = document.querySelector(".box-first")
// 方法三
var spanEl = boxEl.querySelector("span")
spanEl.after(h2El)
-
移除元素我们可以调用元素本身的remove方法:
-
如果我们想要复制一个现有的元素,可以通过cloneNode方法:
-
可以传入一个Boolean类型的值,来决定是否是深度克隆;
-
深度克隆会克隆对应元素的子元素,否则不会;
-
<button class="clone-btn">复制</button>
<button class="remove-btn">移除box</button>
<div class="box">
<h2>我是标题</h2>
<p>我是文本, 哈哈哈哈</p>
</div>
// 1. 获取元素
var boxEl = document.querySelector(".box")
var removeBtnEl = document.querySelector(".remove-btn")
var cloneBtnEl = document.querySelector(".clone-btn")
// 2. 监听btn
removeBtnEl.onclick = function() {
boxEl.remove()
}
// 复制box
var counter = 0
cloneBtnEl.onclick = function() {
var newNode = boxEl.cloneNode(true)
newNode.children[0].textContent = "我也是标题" + counter
// console.log(newNode)
// boxEl.after(newNode)
document.body.append(newNode)
counter++
}
-
在很多地方我们也会看到一些旧的操作方法:
-
parentElem.appendChild(node):
-
在parentElem的父元素最后位置添加一个子元素
-
parentElem.insertBefore(node, nextSibling):
-
在parentElem的nextSibling前面插入一个子元素;
-
-
parentElem.replaceChild(node, oldChild):
- 在parentElem中,新元素替换之前的oldChild元素;
-
parentElem.removeChild(node)****:
- 在parentElem中,移除某一个元素;
-
1.2. 元素大小/位置/滚动
-
clientWidth: cotentWidth + padding(不包含滚动条)
-
clientHeight: contentHeight + padding (不包含滚动条)
// 几乎用不到
-
clientTop: border-top的宽度
-
clientLeft: border-left的宽度
//
-
offsetWidth: 元素完整的宽度(包含border padding)
-
offsetHeight: 元素完整的高度
-
offsetLeft: 距离父元素的x
-
offsetTop: 距离父元素的y
-
scrollHeight: 整个可滚动的区域高度
-
scrollTop: 滚动部分高度
var boxEl = document.querySelector(".box")
// 1. 获取样式(局限性很强)
// var boxStyle = getComputedStyle(boxEl)
// console.log(boxStyle.width, boxStyle.height)
// 2. 获取更多信息
console.log(boxEl.clientWidth)
console.log(boxEl.clientHeight)
console.log(boxEl.clientTop)
console.log(boxEl.clientLeft)
console.log(boxEl.offsetWidth)
console.log(boxEl.offsetLeft)
console.log(boxEl.offsetTop)
console.log(boxEl.scrollHeight)
console.log(boxEl.scrollTop)
// window
window.onclick = function () {
console.log(boxEl.scrollTop)
}
1.3. window大小/滚动
- inner/outer
- html
- scrollX
- scrollY
- scrollBy(x, y)
- scrollTo(x, y)
-
window的width和height
- innerWidth、innerHeight:获取window窗口的宽度和高度(包含滚动条)
- outerWidth、outerHeight:获取window窗口的整个宽度和高度(包括调试工具、工具栏)
- documentElement.clientHeight、documentElement.clientWidth:获取html的宽度和高度(不包含滚动条)
-
window的滚动位置:
-
scrollX:X轴滚动的位置(别名pageXOffset)
-
scrollY:Y轴滚动的位置(别名pageYOffset)
-
-
也有提供对应的滚动方法:
-
方法 scrollBy(x,y) :将页面滚动至 相对于当前位置的 (x, y) 位置;
-
方法 scrollTo(pageX,pageY) 将页面滚动至 绝对坐标;
-
// window大小 console.log(window.outerWidth) console.log(window.outerHeight) console.log(window.innerWidth) console.log(window.innerHeight) console.log(document.documentElement.clientWidth) console.log(document.documentElement.clientHeight) console.log(document.documentElement.offsetWidth) console.log(document.documentElement.offsetHeight) // 获取window的滚动区域 window.onclick = function () { console.log(window.scrollX) console.log(window.scrollY) } // window.onscroll = function() { // console.log(window.scrollY) // } window.onscroll = function () { var scrollY = window.scrollY if (scrollY > 600) { // scrollBtnEl.style.display = "block" scrollBtnEl.hidden = false } else { // scrollBtnEl.style.display = "none" scrollBtnEl.hidden = true } } // 点击按钮滚动某个位置 var scrollBtnEl = document.querySelector(".scroll-btn") scrollBtnEl.hidden = true scrollBtnEl.onclick = function () { // 相对于原来的基础上增加100 // window.scrollBy(0, 100) // 绝对值 定位到300, 每次都定位到300 window.scrollTo(0, 0) }
-
1.4. 案例练习
1.4.1. 动态创建列表 prompt
<h1>动态创建列表</h1>
<ul class="list"></ul>
var ulEl = document.querySelector(".list")
var isFlag = true
while (isFlag) {
var message = prompt("请输入信息")
if (!message) { // 没有输入内容
isFlag = false
} else {
var liEl = document.createElement("li")
liEl.textContent = message
ulEl.append(liEl)
}
}
1.4.2. 动态展示当前时间
<h1 class="time">2022-05-19 11:14:30</h1>
// 封装工具函数
function padLeft(content, count, padStr) {
count = count || 2
padStr = padStr || "0"
content = String(content)
return content.padStart(count, padStr)
}
// 1. 获取时间元素
var timeEl = document.querySelector(".time")
// 2. 获取具体的时间并且格式化
setInterval(function () {
var date = new Date()
var year = date.getFullYear()
var month = padLeft(date.getMonth() + 1)
var day = padLeft(date.getDate())
var hour = padLeft(date.getHours())
var minute = padLeft(date.getMinutes())
var second = padLeft(date.getSeconds())
// 3. 将时间放到timeEl里面
timeEl.textContent = `${year}-${month}-${day} ${hour}:${minute}:${second}`
}, 1000);
// 补充string方法 很新的方法
// var str = "4"
// console.log(str.padStart(2, "0"))
1.4.3. 考拉倒计时展示
- 获取当前时间
- 获取今天的24点
- 计算毫秒时间差
- 转小时
- 转分钟
- 转秒钟
- 计时器中
.countdown {
/* display: flex; */
color: #f00;
font-size: 20px;
}
.countdown .time {
background-color: #f00;
color: #fff;
display: inline-block;
padding: 5px;
border-radius: 3px;
}
<div class="countdown">
<span class="time hour">03</span>
<span class="split">:</span>
<span class="time minute">25</span>
<span class="split">:</span>
<span class="time second">45</span>
</div>
封装的js
// 封装工具函数
function formatPadLeft(content, count, padStr) {
count = count || 2
padStr = padStr || "0"
content = String(content)
return content.padStart(count, padStr)
}
正式的js代码
// 1. 获取元素
var hourEl = document.querySelector(".hour")
var minutEl = document.querySelector(".minute")
var secondEl = document.querySelector(".second")
// 获取倒计时小时-分钟-秒钟
// 11:53 => 24:00:00
var endDate = new Date()
endDate.setHours(24)
endDate.setMinutes(0)
endDate.setSeconds(0)
endDate.setMilliseconds(0)
setInterval(function () {
var newDate = new Date()
var intervalTime = Math.floor((endDate.getTime() - newDate.getTime()) / 1000)
// console.log(intervalTime)
// 55089596: x小时x分钟x秒钟
// 125: x百x十x个
// var num = 125
var hour = Math.floor(intervalTime / 3600)
var minute = Math.floor(intervalTime / 60) % 60
var second = intervalTime % 60
// 2. 设置内容
hourEl.textContent = formatPadLeft(hour)
minutEl.textContent = formatPadLeft(minute)
secondEl.textContent = formatPadLeft(second)
}, 1000)
二. 事件处理
2.1. 事件处理方案
-
如何进行事件监听呢?
-
事件监听方法一: 在script中直接监听 (很少用)
-
事件监听方法二: DOM属性, 通过元素on来监听事件
-
事件监听方法三:通过EventTarget中的addEventLisstener监听
-
-
// 1. 获取元素对象 var btn2El = document.querySelector(".btn2") var btn3El = document.querySelector(".btn3") // 2. onclick属性 // function handleClick01() { // console.log("按钮2发生了点击") // } // // function handleClick02() { // console.log("按钮2发生了点击") // } // btn2El.onclick = function() { // console.log("按钮2发生了点击") // } // btn2El.onclick = handleClick01 // btn2El.onclick = handleClick02 // // var obj = { // name: "why" // } // obj.name = "kobe" // 3. addEventListener(推荐) btn3El.addEventListener("click", function() { console.log("第一个btn3的事件监听") }) btn3El.addEventListener("click", function() { console.log("第二个btn3的事件监听") }) btn3El.addEventListener("click", function() { console.log("第三个btn3的事件监听") })
2.2. 事件捕获冒泡
-
事件流的不同传播
- 捕获
- 冒泡
-
addEventListener(“click”, fn, true)
- 事实上对于事件有一个概念叫做事件流, 为什么会产生事件流呢?
-
我们可以想到一个问题: 当我们浏览器上对着一个元素点击时, 你点击的不仅仅是这个元素本身
- 这是因为我们HTML元素是存在父子元素叠加层级的
- 比如一个span元素放在div元素上, div元素是放在body元素上的, body元素是放在html元素上的
- 我们会发现默认情况下事件是从内层的span向外依次传递的顺序,这个顺序我们称为事件冒泡(Event Bubble)
- 事实上, 还有另外一种监听事件流的方式就是从外层到内层, 这种称之为事件捕获(Event Canture)
- 为什么会产生两种不同的处理流呢?
- 这是因为早期浏览器开发时, 不管ie还是Netscape公司都发现了这个问题
- 但是他们采用了完全相反的事件流来对事件进行传递
- ie采用了事件冒泡的方式吗Netscape采用了事件捕获的方式
-
如果我们都监听, 那么会按照如下的顺序来执行
-
捕获阶段(Capturing phase)
-
事件 (从Window)向下走近元素
-
目标阶段(Target phase)
-
事件到达目标元素
-
冒泡阶段(Bubbling phase)
-
事件从元素上开始冒泡
-
事实上, 我们可以通过event对象来获取当前阶段
-
eventPhase
-
开发中通常会使用事件冒泡, 所以事件捕获了解即可
-
2.3. 事件对象event
- 两个方法:
- preventDefault: 阻止默认行为
- stopPropagation: 阻止事件传递
-
type: 事件的类型
-
target: 当前事件发生的元素 (常用)
-
currentTarget: 当前处理事件的元素 (常用)
-
eventPhase: 事件所处的阶段
-
offsetX, offsetY: 事件发生在元素内的位置
-
clientX, clientY: 事件发生在客户端内的位置
-
pageX, pageY: 事件发生在客户端相对于document的位置
-
screenX, screenY: 事件发生相对于屏幕的位置
var divEl = document.querySelector("div")
var btnEl = document.querySelector(".btn")
// btnEl.onclick = function(e) {
// console.log("按钮发生了点击", e)
// }
// divEl.onclick = function(e) {
// console.log("div发生了点击", e)
// }
divEl.onclick = function(event) {
// 偶尔会使用
// console.log("事件类型", event.type)
// console.log("事件阶段", event.eventPhase)
// 比较少使用
// console.log("事件元素中的位置", event.offsetX, event.offsetY)
// console.log("事件客户端中的位置", event.clientX, event.clientY)
// console.log("事件页面中的位置", event.pageX, event.pageY)
// console.log("事件相对于屏幕的位置", event.screenX, event.screenY)
// target, currentTarget
console.log(event.target)
console.log(event.currentTarget)
console.log(event.target === event.currentTarget)
}
// 阻止默认行为
// var aEl = document.querySelector("a")
// aEl.onclick = function(event) {
// // alert("a元素发生了点击")
// console.log("a元素发生了点击")
// // 阻止默认行为 a就是跳转
// event.preventDefault()
// }
// 阻止事件进一步传递
var btnEl = document.querySelector("button")
var spanEl = document.querySelector("span")
var divEl = document.querySelector(".box")
// div的事件捕获
divEl.addEventListener("click", function(e) {
console.log("div的事件捕获监听")
// e.stopPropagation()
}, true)
spanEl.addEventListener("click", function() {
console.log("span的事件捕获监听")
}, true)
btnEl.addEventListener("click", function(e) {
console.log("btn的事件捕获监听")
e.stopPropagation()
}, true)
divEl.addEventListener("click", function () {
console.log("div的事件冒泡监听")
})
spanEl.addEventListener("click", function () {
console.log("span的事件冒泡监听")
})
btnEl.addEventListener("click", function () {
console.log("btn的事件冒泡监听")
})
2.4. 事件函数中的this
- 处理的元素
-
在函数中, 我们也可以通过this来获取当前的发生元素
-
这是因为浏览器内部, 调用event.handler是绑定到当前的currentTarget上的
var divEl = document.querySelector("div")
var btnEl = document.querySelector("button")
divEl.onclick = function(event) {
console.log(this)
console.log(event.currentTarget)
console.log(divEl)
console.log(this === divEl) // true
}
// divEl.addEventListener("click", function() {
// console.log(this)
// })
2.5. EventTarget的使用
- addEventListener
- removeEventListener
- dispatchEvent
-
我们会发现,所有的节点、元素都继承自EventTarget
- 事实上Window也继承自EventTarget;
-
那么这个EventTarget是什么呢?
- EventTarget是一个DOM接口,主要用于添加、删除、派发Event事件;
-
EventTarget常见的方法:
-
addEventListener:注册某个事件类型以及事件处理函数;
-
removeEventListener:移除某个事件类型以及事件处理函数;
-
dispatchEvent:派发某个事件类型到EventTarget上;
-
var btnEl = document.querySelector("button")
// var foo = function() {
// console.log("监听到按钮的点击")
// }
// btnEl.addEventListener("click", foo)
// 需求: 过5s秒后, 将这个事件监听移除掉
// setTimeout(function() {
// btnEl.removeEventListener("click", foo)
// }, 5000)
// 无法移除的
btnEl.addEventListener("click", function() {
console.log("监听的事件")
})
setTimeout(function() {
btnEl.removeEventListener("click", function() {
})
}, 5000)
// eventtarget就可以实现类似于事件总线
window.addEventListener("coderwhy", function() {
console.log("监听到coderwhy事件")
})
// 5秒后派发coderwhy事件
setTimeout(function() {
window.dispatchEvent(new Event("coderwhy"))
}, 5000)
2.6. 事件委托(delegation)
- 案例一: ul中li点击active
- 案例二: 排他的思想
- 案例三: 多个按钮的区分
- data-*
-
事件冒泡在某种情况下可以帮助我们实现强大的事件处理模式 – 事件委托模式**(也是一种设计模式)** 那么这个模式是怎么样的呢?
-
因为当子元素被点击时,父元素可以通过冒泡可以监听到子元素的点击;
-
并且可以通过event.target获取到当前监听的元素;
-
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
<li>6</li>
<li>7</li>
<li>8</li>
<li>9</li>
<li>10</li>
</ul>
var liEls = document.querySelectorAll('li');
// for(var i = 0; i < liEls.length; i++) {
// }
// 1. 每一个函数都监听自己的点击, 并且有自己的处理函数 (自己的函数)
// for (var liEl of liEls) {
// liEl.onclick = function(event) {
// // 方法一
// // this.classList.add("active")
// // 方法二
// event.currentTarget.classList.add("active")
// }
// }
// 2. 同一在ul中监听
// var ulEl = document.querySelector("ul")
// ulEl.onclick = function(event) {
// // console.log("点击了某一个li", e.target)
// event.target.classList.add("active")
// }
// 3. 新需求: 点击li变成active, 其他的取消active
// var ulEl = document.querySelector("ul")
// ulEl.onclick = function (event) {
// // 将之前的active移除掉
// for (var i = 0; i < ulEl.children.length; i++) {
// var liEl = ulEl.children[i]
// if (liEl.classList.contains("active")) {
// ulEl.children[i].classList.remove("active")
// }
// }
// // 给点击元素添加active
// event.target.classList.add("active")
// }
// var ulEl = document.querySelector("ul")
// ulEl.onclick = function (event) {
// var activeLiEl = ulEl.querySelector(".active")
// // 找到active的li移除掉
// // activeLiEl && activeLiEl.classList.remove("active") // 或者用if
// if (activeLiEl) {
// activeLiEl.classList.remove("active")
// }
// event.target.classList.add("active")
// }
// 另一个方法
var ulEl = document.querySelector('ul');
var activeLiEl = null;
ulEl.onclick = function (event) {
// 1. 变量记录的方式
if (activeLiEl && event.target !== ulEl) {
activeLiEl.classList.remove('active');
}
// 2. 点击的元素添加active
if (event.target !== ulEl) {
event.target.classList.add('active');
}
// 3. 记录最新的active对应的li
activeLiEl = event.target;
};
<div class="box">
<!-- 因为不加上data的话是非标准拿出来麻烦所以加上data -->
<button data-action="remove">移除</button>
<button data-action="new">新建</button>
<button data-action="search">搜索</button>
<button>1111</button>
</div>
var boxEl = document.querySelector(".box")
boxEl.onclick = function(event) {
var btnEl = event.target
var action = btnEl.dataset.action
switch(action) {
case "remove":
console.log("点击了移除按钮")
break
case "new":
console.log("点击了新建按钮")
break
case "search":
console.log("点击了搜索按钮")
break
default:
console.log("点击了其他")
}
}