基础概念
术语
- 事件:发生一件事
- 事件类型:发生什么事情;点击、鼠标按下、鼠标抬起、鼠标移入、鼠标移出、键盘按下、键盘抬起…
- 事件处理程序:一个函数,当某件事情发生时运行。
- 事件注册:将一个事件处理程序,挂载到某个事件上。
事件流
事件流:当某个事件发生的时候,哪些元素会监听到该事件发生,这些元素发生该事件的顺序。
当一个元素发生了某个事件时,那该元素的所有祖先元素都发生了该事件
事件冒泡: 先触发最里层的元素,然后再依次触发外层元素
事件捕获: 先触发外层的元素,然后再依次触发里面元素
目前,标准规定,默认情况下,事件是冒泡的方式触发。
事件源(事件目标): 事件目标阶段的元素
test.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>事件冒泡</title>
<style>
div {
width: 100px;
height: 100px;
background: red;
position: relative;
}
button {
position: absolute;
right: -200px;
}
</style>
</head>
<body>
<div>
<button>
点击我
</button>
</div>
<p></p>
<script>
// 点击button后的触发顺序
document.documentElement.onclick = function () {
console.log("html") // ==> 4
}
document.onclick = function () {
console.log("document") // ==> 5
}
document.querySelector("div").onclick = function () {
console.log("div") // ==> 2
}
document.body.onclick = function () {
console.log("body") // ==> 3
}
document.querySelector("button").onclick = function () {
console.log("button") // ==> 1
}
document.querySelector("p").onclick = function () {
console.log("p") // ==> 不会触发
}
// 注意:
// 1 ==> 只要button被点击 就会向上层冒泡 上层的元素都能监听到 比如 body html 都能监听到点击事件, 这和它们是否注册了点击事件无关;
// 2 ==> 执行顺序看的是结构上的关系 而不是 视觉效果上的关系;
</script>
</body>
</html>
事件注册
也叫事件绑定
dom0
将事件名称(事件类型)前面加上on,作为dom的属性名,给该属性赋值为一个函数,即为事件注册。
事件的移除: 重新给事件属性赋值,通常赋值为null
和undefined
dom2
dom对象.addEventListener:注册事件
与dom0的区别
- dom2可以为某个元素的同一个事件,添加多个处理程序,按照注册的先后顺序运行
- dom2允许开发者控制事件处理的阶段,使用第三个参数,表示是否在捕获阶段触发
- 如果元素是目标元素(事件源),第三个参数无效
事件的移除: dom对象.removeEventListener(事件名, 处理函数);
dom2中如果要移除事件,不能使用匿名函数 (因为使用匿名函数就好比重新创建了一个函数(匿名函数无法获得它的引用) 所以需要将函数名传入)
细节:
- dom2在IE8及以下不兼容,需要使用attachEvent,detachEvent添加和移除事件
- 添加和移除事件时,可以将第三个参数写为一个对象,进行相关配置
<!-- 事件注册 ==> dom0 ==> dom.onclick -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>事件注册</title>
</head>
<body>
<button id="btn1">点击1</button>
<script>
// 事件注册 (事件绑定)
// 将事件名称(事件类型)前面加上on, 作为dom的属性名, 给该属性赋一个函数即为事件注册(事件绑定)
var btn = document.getElementById("btn1");
btn.onclick = function () {
console.log("1");
}
// 事件移除 ==> 重新给事件属性赋值 通常赋 undefined 或 null
// btn.onclick = null; // 啥都不写 直接在控制台输出 btn.onclick ==> null
</script>
</body>
</html>
<!-- 事件注册 ==> dom0 ==> 在dom元素身上直接写 onclick="js代码" 1 -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>事件注册</title>
</head>
<body>
<button id="btn1" onclick="console.log('1')">点击1</button>
<script>
var btn = document.getElementById("btn1");
// 直接在 对应的元素身上写 事件处理程序也okk ==> but 这种方式并不好 这会将 html 和 js 代码混合在一起 不利于后期的维护工作
// 不过这种直接写在元素身上的 事件处理程序 也可通过给其重新赋值来移除事件
// 事件移除 ==> 重新给事件属性赋值 通常赋 undefined 或 null
btn.onclick = null;
</script>
</body>
</html>
<!-- 事件注册 ==> dom0 ==> 在dom元素身上直接写 onclick="js代码" 2 -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>事件注册</title>
</head>
<body>
<button id="btn1" onclick="test()">点击1</button>
<script>
var btn = document.getElementById("btn1");
test = function () {
console.log('1')
}
// 事件移除
// btn.onclick = null;
</script>
</body>
</html>
<!-- 事件注册 ==> dom2 ==> dom对象.addEventListener:注册事件
1. dom2可以为某个元素的同一个事件,添加多个处理程序,按照注册的先后顺序运行 -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>dom2 和 dom0 在事件注册方面的区别1</title>
</head>
<body>
<button id="btn1">点击1</button>
<script>
// dom2可以为某个元素的同一个事件,添加多个处理程序,按照注册的先后顺序运行(注意 这是相对于同一个 元素 的 同一个事件类型 而言的)
var btn = document.getElementById("btn1");
btn.addEventListener("click", function () {
console.log("1");
})
btn.addEventListener("click", function () {
console.log("2");
})
// 点击 #btn1 后 ==> 1 换行 2
</script>
</body>
</html>
<!-- 事件注册 ==> dom2 ==> dom对象.addEventListener:注册事件
2. dom2允许开发者控制事件处理的阶段,使用第三个参数,表示是否在捕获阶段触发
- 如果元素是目标元素(事件源),第三个参数无效 -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>dom2 和 dom0 在事件注册方面的区别2</title>
<style>
div {
background: lightblue;
}
</style>
</head>
<body>
<div>
<button id="btn1">点击1</button>
</div>
<script>
var btn = document.getElementById("btn1");
// dom2允许开发者控制事件处理的阶段,使用第三个参数,表示是(true)否(false)在捕获阶段触发 (默认 false)
btn.addEventListener("click", function () {
console.log("btn1");
}, false);
btn.addEventListener("click", function () {
console.log("btn2");
}, true);
var div = document.querySelector("div")
div.addEventListener("click", function () {
console.log("div 冒泡");
}, false);
div.addEventListener("click", function () {
console.log("div 捕获");
}, true);
/* 点击 btn 后 输出内容如下 (这一部分的知识点 很容易出面试题)
div 捕获
btn1
btn2
div 冒泡 */
// 解释 ==> 实际上 此时 在给btn添加事件时 传入的第三个参数是没啥用的
// 因为 此时点击的元素 就是 btn 所以 btn 是作为事件目标阶段的元素 一定会触发 无论是事件捕获阶段还是事件冒泡阶段
// 而且触发btn后 btn1 和 btn2 输出的顺序 是由绑定这两个事件处理函数的顺序来决定的
// 至于 在给div添加事件时所传入的第三个参数 将会影响其触发阶段 true ==> 事件捕获时就触发(在事件目标阶段之前) false ==> 事件冒泡时才触发(在事件目标阶段之后)
// 它们触发的顺序将不在由注册的先后顺序来决定
</script>
</body>
</html>
<!-- 事件注册 ==> dom2 ==> 事件的移除: dom对象.removeEventListener(事件名, 处理函数);
细节1 ==> dom2中如果要移除事件,不能使用匿名函数 (因为使用匿名函数就好比重新创建了一个函数(匿名函数无法获得它的引用) 所以需要将函数名传入) -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>dom对象.removeEventListener(事件名, 处理函数);</title>
<style>
div {
background: lightblue;
}
</style>
</head>
<body>
<div>
<button id="btn1">点击1</button>
<button id="btnremove">移除按钮1的事件</button>
</div>
<script>
var btn = document.getElementById("btn1");
var btnRemove = document.getElementById("btnremove");
// 正确写法
function handler1() {
console.log("btn1");
}
function handler2() {
console.log("btn2");
}
btn.addEventListener("click", handler1, {
once: true // 是否只触发一次
});
btn.addEventListener("click", handler2);
// dom对象.removeEventListener(事件名, 处理函数);
btnRemove.addEventListener("click", function () {
btn.removeEventListener("click", handler2);
btn.removeEventListener("click", handler1);
})
// 错误写法
/* btn.addEventListener("click", function () {
console.log("btn1")
})
btnRemove.addEventListener("click", function () {
btn.removeEventListener("click", function () { // 这个函数和添加到btn上的函数 指向的空间不同 这里相当于是重新在内存中开辟了一块新的空间 所以需要通过引用来传递
console.log("btn1")
})
}) */
</script>
</body>
</html>
<!-- 事件注册 ==> dom2 ==> 事件的移除: dom对象.removeEventListener(事件名, 处理函数);
细节2 ==> 添加和移除事件时,可以将第三个参数写为一个对象,进行相关配置 -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>重点 ==> 移除捕获阶段的事件 需要传入第三个参数 true (默认情况下 第三个参数的值是false)</title>
<style>
div {
background: lightblue;
}
</style>
</head>
<body>
<div>
<button id="btn1">点击1</button>
<button id="btnremove">移除按钮1的事件</button>
</div>
<script>
var btn = document.getElementById("btn1");
var btnRemove = document.getElementById("btnremove");
var div = document.querySelector("div");
function handler1() {
console.log("btn1");
}
function handler2() {
console.log("btn2");
}
btn.addEventListener("click", handler1, {
once: true // 是否只触发一次
});
btn.addEventListener("click", handler2);
function divHandler1() {
console.log("div 捕获");
}
function divHandler2() {
console.log("div 冒泡");
}
div.addEventListener("click", divHandler1, {
capture: true // 表示是否在捕获阶段触发
})
div.addEventListener("click", divHandler2) // 默认 capture 为 false
btnRemove.addEventListener("click", function () {
btn.removeEventListener("click", handler2);
btn.removeEventListener("click", handler1);
div.removeEventListener("click", divHandler1, true); // 不写第三个参数(即第三个参数取默认值 false) 默认移除的是冒泡阶段的事件
// 传入第三个参数 ==> 可以移除捕获阶段的事件
// 测试 ==> 先点击button ==> "移除按钮1的事件" ==> 然后点击button ==> "点击1" ==> 只会触发冒泡事件 divHandler2
// 然后将第三个参数 true 去掉 再试试 ==> 会发现 divHandler1 和 divHandler2 都会触发 ==> 并没有移除
// [补充]
// 写法 1 ==> div.removeEventListener("click", divHandler1, true);
// 写法 2 ==> div.removeEventListener("click", divHandler1, {
// capture: true
// });
// 这两种写法都是等效的
})
</script>
</body>
</html>
事件对象
事件对象封装了事件的相关信息
获取事件对象
黄色标识出来的是非常重要的知识点
- 通过事件处理函数的参数获取
- 旧版本的IE浏览器通过window.event获取
e = e || window.event; // [兼容处理]
事件对象的通用成员
- target & srcElement
事件目标(事件源对象)
var source = e.target || e.srcElement; // [兼容处理]
事件委托: 通过给祖先元素注册事件,在处理程序中判断事件源进行不同的处理。就是把子元素的事件委托给父元素处理
通常,事件委托用于动态生成元素的区域。这句话 貌似蛮重要的, 目前的理解就是说, 额... 就拿TODOList来说吧, 如果在按钮身上注册事件, 那么当用户 新增 item 时 我们还需要给用户新增的 item 里面的按钮注册事件 但是如果我们将事件注册在祖先元素上 而不是 按钮上, 那么
- currentTarget
当前目标:获取绑定事件的元素,等效于this
- type
字符串,得到事件的类型
- preventDefault & returnValue
preventDefault方法
阻止浏览器默认行为。
注意 ==> returnValue 是IE中的阻止默认行为的写法 比如: 给默认带有默认行为的元素的事件处理程序中的事件对象内部的returnValue属性赋值为false ==>
e.returnValue = false;
dom0的方式:在事件处理程序中返回false
也可以直接在 dom 元素身上直接
onclick = "return false"
比如:<button type="submit" onclick="return false;">提交</button>
针对a元素,可以设置为功能性链接解决跳转问题
<a href="javascript:;">啥也不做</a>
<a href="javascript:console.log('abc');">输出abc</a>
<a href="javascript:void 0;">void 0</a>
<a href="javascript:void(0);">void(0)</a>
[补充] ==> void 是一个运算符 返回 undefined
以上的几种写法都是以后可能会见到的一些写法
需要知道这些写法的作用都是为了阻止a元素的默认行为即可
- stopPropagation方法
阻止事件冒泡
- eventPhase
得到事件所处的阶段
根据不同的返回值 得知事件所处的不同阶段
1 ==> 事件捕获
2 ==> 事件目标
3 ==> 事件冒泡
test.html
<!-- 获取事件对象 并 了解事件对象的通用成员
1. 获取事件对象
- 通过事件处理函数的参数获取
- 旧版本的IE浏览器通过window.event获取
2. 事件对象的通用成员(获取目标对象)
- target ==> 事件目标(事件源对象)
- srcElement ==> 事件目标(事件源对象) ==> (旧版本的IE浏览器) -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>获取事件对象 并 了解事件对象的通用成员</title>
<style>
div {
background: lightblue;
}
</style>
</head>
<body>
<div>
<button>Lorem.</button>
<button>Repudiandae.</button>
<button>Consequatur!</button>
<button>Vero.</button>
<button>Porro.</button>
</div>
<script>
var div = document.querySelector("div")
// 事件对象封装了事件的相关信息
div.onclick = function (e) { // 参数 e 不用咋们传 浏览器会自动将这个事件对象传入
// e = e || window.event; // [兼容处理] 补充 ==> 兼容处理这一部分不需要掌握 后期使用 框架时 兼容性基本是不需要考虑的问题
// var source = e.target || e.srcElement; // [兼容处理]
console.log(e.target);
}
</script>
</body>
</html>
<!-- 事件委托 ==> 通过给祖先元素注册事件,在程序处理程序中判断事件源进行不同的处理。
通常 ==> 事件委托用于动态生成元素的区域。(重点 需要理解) -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>事件委托</title>
<style>
div {
background: lightblue;
}
</style>
</head>
<body>
<div>
<p><span>Lorem ipsum dolor sit amet.</span><button>删除</button></p>
<p><span>Architecto adipisci provident omnis placeat!</span><button>删除</button></p>
<p><span>Impedit, optio labore. Alias, quaerat!</span><button>删除</button></p>
<p><span>Quo molestias tenetur unde excepturi.</span><button>删除</button></p>
<p><span>Labore tempore inventore at aspernatur?</span><button>删除</button></p>
<p><span>Repellendus sapiente veniam doloremque adipisci!</span><button>删除</button></p>
<p><span>Vitae animi error hic asperiores!</span><button>删除</button></p>
<p><span>Veritatis placeat quibusdam voluptas natus!</span><button>删除</button></p>
<p><span>Itaque cumque ad culpa porro?</span><button>删除</button></p>
<p><span>Quas, rerum. Unde, odio dolore!</span><button>删除</button></p>
</div>
<script>
// p*10>(span>lorem5)+button{删除}
var div = document.querySelector("div")
div.onclick = function (e) {
if (e.target.tagName === "BUTTON") {
e.target.parentElement.remove();
// button 的 parentElement 是 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">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>事件委托的注意事项</title>
<style>
div {
background: lightblue;
}
</style>
</head>
<body>
<div>
<p><span>Lorem ipsum dolor sit.</span><button><span>删除</span></button></p>
<p><span>Nostrum placeat sint odit.</span><button><span>删除</span></button></p>
<p><span>Perferendis, deleniti! Quo, unde.</span><button><span>删除</span></button></p>
<p><span>Nesciunt facilis atque eos.</span><button><span>删除</span></button></p>
<p><span>Aliquam doloremque at dolorum.</span><button><span>删除</span></button></p>
<p><span>Recusandae inventore quasi tempora?</span><button><span>删除</span></button></p>
<p><span>Vel optio numquam adipisci?</span><button><span>删除</span></button></p>
<p><span>Aperiam, error molestiae? Aut!</span><button><span>删除</span></button></p>
<p><span>Eaque ipsam vel est.</span><button><span>删除</span></button></p>
<p><span>Architecto assumenda atque esse.</span><button><span>删除</span></button></p>
</div>
<script>
// 事件委托的注意事项 ==> 具体情况具体分析
var div = document.querySelector("div")
div.onclick = function (e) {
console.log(e.target); // 尝试点击页面中的不同区域 会发现 e.target
// 可能是 <span>删除</span> <button>...</button> <p>...</p> 等等
// 所以说这种情况下 再使用事件委托就有点不合适了 具体还要视html的结构而定
// 之所以说不合适 是因为判断条件变得更加苛刻了 首先 要明确可以删除的条件
// 1. 点击 button区域时 可以删除
// 2. 点击 <span>删除</span> 区域时 可以删除 (但是这里还需要判断点击的 span 是button里面的span)
if (e.target.tagName === "BUTTON") {
e.target.parentElement.remove();
}else if(e.target.tagName === "SPAN" && e.target.innerText === "删除"){
e.target.parentElement.parentElement.remove();
}
}
</script>
</body>
</html>
<!-- 事件对象的通用成员 ==> currentTarget 和 type
1. currentTarget ==> 当前目标 ==> 获取绑定事件的元素 ==> 等效于this ==> 事件绑定在哪个dom上 就返回哪个dom
2. type ==> 得到事件的类型 ==> 返回String类型 -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>currentTarget 和 type</title>
<style>
div {
background: lightblue;
}
</style>
</head>
<body>
<div>
<p><span>Lorem ipsum dolor sit.</span><button><span>删除</span></button></p>
<p><span>Nostrum placeat sint odit.</span><button><span>删除</span></button></p>
<p><span>Perferendis, deleniti! Quo, unde.</span><button><span>删除</span></button></p>
<p><span>Nesciunt facilis atque eos.</span><button><span>删除</span></button></p>
<p><span>Aliquam doloremque at dolorum.</span><button><span>删除</span></button></p>
<p><span>Recusandae inventore quasi tempora?</span><button><span>删除</span></button></p>
<p><span>Vel optio numquam adipisci?</span><button><span>删除</span></button></p>
<p><span>Aperiam, error molestiae? Aut!</span><button><span>删除</span></button></p>
<p><span>Eaque ipsam vel est.</span><button><span>删除</span></button></p>
<p><span>Architecto assumenda atque esse.</span><button><span>删除</span></button></p>
</div>
<script>
// 事件委托的注意事项 ==> 具体情况具体分析
var div = document.querySelector("div")
div.onclick = function (e) {
// e.currentTarget === this ==> 事件绑定在谁身上 就返回谁
console.log(e.currentTarget)
console.log(this)
console.log(this === e.currentTarget) // true
console.log(e.type) // "click"
}
</script>
</body>
</html>
<!-- 事件对象的通用成员 ==> type -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>type</title>
<style>
div {
background: lightblue;
}
</style>
</head>
<body>
<div>
<p><span>Lorem ipsum dolor sit.</span><button><span>删除</span></button></p>
<p><span>Nostrum placeat sint odit.</span><button><span>删除</span></button></p>
<p><span>Perferendis, deleniti! Quo, unde.</span><button><span>删除</span></button></p>
<p><span>Nesciunt facilis atque eos.</span><button><span>删除</span></button></p>
<p><span>Aliquam doloremque at dolorum.</span><button><span>删除</span></button></p>
<p><span>Recusandae inventore quasi tempora?</span><button><span>删除</span></button></p>
<p><span>Vel optio numquam adipisci?</span><button><span>删除</span></button></p>
<p><span>Aperiam, error molestiae? Aut!</span><button><span>删除</span></button></p>
<p><span>Eaque ipsam vel est.</span><button><span>删除</span></button></p>
<p><span>Architecto assumenda atque esse.</span><button><span>删除</span></button></p>
</div>
<script>
var div = document.querySelector("div");
function handler(e){
console.log(e.type)
}
div.onclick = handler // 鼠标抬起时 输出 "click"
div.onmousedown = handler // 鼠标按下时 输出 "mousedown"
</script>
</body>
</html>
<!-- 事件对象的通用成员 ==> preventDefault & returnValue -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>preventDefault & returnValue</title>
<style>
</style>
</head>
<body>
<a href="https://www.baidu.com">百度</a>
<form action="https://www.taobao.com">
<p>
<input type="text" name="account">
</p>
<p>
<button>提交</button>
</p>
</form>
<script>
var a = document.querySelector("a");
var btn = document.querySelector("button");
// a 远元素有默认行为 跳转页面
a.addEventListener("click", function(e) {
e.preventDefault(); //阻止浏览器的默认行为
console.log("被点击了!");
})
// form 表单中的提交按钮 也有默认行为 跳转页面
// [注意] <button>提交</button> 按钮 默认是 type="submit" 即: <button type="submit">提交</button>
// 如果将 button 的 type属性值 设置为 button 那么就不会有默认行为
btn.onclick = function(e){
e.preventDefault();
// return false; // 这是 dom0 中阻止默认行为的写法
// e.returnValue = false; // 这是 ie 中阻止默认行为的写法
}
</script>
</body>
</html>
<!-- 针对a元素,可以设置为功能性链接解决跳转问题 -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>针对a元素,可以设置为功能性链接解决跳转问题</title>
<style>
</style>
</head>
<body>
<a href="javascript:;">啥也不做</a>
<a href="javascript:console.log('abc');">输出abc</a>
<a href="javascript:void 0;">void 0</a>
<a href="javascript:void(0);">void(0)</a>
</body>
</html>
<!-- 阻止事件冒泡 ==> stopPropagation -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>阻止事件冒泡 ==> 常见的面试题</title>
<style>
</style>
</head>
<body>
<div id="div1">
<p id="p">
<span id="span">
<button>点击</button>
</span>
</p>
</div>
<script>
var div1 = document.getElementById("div1");
var p = document.getElementById("p");
var span = document.getElementById("span");
var btn = document.querySelector("button");
div1.onclick = p.onclick = span.onclick = btn.onclick = function(e){
console.log(this.tagName);
}
p.onclick = function(e){
e.stopPropagation();
}
/* 输出内容如下
BUTTON
SPAN
[解释] ==> 最先输出的是 BUTTON 这是因为它是事件目标 所以经过事件捕获阶段 最先输出的就是 BUTTON
==> 其次输出 SPAN 它之所以能够输出 是因为 SPAN 在结构上 位于 P 的内部 p.onclick 事件处理函数
仅仅阻止了 p 及 p 之后的点击事件的触发 所以 SPAN 是能够输出的 P 和 DIV 是不能够输出的
*/
</script>
</body>
</html>
<!-- 得到事件所处的阶段 ==> eventPhase -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>eventPhase</title>
</head>
<body>
<div id="div1">
<button>点击</button>
</div>
<script>
var div1 = document.getElementById("div1");
var btn = document.querySelector("button");
div1.addEventListener("click", function (e) {
console.log(e.eventPhase + ' ==> ' + "这是在事件捕获阶段运行的事件");
}, true)
div1.addEventListener("click", function (e) {
console.log(e.eventPhase + ' ==> ' + "这是在事件冒泡阶段运行的事件");
})
btn.onclick = function (e) {
console.log(e.eventPhase + ' ==> ' + "这是在事件目标阶段运行的事件");
}
/* 输出内容如下:
1 ==> 这是在事件捕获阶段运行的事件
2 ==> 这是在事件目标阶段运行的事件
3 ==> 这是在事件冒泡阶段运行的事件
*/
</script>
</body>
</html>
[作业讲解]事件对象
<!-- 1. 动态列表 -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>动态列表</title>
</head>
<body>
<p>
<input type="text" id="txt">
<button id="btnAdd">添加</button>
</p>
<ul>
<li>
<span>列表1</span>
<button>删除</button>
</li>
<li>
<span>列表2</span>
<button>删除</button>
</li>
<li>
<span>列表3</span>
<button>删除</button>
</li>
</ul>
<script>
var btnAdd = document.getElementById("btnAdd");
var txt = document.getElementById("txt");
var ul = document.getElementsByTagName("ul")[0];
btnAdd.onclick = function() {
var value = txt.value; // 获取输入框的值
var li = document.createElement("li");
li.innerHTML = `<span>${value}</span>
<button>删除</button>`;
ul.appendChild(li);
txt.value = ""; // 将输入框的值清空
}
// 删除的时候使用了 事件委托
ul.onclick = function(e) {
if (e.target.tagName === "BUTTON") {
e.target.parentElement.remove();
// 注意点 ==> e.target ==> 表示事件源 ==> 即: 点击事件是发生在哪个dom上的
// e.currentTarget ==> 表示当前目标 ==> 获取到的是绑定事件的元素 ==> 等效于this
}
}
// 过程 ==> 用户点击 button (事件源 e.target 也就是 button) ==> 事件冒泡 冒到 ul.onclick ==> 触发相应的事件处理程序
// 思考 ==> 在这种情况下 如果还是直接直接将事件绑定在每一个li内部的button中
// 那么操作起来就会变得比较繁琐 首先要获取到 每一个 button 然后遍历 并 注册事件
// 显然 直接给button注册事件的做法 在这种情况下是相当低效的
</script>
</body>
</html>
<!-- 2. 更换图片 -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>变换图片</title>
<style>
#buttons button {
margin-right: 20px;
}
img {
width: 200px;
height: 200px;
margin-top: 30px;
}
</style>
</head>
<body>
<div id="buttons"></div>
<!-- 一个div元素 ==> 作为 button 的容器 -->
<img src="" alt="">
<!-- 一个img元素 ==> 作为一个图片容器 -->
<script>
// 基本配置信息
var imgs = [
"img/1.jpg",
"img/2.jpg",
"img/3.jpg",
"img/4.jpg"
];
var img = document.getElementsByTagName("img")[0]; // 获取到图片容器
// 默认图片容器中所展示的第一张图片是 配置信息 中的第一张图片
img.src = imgs[0];
// 获取到装 button 的容器 ==> divButtons
var divButtons = document.getElementById("buttons");
// 根据 配置信息 中的成员(图片)数量 ==> 确定需要生成的按钮数量
imgs.forEach(function(src, i) {
// 给 配置信息 中的每一张图片都创建一个对应的 button
var btn = document.createElement("button");
// 设置按钮上显示的文本内容
btn.innerText = `第${i+1}张图片`;
// 将每一次创建出来的按钮 append 到 button 容器 divButtons 中
divButtons.appendChild(btn);
// 将图片的地址 暂存到 button 的自定义属性 data-src 中
btn.dataset.src = src;
});
// 事件委托
divButtons.onclick = function(e) {
// if (e.target.dataset.src) {
// img.src = e.target.dataset.src;
// }
if (e.target.tagName === 'BUTTON') {
img.src = e.target.dataset.src;
}
}
</script>
</body>
</html>
<!-- 3. 变更栏目框 -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>变更栏目框</title>
<style>
* {
margin: 0;
padding: 0;
}
.container {
width: 390px;
height: 655px;
margin: 0 auto;
margin-top: 20px;
}
.top {
width: 100%;
height: 10%;
border-bottom: 2px solid #ccc;
}
.content {
width: 100%;
height: 90%;
}
.top ul {
width: 100%;
height: 100%;
display: flex;
justify-content: space-around;
}
li {
width: 25%;
height: 100%;
list-style: none;
text-align: center;
/* 将box-sizing设置为border-box,可以随意修改padding而不用修改盒子大小 */
box-sizing: border-box;
padding-top: 35px;
font-size: 18px;
color: #2c7bbb;
cursor: pointer;
}
li:hover {
color: #d32228;
}
.current {
border-bottom: 4px solid #d32228;
color: #d32228;
}
img {
margin-top: 5px;
}
</style>
</head>
<body>
<div class="container">
<div class="top">
<ul>
<li data-src="images/1.png">电玩</li>
<li data-src="images/2.png" class="current">网游</li>
<li data-src="images/3.png">手游</li>
<li data-src="images/4.png">数码</li>
</ul>
</div>
<div class="content">
<img src="./images/1.png" alt="" width="390">
</div>
</div>
<script>
/* 上面的样式暂时先不管 重点在于实现 栏目框的变更4效果 */
var ul = document.querySelector(".container .top ul");
var img = document.querySelector("img");
ul.onclick = function (e) {
if (e.target.dataset.src) {
document.querySelector("li.current").classList.remove("current");
img.src = e.target.dataset.src;
e.target.classList.add("current");
}
}
</script>
</body>
</html>
<!-- 4. 手风琴 -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
ul {
margin: 0;
list-style: none;
padding: 0;
}
.wrap {
width: 200px;
border: 1px solid #ccc;
text-align: center;
line-height: 2;
}
.title {
background: rgb(236, 135, 52);
display: block;
cursor: pointer;
}
</style>
</head>
<body>
<div class="wrap" id="wrap">
<ul class="menu"></ul>
</div>
<script>
// if (!("currentStyle" in Element.prototype)) {
Object.defineProperty(Element.prototype, "currentStyle", {
get: function () {
return window.getComputedStyle(this);
}
});
var menu = [{
name: "菜单一",
children: ["商品一", "商品二", "商品三", "商品四"]
},
{
name: "菜单二",
children: ["商品一", "商品二", "商品三", "商品四"]
}, {
name: "菜单三",
children: ["商品一", "商品二", "商品三", "商品四"]
}, {
name: "菜单四",
children: ["商品一", "商品二", "商品三", "商品四"]
}
];
var ul = document.querySelector("ul.menu");
menu.forEach(function (item) {
var li = document.createElement("li");
li.innerHTML = `<span class="title">${item.name}</span>`;
var childUl = document.createElement("ul");
item.children.forEach(function (child) {
childUl.innerHTML += `<li>${child}</li>`;
})
li.appendChild(childUl);
ul.appendChild(li);
})
// 事件委托 ==> 实现 展开 和 收缩
ul.onclick = function (e) {
if (e.target.className === "title") {
var nextUl = e.target.nextElementSibling;
// var style = getComputedStyle(nextUl);
// if (style.display === "none") {
// nextUl.style.display = "block";
// } else {
// nextUl.style.display = "none";
// }
if (nextUl.currentStyle['display'] === "none") {
nextUl.style.display = "block";
} else {
nextUl.style.display = "none";
}
}
}
</script>
</body>
</html>
事件类型-鼠标事件
事件类型
- click:用户单击主鼠标按钮(一般是左键)或者按下在聚焦时按下回车键时触发
- dblclick:用户双击主鼠标按键触发(频率取决于系统配置)
- mousedown:用户按下鼠标任意按键时触发
- mouseup:用户抬起鼠标任意按键时触发
- mousemove:鼠标在元素上移动时触发
- mouseover:鼠标进入元素时触发
- mouseout:鼠标离开元素时触发
- mouseenter:鼠标进入元素时触发,该事件不会冒泡
- mouseleave:鼠标离开元素时触发,该事件不会冒泡
区别:(面试题爱考)
- over和out,不考虑子元素,从父元素移动到子元素,对于父元素而言,仍然算作离开
- enter和leave,考虑子元素,子元素仍然是父元素的一部分
- mouseenter和mouseleave不会冒泡
事件对象
所有的鼠标事件,事件处理程序中的事件对象,都为 MouseEvent
- altKey:触发事件时,是否按下了键盘的alt键
- ctrlKey:触发事件时,是否按下了键盘的ctrl键
- shiftKey:触发事件时,是否按下了键盘的shift键
- button:触发事件时,鼠标按键类型
- 0:左键
- 1:中键
- 2:右键
- 前3个
e.altKey
e.ctrlKey
e.shiftKey
得到的结果是一个 Boolean 类型;- [细节]
e.button
==> 如果是click
或mousemove
事件 ==> 那么得到的结果永远都是0
, 可以使用mousedown
或mouseup
来测试;
与鼠标位置相关的属性:
- page:pageX、pageY,当前鼠标距离页面的横纵坐标
- client: clientX、clientY,鼠标相对于视口的坐标
- offset:offsetX、offsetY,鼠标相对于事件源的内边距的坐标
- screen: screenX、screenY,鼠标相对于屏幕
- x、y,等同于clientX、clientY
- movement:movementX、movementY,只在鼠标移动事件中有效,相对于上一次鼠标位置,偏移的距离
<!-- 触发鼠标事件 以及 这些相关事件的触发先后顺序问题 -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>鼠标事件</title>
</head>
<body>
<div>
<button>点击</button>
</div>
<script>
// click:用户单击主鼠标按钮(一般是左键)或者按下在聚焦时按下回车键时触发
var btn = document.querySelector("button")
// btn.onclick = function(){
// console.log("被点击了!");
// /* 细节
// 1. 需要在按钮上 按下 并在 按钮上 弹起 ==> 才能触发 click事件
// 若弹起鼠标时 鼠标不位于按钮上 那么不会触发 click事件
// 2. 另外一种触发 click事件 的方式 ==> 按tab聚焦 再按回车 */
// }
// dblclick:用户双击主鼠标按键触发(频率取决于系统配置)
// btn.ondblclick = function(){
// console.log("被双击了!");
// /* 细节
// 1. 如果双击的比较慢 那么不会触发 dblclick */
// }
// mousedown:用户按下鼠标任意按键时触发
// btn.onmousedown = function(){
// console.log("按下!");
// }
// mouseup:用户抬起鼠标任意按键时触发
// btn.onmouseup = function(){
// console.log("抬起");
// /* 细节
// 1. 顺序问题 ==> onmousedown ==> onmouseup ==> click ==> dblclick */
// }
// mousemove:鼠标在元素上移动时触发
// btn.onmousemove = function(){
// console.log("移动");
// }
// mouseover:鼠标进入元素时触发
btn.onmouseover = function () {
console.log("进入元素:over");
}
// mouseout:鼠标离开元素时触发
btn.onmouseout = function () {
console.log("离开元素:out");
}
// mouseenter:鼠标进入元素时触发,该事件不会冒泡
btn.onmouseenter = function () {
console.log("进入元素:enter");
}
// mouseleave:鼠标离开元素时触发,该事件不会冒泡
btn.onmouseleave = function () {
console.log("离开元素:leave");
/* 细节
1. 顺序问题 ==> mouseover ==> mouseenter ==> mouseout ==> mouseleave */
}
</script>
</body>
</html>
<!-- - mouseover:鼠标进入元素时触发
- mouseout:鼠标离开元素时触发
- mouseenter:鼠标进入元素时触发,该事件不会冒泡
- mouseleave:鼠标离开元素时触发,该事件不会冒泡
区别:==>(面试题爱考)
- over和out,不考虑子元素,从父元素移动到子元素,对于父元素而言,仍然算作离开
- enter和leave,考虑子元素,子元素仍然是父元素的一部分
- mouseenter和mouseleave不会冒泡
运行一下下面的三个案例 体会体会 -->
<!-- 案例1 -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>两对 进入和离开 之间的区别</title>
<style>
div {
width: 300px;
height: 300px;
background: lightblue;
}
</style>
</head>
<body>
<div>
<button>点击</button>
</div>
<script>
var div = document.querySelector("div")
var btn = document.querySelector("button")
div.onmouseover = function () {
console.log("进入:over", e.bubbles);
}
div.onmouseout = function () {
console.log("离开:out", e.bubbles);
}
</script>
</body>
</html>
<!-- 案例2 -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>两对 进入和离开 之间的区别</title>
<style>
div {
width: 300px;
height: 300px;
background: lightblue;
}
</style>
</head>
<body>
<div>
<button>点击</button>
</div>
<script>
var div = document.querySelector("div")
var btn = document.querySelector("button")
btn.onmouseover = function (e) {
e.stopPropagation();
}
btn.onmouseout = function (e) {
e.stopPropagation();
}
div.onmouseover = function () {
console.log("进入:over", e.bubbles);
}
div.onmouseout = function () {
console.log("离开:out", e.bubbles);
}
</script>
</body>
</html>
<!-- 案例3 -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>两对 进入和离开 之间的区别</title>
<style>
div {
width: 300px;
height: 300px;
background: lightblue;
}
</style>
</head>
<body>
<div>
<button>点击</button>
</div>
<script>
var div = document.querySelector("div")
var btn = document.querySelector("button")
div.onmouseenter = function (e) {
console.log("鼠标进入:enter", e.bubbles)
}
div.onmouseleave = function (e) {
console.log("鼠标离开:leave", e.bubbles)
}
</script>
</body>
</html>
<!-- 事件对象 - 1 -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>事件对象 - 1</title>
</head>
<body>
<button>按钮</button>
<script>
var btn = document.querySelector("button")
btn.onclick = function (e) {
console.log(e.altKey, e.ctrlKey, e.shiftKey);
}
</script>
</body>
</html>
<!-- 事件对象 - 2 -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>事件对象 - 2</title>
</head>
<body>
<button>按钮</button>
<script>
var btn = document.querySelector("button")
btn.onclick = function (e) {
console.log(e.button);
}
btn.onmousedown = function (e) {
console.log(e.button);
}
btn.onmouseup = function (e) {
console.log(e.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">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
div {
border: 2px solid;
background: lightblue;
width: 500px;
height: 500px;
padding: 50px;
margin: 0 auto;
}
p {
border: 2px solid;
background: rgb(252, 81, 81);
width: 100px;
height: 100px;
padding: 50px;
background-clip: content-box;
/* 让背景仅填充内容盒 */
}
</style>
</head>
<body>
<header>
lorem1000
</header>
<div>
<p></p>
</div>
<footer>
lorem1000
</footer>
<script>
var p = document.querySelector("p");
var div = document.querySelector("div");
div.onmousemove = function (e) {
// console.log(e.pageX, e.pageY); // 相对于整个页面
// console.log(e.clientX, e.clientY); // 相对于可视区域
// console.log(e.offsetX, e.offsetY); // 相对于事件源的内边距
// console.log(e.screenX, e.screenX); // 相对于屏幕
// console.log(e.movementX, e.movementY); // 相对于上一次触发时的鼠标位置
}
</script>
</body>
</html>
<!-- 拖拽效果 -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>拖拽效果</title>
<style>
div {
width: 200px;
height: 200px;
border-radius: 50%;
background: rgb(247, 93, 93);
cursor: move;
position: absolute;
left: 100px;
top: 100px;
}
p {
width: 100px;
height: 100px;
background: lightblue;
}
</style>
</head>
<body>
<div>
<!-- <p></p> -->
</div>
<script>
var div = document.querySelector("div");
div.onmousedown = function(e) {
if (e.button !== 0) { //如果不是鼠标左键
return;
}
var pageX = e.pageX;
var pageY = e.pageY;
var style = getComputedStyle(div);
var divLeft = parseFloat(style.left);
var divTop = parseFloat(style.top);
window.onmousemove = function(e) {
var disX = e.pageX - pageX;
var disY = e.pageY - pageY;
div.style.left = divLeft + disX + "px";
div.style.top = divTop + disY + "px";
}
window.onmouseup = window.onmouseleave = function(e) {
if (e.button === 0) {
window.onmousemove = null; //移除鼠标移动事件
}
}
}
// var div = document.querySelector("div");
// var style = getComputedStyle(div);
// var divLeft = parseFloat(style.left);
// var divTop = parseFloat(style.top);
// div.onmousedown = function(e) {
// if (e.button !== 0) { //如果不是鼠标左键
// return;
// }
// window.onmousemove = function(e) {
// divLeft += e.movementX;
// divTop += e.movementY;
// div.style.left = divLeft + "px";
// div.style.top = divTop + "px";
// }
// window.onmouseup = window.onmouseleave = function(e) {
// if (e.button === 0) {
// window.onmousemove = null; //移除鼠标移动事件
// }
// }
// }
</script>
</body>
</html>
[作业讲解]星星评分、放大镜
<!-- 星星评分 -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>星星评分</title>
<style>
.left {
float: left;
margin-left: 10px;
line-height: 30px;
}
</style>
</head>
<body>
<div id="divstars" class="left">
<img src="images/empty.png" alt="">
<img src="images/empty.png" alt="">
<img src="images/empty.png" alt="">
<img src="images/empty.png" alt="">
<img src="images/empty.png" alt="">
</div>
<div id="divword" class="left">
</div>
<script>
var words = ["满意", "一般满意", "还不错", "很满意", "非常满意"];
var divstars = document.getElementById("divstars");
var divword = document.getElementById("divword");
var star = -1; //记录评分,点击的是第几个星星
divstars.onmouseover = function(e) {
if (e.target.tagName === "IMG") {
e.target.src = "images/shining.png";
//处理之前的
var prev = e.target.previousElementSibling;
while (prev) {
prev.src = "images/shining.png";
prev = prev.previousElementSibling;
}
//处理之后的
var next = e.target.nextElementSibling;
while (next) {
next.src = "images/empty.png";
next = next.nextElementSibling;
}
//处理文字
var index = Array.from(divstars.children).indexOf(e.target)
divword.innerHTML = words[index];
}
}
divstars.onclick = function(e) {
if (e.target.tagName === "IMG") {
star = Array.from(divstars.children).indexOf(e.target);
}
}
divstars.onmouseleave = function() {
divword.innerHTML = words[star] || "";
for (var i = 0; i < divstars.children.length; i++) {
if (i <= star) {
divstars.children[i].src = "images/shining.png";
} else {
divstars.children[i].src = "images/empty.png";
}
}
}
</script>
</body>
</html>
<!-- 放大镜 -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>放大镜</title>
<style>
.small {
width: 350px;
height: 350px;
border: 1px solid #ccc;
float: left;
background-clip: padding-box;
position: relative;
}
.big {
width: 540px;
height: 540px;
border: 1px solid #ccc;
float: left;
margin-left: 10px;
background-clip: padding-box;
display: none;
}
.small .move {
position: absolute;
background: rgba(200, 200, 50, 0.5);
border: 1px solid #999;
box-sizing: border-box;
cursor: move;
display: none;
}
</style>
</head>
<body>
<div class="small">
<div class="move"></div>
</div>
<div class="big"></div>
<script>
/**
* 初始化
*/
(function () {
//配置
var config = {
smallBg: "images/mouse.jpg", // 小图背景路径
bigBg: "images/mouseBigSize.jpg", //大图背景路径
divBig: document.querySelector(".big"), //大图div dom元素
divSmall: document.querySelector(".small"), //小图div dom元素
divMove: document.querySelector(".small .move"), //可移动的div
smallImgSize: { //小图尺寸
width: 350,
height: 350
},
divBigSize: { //大的div的尺寸
width: 540,
height: 540
},
bigImgSize: { //大图尺寸
width: 800,
height: 800
}
};
//计算可移动的div的宽高
config.moveSize = {
width: config.divBigSize.width / config.bigImgSize.width * config.smallImgSize.width,
height: config.divBigSize.height / config.bigImgSize.height * config.smallImgSize.height,
};
initDivBg();
initMoveDiv();
initDivSmallEvent();
/**
* 初始化div背景
*/
function initDivBg() {
config.divSmall.style.background = `url("${config.smallBg}") no-repeat left top/100% 100%`;
config.divBig.style.background = `url("${config.bigBg}") no-repeat`;
}
/**
* 初始化可移动的div
*/
function initMoveDiv() {
config.divMove.style.width = config.moveSize.width + "px";
config.divMove.style.height = config.moveSize.height + "px";
}
/**
* 初始化小图div的鼠标事件
*/
function initDivSmallEvent() {
config.divSmall.onmouseenter = function () {
config.divMove.style.display = "block";
config.divBig.style.display = "block";
}
config.divSmall.onmouseleave = function () {
config.divMove.style.display = "none";
config.divBig.style.display = "none";
}
config.divSmall.onmousemove = function (e) {
var offset = getOffset(e);
setPosition(offset);
setBigBgPosition();
}
/**
* 设置大图背景图位置
*/
function setBigBgPosition() {
var style = getComputedStyle(config.divMove);
var left = parseFloat(style.left);
var top = parseFloat(style.top);
var bgLeft = left / config.smallImgSize.width * config.bigImgSize.width;
var bgTop = top / config.smallImgSize.height * config.bigImgSize.height;
config.divBig.style.backgroundPosition = `-${bgLeft}px -${bgTop}px`;
}
/**
* 根据鼠标坐标,设置divMove的坐标
* @param {*} offset
*/
function setPosition(offset) {
var left = offset.x - config.moveSize.width / 2;
var top = offset.y - config.moveSize.height / 2;
if (left < 0) {
left = 0;
}
if (top < 0) {
top = 0;
}
if (left > config.smallImgSize.width - config.moveSize.width) {
left = config.smallImgSize.width - config.moveSize.width;
}
if (top > config.smallImgSize.height - config.moveSize.height) {
top = config.smallImgSize.height - config.moveSize.height;
}
config.divMove.style.left = left + "px";
config.divMove.style.top = top + "px";
}
/**
* 根据鼠标事件参数,得到鼠标在divsmall中的坐标
* @param {MouseEvent} e
*/
function getOffset(e) {
if (e.target === config.divSmall) {
return {
x: e.offsetX,
y: e.offsetY
}
} else {
//事件源是divMove
var style = getComputedStyle(config.divMove);
var left = parseFloat(style.left);
var top = parseFloat(style.top);
return {
x: e.offsetX + left + 1, //加1是因为边框
y: e.offsetY + top + 1
}
}
}
}
}())
</script>
</body>
</html>
事件类型-键盘事件
事件类型
- keydown:按下键盘上任意键触发,如果按住不放,会重复触发此事件
- keypress:按下键盘上一个字符键时触发
- keyup:抬起键盘上任意键触发
keydown、keypress 如果阻止了事件默认行为,文本不会显示。
事件对象
KeyboardEvent
- code:得到按键字符串,适配键盘布局。
- key:得到按键字符串,不适配键盘布局。能得到打印字符。
- keyCode、which:得到键盘编码
<!-- # 键盘事件
## 事件对象
KeyboardEvent
- code:得到按键字符串,适配键盘布局。
- key:得到按键字符串,不适配键盘布局。能得到打印字符。
- keyCode、which:得到键盘编码 -->
<!-- 键盘事件的事件类型 及 键盘事件的事件对象中的相关属性 -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>键盘事件的事件类型 及 键盘事件的事件对象中的相关属性</title>
</head>
<body>
<input type="text">
<script>
// keydown:按下键盘上任意键触发,如果按住不放,会重复触发此事件
// keypress:按下键盘上一个**字符键**时触发
// keyup:抬起键盘上任意键触发
// 注意 ==> keydown、keypress 如果阻止了事件默认行为,文本不会显示。
var inp = document.querySelector("input")
inp.onkeydown = function (e) {
console.log("键盘按下了 key down", e.code, e.key, e.keyCode)
// return false; // 阻止事件默认行为 ==> input 输入框中的文本将不会显示
}
inp.onkeypress = function (e) {
console.log("键盘按下了!key press", e.charCode); // e.charCode ==> 得到字符 Unicode 编码
// 注意 触发 keypress 的按键一般是有反馈的一些按键(字符键) 比如: 26个英文字母 或者 0-9 等 [无法触发的按键 如: Ctrl]
// return false; // 阻止事件默认行为 ==> input 输入框中的文本将不会显示
}
inp.onkeyup = function () {
console.log("键盘抬起了!key up")
// return false; // 阻止事件默认行为 ==> 由于已经按键已经按下了 所以input文本框中的内容 依旧会正常显示
}
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>不能在div等元素上绑定键盘事件 因为div无法聚焦</title>
<style>
div{
width: 200px;
height: 200px;
background: red;
}
</style>
</head>
<body>
<div>
<input type="text">
</div>
<script>
//
var div = document.querySelector("div");
div.onkeydown = function(e) {
console.log("键盘按下了", e.code, e.key, e.keyCode)
}
div.onkeypress = function(e) {
console.log("键盘按下了!key press")
}
div.onkeyup = function() {
console.log("键盘抬起了!key up")
}
</script>
</body>
</html>
[作业讲解]键盘事件
1. 坦克移动
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>坦克移动</title>
<style>
body {
background: #000;
}
img {
position: fixed;
left: 500px;
top: 400px;
}
</style>
</head>
<body>
<img src="imgs/tankU.gif" alt="">
<script src="index.js"></script>
</body>
</html>
var tank = {
direction: "U",// U L R D
left: 500,
top: 400,
dom: document.querySelector("img"),
show: function () { //显示
this.dom.style.left = this.left + "px";
this.dom.style.top = this.top + "px";
this.dom.src = "imgs/tank" + this.direction + ".gif";
}
}
//切换方向 和 移动
document.addEventListener("keydown", function (e) {
console.log(e.key); // 得到按键字符创, 不适配键盘布局. ==> 能得到打印字符
if (e.key === "ArrowUp") {
tank.direction = "U";
tank.top -= 4;
}
else if (e.key === "ArrowDown") {
tank.direction = "D";
tank.top += 4;
}
else if (e.key === "ArrowLeft") {
tank.direction = "L";
tank.left -= 4;
}
else if (e.key === "ArrowRight") {
tank.direction = "R";
tank.left += 4;
}
tank.show();
})
// 小 bug ==> 边界问题
2. 数字文本框
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>数字文本框</title>
</head>
<body>
<input type="text">
<script>
var inp = document.querySelector("input");
inp.onkeypress = function(e) {
var code = e.key[0].charCodeAt(0);
// e.key[0].charCodeAt(0) === e.key.charCodeAt(0)
if (code < 48 || code > 57) {
e.preventDefault(); // 阻止事件默认行为
// return false;
}
}
// 下面这一部分是后边的相关知识 用于解决复制粘贴非数字的bug [但是在输入法下 还是存在此bug。。]
inp.onpaste = function(e) { // onpaste 事件在用户向元素中粘贴文本时触发。
var text = e.clipboardData.getData("text/plain");
if (!text || !/\d+/.test(text)) {
e.preventDefault();
}
}
</script>
</body>
</html>
其他事件
表单事件
- focus:元素聚焦的时候触发(能与用户发生交互的元素,都可以聚焦),该事件不会冒泡
- blur:元素失去焦点时触发,该事件不会冒泡。
- submit:提交表单事件,仅在form元素有效。
- change:文本改变事件
- input: 文本改变事件,即时触发
test.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>表单事件 聚焦focus 和 失焦blur</title>
<style>
.noinput {
color: #ccc;
}
</style>
</head>
<body>
<div>
<input type="text" class="noinput" value="请输入关键字">
</div>
<script>
var inp = document.querySelector("input")
inp.onfocus = function () {
// 补充 ==> 聚焦方式可以是鼠标点击文本输入框 也可以按 tab键
// 注意 ==> focus聚焦事件 ==> 不会冒泡 ==> 所以父元素 div 上 不会触发该事件
if (this.value === this.defaultValue && this.className === "noinput") { // 多添加一个条件判断 预防用户输入的文字 和 文本框中的默认 value 值 相同时 而被清空
// console.log(this.defaultValue); // 表示文本框中默认的 value 值 也就是 "请输入关键字"
this.value = "";
this.className = "";
}
// console.log(e.bubbles); // false => true 表示该事件会冒泡 false 表示该事件不会冒泡
}
inp.onblur = function () { // blur 失去焦点时触发
if (!this.value) { // 若没有文本
this.value = this.defaultValue;
this.className = "noinput";
}
console.log(e.bubbles); // false
}
// 当文本框中没有内容时 显示一些提示文字 这种交互效果 在以前是通过js来实现的 不过现在被 placeholder 这个属性来取代了
// <input type="text" placeholder="请输入关键字">
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>form.onsubmit</title>
</head>
<body>
<form action="https://www.taobao.com">
<p>
<input type="text" placeholder="请输入账号">
</p>
<button>按钮1</button>
<button>按钮2</button>
</form>
<script>
// 提交数据之前 先检测
var form = document.querySelector("form");
var inp = document.querySelector("input");
form.onsubmit = function(e) {
console.log(e.bubbles); // true
if(!inp.value.trim()){
console.log("请输入账号");
e.preventDefault();
// return false; // 若输入框中的内容为空 那么阻止提交
}
}
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>change 和 input</title>
</head>
<body>
<select name="" id="sel">
<option value="1">Lorem.</option>
<option value="2">Porro?</option>
<option value="3">A?</option>
<option value="4">Ad.</option>
<option value="5">Voluptates?</option>
<option value="6">Blanditiis!</option>
<option value="7">Dicta.</option>
<option value="8">Illum!</option>
<option value="9">Sint.</option>
<option value="10">Sunt.</option>
</select>
<input type="text" id="txt1">
<textarea id="txt2"></textarea>
<script>
sel.onchange = function() {
console.log(sel.value, sel.options[sel.selectedIndex].innerHTML);
// 此时 sel.options 和 sel.children 差不多..
}
// txt1.onchange = txt2.onchange = function(){}
// 触发条件 ==> 1. 输入框中的内容发生改变; 2. 失去焦点;
txt1.oninput = txt2.oninput = function(e) {
// e.preventDefault(); // 阻止不了 因为这是内容输入进去 内容发生改变了之后 才触发
if (!this.value.trim()) {
console.log("请输入内容");
return;
}
console.log(this.value, e.bubbles);
}
// txt1.oninput = txt2.oninput = function(){}
// 触发条件 ==> 一旦文本框中内容发生改变就会触发
</script>
</body>
</html>
其他事件
window全局对象
- load、DOMContentLoaded、readystatechange
window的load:window.onload
页面中所有资源全部加载完毕的事件针对图片元素还是比较有用的
图片的load:imgDom.onload
图片资源加载完毕的事件图片是异步渲染的
// 这一部分的内容没看懂 ==> 视频区间 ==> (45min,51min)
// 获取图片尺寸
document.addEventListener("DOMContentLoaded", function () {
var img = document.querySelector("img");
getImgSize(img, function (size) {
console.log(size);
});
})
function getImgSize(img, callback) {
if (img.width === 0 && img.height === 0) {
img.onload = function () {
callback({
width: img.width,
height: img.height
});
}
} else {
callback({
width: img.width,
height: img.height
});
}
}
浏览器渲染页面的过程:
- 得到页面源代码
- 创建document节点
- 从上到下,将元素依次添加到dom树中,每添加一个元素,进行预渲染
因为后续元素可能会影响到前面元素的渲染 比如说前面的元素高度将由后续内容决定的这种情况
- 按照结构,依次渲染子节点
document的DOMContentLoaded:document.addElementListener("DOMContentLoaded". function(){}) // 不能使用 dom0 的方式 document.onDOMContentLoaded 来注册
dom树构建完成后发生
readystate: loading、interactive、complete网页加载资源的三种状态
interactive:触发DOMContentLoaded事件
complete:触发window的load事件
常见的面试题 ==> index.js
/* 查 mdn readyState */
console.log(document.readyState); // loading ==> 表示代码正在加载过程中 ==> mdn的解释: loading / 正在加载 document 仍在加载
document.addEventListener("DOMContentLoaded", function () {
console.log(document.readyState); // interactive ==> 表示 DOM树 已经加载完成 ==> mdn的解释: interactive / 可交互 文档已被解析, "正在加载"状态结束, 但是诸如图像, 样式表和框架之类的子资源仍在加载.
})
window.onload = function () {
console.log(document.readyState); // complete ==> 表示所有资源已经加载完毕 ==> mdn的解释: complete / 完成 文档和所有子类资源已完成加载 表示load状态的事件即将被触发
}
// 当 document.readyState 的值发生变化时 ==> document 对象上的 readystatechange 事件将被触发
- css应该写到页面顶部:避免出现闪烁(如果放到页面底部,会导致元素先没有样式,使用丑陋的默认样式,然后当读到css文件后,重新改变样式)
- JS应该写到页面底部:避免阻塞后续的渲染,也避免运行JS时,得不到页面中的元素。
js代码应该尽量写到页面底部道理很简单 代码都是从上到下依次同步执行的, 如果js代码放前面, 而且代码量还不少的话, 那么用户的体验就是, 一开始一段时间, 看不到页面, 因为这段时间正在加载功能, 功能加载好了之后, 用户才能看到页面; 当然, 这还不是主要的问题, 如果先加载js, 后加载页面元素的话, 那么在加载js时, 执行内部的代码时, 压根就获取不到页面中的元素, 所以... js代码就丢底部就对了
当然也可以将script元素变成异步渲染, 但是不推荐; 因为这么写的话, 就无法确定 js 代码是在什么时候执行的了.
- unload、beforeunload
beforeunload: window的事件,关闭窗口时运行,可以阻止关闭窗口
unload:window的事件,关闭窗口时运行
这两个事件需要在IE浏览器中测试 chrome浏览器中测试看不到效果
不过后期会用它来实现 在关闭窗口时 发送一些 ajax 请求啥的
- scroll
窗口发生滚动时运行的事件这个 scroll 事件 不仅可以 写在window上面 还可以写在 元素上 (监听有一定的时间间隔)
若写在元素上 那么需要给对应的元素添加上一条css声明 ==>
overflow: scroll;
或overflow: auto;
通过scrollTop和scrollLeft,可以获取和设置 滚动距离。这两个属性 可读可写
// 得到整个网页的滚动高度 一般采用下面这种写法
document.documentElement.scrollTop + document.body.scrollTop
// [解释] 这是考虑到了兼容性的问题 ==> 在不同浏览器中 或 在同一浏览器的不同版本中 ==> 这俩值必然有一个是 正常的 另一个是 0 所以只要把两个值相加 就能得到需要的值
- resize
窗口尺寸发生改变时运行的事件,监听的是视口尺寸window 上的属性
==第九节课程中最重要的内容 > 下面的这些尺寸
重视”许愿墙”这个作业; 这个作业里面会涉及到很多的尺寸问题;
- contextmenu
右键菜单事件可以阻止默认行为 ==> 弹出右键菜单
看似与鼠标事件重复了 但是需要认识到一点 ==> 打开右键菜单的方式有很多种 并不一定得使用 鼠标右键的方式 ==> 实际上浏览器上的很多操作都是可以直接通过键盘来实现的 不使用鼠标也OKK
- paste
粘贴事件
- copy
复制事件
- cut
剪切事件
[作业讲解]自定义右键菜单
重写后再丢上来
[作业讲解]许愿墙
重写后再丢上来
补充知识
元素位置
- offsetParent
获取某个元素第一个定位的祖先元素,如果没有,则得到body
body的offsetParent为null
- offsetLeft、offsetTop
相对于该元素的offsetParent的坐标
如果offsetParent是body,则将其当作是整个网页注意 chrome 浏览器的body 默认带有 margin: 8px
得到某个元素相对于整个网页的位置
function getPagePosition(dom) {
var left = dom.offsetLeft;
var top = dom.offsetTop;
var parent = dom.offsetParent;
while (parent) {
left += parent.offsetLeft;
top += parent.offsetTop;
parent = parent.offsetParent;
}
return {
x: left,
y: top
}
}
- getBoundingClientRect方法
该方法得到一个对象,该对象记录了该元素相对于视口的距离
[补充1]
对该方法得到的对象中的一些属性的认识:
[补充2]
function getPagePosition(dom){ } ==> 是相对于整个网页; dom.getBoundingClientRect() ==> 是相对于视口 [也就是说在没有出现滚动条的情况下, 这两者是等效的]
事件模拟
- click
- sumbit
- dispatchEvent
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>事件模拟</title>
</head>
<body>
<form action="https://www.taobao.com">
<div style="width:100px; height:100px; background:red"></div>
</form>
<button id="btn1">按钮1</button>
<button id="btn2">按钮2</button>
<script>
var div = document.querySelector("div");
// 通过点击 div 来实现表单的提交
div.onclick = function() {
var form = document.querySelector("form");
form.submit(); // 模拟form表单被提交
}
btn1.onclick = function() {
console.log("按钮1被点击了")
}
// 实现效果 ==> 点击按钮2 等价于 点击了 按钮2 和 按钮1
btn2.onclick = function() {
console.log("按钮2被点击了");
btn1.click(); // 模拟按钮1被点击
}
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>事件模拟</title>
</head>
<body>
<form action="https://www.taobao.com">
<div style="width:100px; height:100px; background:red"></div>
</form>
<button id="btn1">按钮1</button>
<button id="btn2">按钮2</button>
<button id="btn3">鼠标移入按钮1</button>
<button id="btn4">鼠标移出按钮1</button>
<script>
var div = document.querySelector("div");
div.onclick = function() {
var form = document.querySelector("form");
form.submit();
}
btn1.onclick = function() {
console.log("按钮1被点击了")
}
btn1.onmouseenter = function() {
this.style.background = "red";
this.style.color = "#fff";
}
btn1.onmouseleave = function() {
this.style.background = "initial";
this.style.color = "initial";
}
btn2.onclick = function() {
console.log("按钮2被点击了");
btn1.click();
}
// 实现效果 ==> 点击按钮3 等价于 鼠标移入 按钮1
btn3.onclick = function() {
var event = new MouseEvent("mouseenter", {
bubbles: false
});
btn1.dispatchEvent(event);
}
// 实现效果 ==> 点击按钮4 等价于 鼠标移出 按钮1
btn4.onclick = function() {
var event = new MouseEvent("mouseleave", {
bubbles: false
});
btn1.dispatchEvent(event);
}
</script>
</body>
</html>
其他补充
- window.scrollX、window.pageXOffset、window.scrollY、window.pageYOffset
window.scrollX、window.pageXOffset: 相当于根元素的scrollLeft
window.scrollY、window.pageYOffset: 相当于根元素的scrollTop
- scrollTo、scrollBy ==> 滚动条位置
scrollTo: 设置滚动条位置window.scrollTo(x,y) 将滚动条的位置设置为 (x,y)
scrollBy: 设置偏移量window.scrollBy(x,y) 在原来的基础上 偏移 (x,y)
- resizeTo、resizeBy ==> 窗口尺寸
吸附效果(很常见)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>吸附效果</title>
<style>
.left {
float: left;
width: 70%;
border: 1px solid;
}
.right {
float: right;
width: 25%;
border: 1px solid;
}
.container {
width: 100%;
height: 300px;
background: pink
}
</style>
</head>
<body>
<div class="left">lorem10000</div>
<div class="right">
<p>lorem1000</p>
<div class="container"></div>
</div>
<script src="./script/index.js"></script>
</body>
</html>
var div = document.querySelector(".container");
var scrollTop = 0;
window.onscroll = function () {
var rect = div.getBoundingClientRect();
if (rect.top < 0) {
div.style.position = "fixed";
div.style.top = 0;
div.style.left = rect.left + "px";
// 这里需要将宽度和高度设置为原来的宽度和高度
// 按理来说应该是不用重新设置的 但是这可能是涉及到
// 百分比宽度相对父元素的问题 fixed 在设置 fixed 定位之前
// 貌似是相对于 div.right 的宽度计算的
// 但是设置了 fixed 之后 就相对于 body的宽度计算的
div.style.width = rect.width + "px";
div.style.height = rect.height + "px";
// 若吸附元素还带有 padding border 啥的 那么设置一下 box-sizing: border-box 最好
div.style.boxSizing = "border-box";
// 记录滚动条的位置 ==> 从啥位置开始吸附的
scrollTop = window.scrollY + rect.top;
}
// 当用户向上滚动 越过吸附位置时 将前面为了令吸附元素吸附而设置的行间样式全部清除
if (window.scrollY < scrollTop) {
div.setAttribute("style", "");
}
}