一、事件
1.事件及浏览器常用的事件行为
- 事件是元素天生自带的默认操作行为
=>不论我们是否给其绑定了方法,当我们操作的时候,也会把对应的事件触发- 事件绑定是给元素的某个行为绑定一个方法
=>目的是当事件行为触发的时候,可以做一些事情
常用的事件行为:
[鼠标事件]
* click 点击(移动端click被识别为单击)
* dblclick 双击
* mousedown 鼠标按下
* mouseup 鼠标抬起
* mousemove 鼠标移动
* mouseover 鼠标滑过
* mouseout 鼠标滑出
* mouseenter 鼠标进入
* mouseleave 鼠标离开
* mousewhell 鼠标滚轮滚动
[键盘事件]
* keydown 按下某个键
* keyup 抬起某个键
* keypress 除Shift/Fn/CapsLock键以外,其它键按住(连续触发)
[移动端手指事件]
* 单手指事件模型 Touch
* touchstart 手指按下
* touchmove 手指移动
* touchend 手指松开
* touchcancel 操作取消(一般应用于非正常状态下操作结束)
* 多手指事件模型 Gesture
* gesturestart
* gesturechange / gestureupdate
* gestureend
* gesturecancel
[表单元素常用事件]
* focus 获取焦点
* blur 失去焦点
* change 内容改变
[音视频常用事件]
* canplay 可以播放(资源没有加载完,播放中可能会卡顿)
* canplaythrough 可以播放(资源已经加载完,播放中不会卡顿)
* play 开始播放
* playing 播放中
* pause 暂停播放
[其它常用事件]
* load 资源加载完
* unload 资源卸载
* beforeunload 当前页面关闭之前
* error 资源加载失败
* scroll 滚动事件
* readystatechange AJAX请求状态改变事件
* contextmenu 鼠标右键触发
* …
* https://developer.mozilla.org/zh-CN/docs/Web/Events
* 或者查看元素的属性(属性中onxxx就是元素拥有的事件行为)
2.DOM0和DOM2事件绑定的区别【重点】
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>珠峰培训</title>
<link rel="stylesheet" href="css/reset.min.css">
<style>
html,
body {
height: 100%;
background: lightcyan;
}
#box {
box-sizing: border-box;
position: absolute;
top: 50%;
left: 50%;
margin: -100px 0 0 -100px;
width: 200px;
height: 200px;
background: lightcoral;
}
</style>
</head>
<body>
<div id="box"></div>
<script>
/*
* DOM0事件绑定 VS DOM2事件绑定
* [DOM0]
* 元素.on事件行为=function(){}
* [DOM2]
* 元素.addEventListener(事件行为,function(){},true/false)
* 【注:false:代表在冒泡阶段执行此方法;true:代表在捕获阶段执行此方法. false/true不写默认是false】
* IE6~8中:元素.attachEvent('on事件行为',function(){})
*/
/* DOM0事件绑定的原理:给元素的私有属性赋值,当事件触发,浏览器会帮我们把赋的值执行,但是这样也导致 “只能给当前元素某一个事件行为绑定一个方法” */
/* box.onclick = function () {
console.log('哈哈哈~~');
}
box.onclick = function () {
console.log('呵呵呵~~');
} */
/* box.onclick = function () {
console.log('哈哈哈~~');
//=>移除事件绑定:DOM0直接赋值为null即可
box.onclick = null;
} */
/* ================================================ */
/* DOM2事件绑定的原理:基于原型链查找机制,找到EventTarget.prototype上的方法并且执行,此方法执行,会把给当前元素某个事件行为绑定的所有方法,存放到浏览器默认的事件池中(绑定几个方法,会向事件池存储几个),当事件行为触发,会把事件池中存储的对应方法,依次按照顺序执行 “给当前元素某一个事件行为绑定多个不同方法” */
/* box.addEventListener('click', function () {
console.log('哈哈哈~~');
}, false);
box.addEventListener('click', function () {
console.log('呵呵呵~~');
}, false); */
//=>DOM2事件绑定的时候,我们一般都采用实名函数
//=>目的:这样可以基于实名函数去移除事件绑定
/* function fn() {
console.log('哈哈哈~~');
//=>移除事件绑定:从事件池中移除,所以需要指定好事件类型、方法等信息(要和绑定的时候一样才可以移除)
box.removeEventListener('click', fn, false);
}
box.addEventListener('click', fn, false); */
/* function fn1(){ console.log(1); }
function fn2(){ console.log(2); }
function fn3(){ console.log(3); }
box.addEventListener('click', fn2, false);
box.addEventListener('click', fn3, false);
box.addEventListener('click', fn1, false);
//=>基于addEventListener向事件池增加方法,存在去重的机制 “同一个元素,同一个事件类型,在事件池中只能存储一遍这个方法,不能重复存储”
box.addEventListener('click', fn1, false);
box.addEventListener('mouseover', fn1, false); */
/* window.oncontextmenu = function (ev) {
alert("哇咔咔~~");
} */
/* DOM0和DOM2可以混在一起用【并不会冲突,因为处理的机制不一样】:执行的顺序以绑定的顺序为主 */
/* box.addEventListener('click', function () {
console.log('哔咔哔咔~~');
});
box.onclick = function () {
console.log('哇咔咔~~');
}
box.addEventListener('click', function () {
console.log('call~~');
}); */
/* DOM0中能做事件绑定的事件行为,DOM2都支持;DOM2里面一些事件,DOM0不一定能处理绑定,例如:transitionend、DOMContentLoaded... */
// box.style.transition = 'opacity 1s';
/* box.ontransitionend = function () {
console.log('哇咔咔~~');
} */
/* box.addEventListener('transitionend', function () {
console.log('哇咔咔~~');
}); */
/* window.addEventListener('load', function () {
//=>所有资源都加载完成触发
console.log('LOAD');
});
window.addEventListener('DOMContentLoaded', function () {
//=>只要DOM结构加载完就会触发
console.log('DOMContentLoaded');
}); */
</script>
</body>
</html>
DOM2级事件存在去重机制: “同一个元素,同一个事件类型,在事件池中只能存储一遍这个方法,不能重复存储”。
function fn1(){ console.log(1); }
function fn2(){ console.log(2); }
function fn3(){ console.log(3); }
box.addEventListener('click', fn2, false);
box.addEventListener('click', fn3, false);
box.addEventListener('click', fn1, false);
//=>基于addEventListener向事件池增加方法,存在去重的机制 “同一个元素,同一个事件类型,在事件池中只能存储一遍这个方法,不能重复存储”
box.addEventListener('click', fn1, false);
box.addEventListener('mouseover', fn1, false);
浏览器事件池:去掉了重复的fn1函数。
3.JQuery中事件的处理机制
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>珠峰培训</title>
<link rel="stylesheet" href="css/reset.min.css">
<style>
html,
body {
height: 100%;
background: lightcyan;
}
#box {
box-sizing: border-box;
position: absolute;
top: 50%;
left: 50%;
margin: -100px 0 0 -100px;
width: 200px;
height: 200px;
background: lightcoral;
}
</style>
</head>
<body>
<div id="box"></div>
<script>
//=>$(document).ready(function(){})
/* $(function () {
//=>JQ中的这个处理(DOM结构加载完触发)采用的就是DOMContentLoaded事件,并且依托DOM2事件绑定来处理,所以同一个页面中,此操作可以被使用多次
});
$(function () {
}); */
/* JQ中的事件绑定采用的都是DOM2事件绑定,例如:on/off/one */
</script>
<script src="js/jquery.min.js"></script>
<script>
let $box = $('#box');
/* $box.one('click', function () {
console.log('哇咔咔~~');
}); */
$box.on('click', function () {
console.log('哇咔咔~~');
});
$box.on('click', function () {
console.log('哔咔哔咔,丘~~');
});
</script>
</body>
</html>
面试题: window.onload VS $(document).ready()
这个题我知道,我之前看过部分JQ源码。
// 1.$(document).ready() 采用的是DOM2事件绑定,监听的是DOMContentLoaded这个事件,所以只要DOM结构加载完成就会被触发执行,而且同一个页面中可以使用多次(绑定不同的方法,因为基于DOM2事件池绑定机制完成的)
// 2.window.onload必须等待所有资源都加载完成才会被触发执行,采用DOM0事件绑定,同一个页面只能绑定一次(一个方法),想绑定多个也需要改为window.addEventListener(‘load’,
function () { })DOM2绑定方式
二、事件对象
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>珠峰培训</title>
<link rel="stylesheet" href="css/reset.min.css">
<style>
html,
body {
height: 100%;
background: lightcyan;
}
#box {
box-sizing: border-box;
position: absolute;
top: 50%;
left: 50%;
margin: -100px 0 0 -100px;
width: 200px;
height: 200px;
background: lightcoral;
}
</style>
</head>
<body>
<div id="box"></div>
<script src="js/jquery.min.js"></script>
<script>
let $box = $('#box'),
box = $box.get(0);
/*
* 给元素的事件行为绑定方法,当事件行为触发方法会被执行,不仅被执行,而且还会把当前操作的相关信息传递给这个函数 =>“事件对象ev”
* 如果是鼠标操作,获取的是MouseEvent类的实例 =>鼠标事件对象
* 鼠标事件对象 -> MouseEvent.prototype -> UIEvent.prototype -> Event.prototype -> Object.prototype
* 如果是键盘操作,获取的是KeyboardEvent类的实例 =>键盘事件对象
* 除了以上还有:普通事件对象(Event)、手指事件对象(TouchEvent)等
*/
/*box.onclick = function (ev) {
//=>鼠标事件对象
//clientX/clientY:当前鼠标触发点距离当前窗口左上角的X/Y轴坐标
//pageX/pageY:触发点距离当前页面左上角的X/Y轴坐标
//type:触发事件的类型
//target:事件源(操作的是哪个元素,哪个元素就是事件源),在不兼容的浏览器中可以使用srcElement获取,也代表的是事件源
//preventDefault():用来阻止默认行为的方法,不兼容的浏览器中用ev.returnValue=false也可以阻止默认行为
//stopPropagation():阻止冒泡传播,不兼容的浏览器中用ev.cancelBubble=true也可以阻止默认行为
console.log(ev); //MouseEvent {isTrusted: true, screenX: 258, screenY: 390, clientX: 258, clientY: 287, …}
}*/
/* 事件对象和函数以及给谁绑定的事件没啥必然关系,它存储的是当前本次操作的相关信息,操作一次只能有一份信息,所以在哪个方法中获取的信息都是一样的;第二次操作,存储的信息会把上一次操作存储的信息替换掉...;
*
* 每一次事件触发,浏览器都会这样处理一下
* 1.捕获到当前操作的行为(把操作信息获取到),通过创建MouseEvent等类的实例,得到事件对象EV
* 2.通知所有绑定的方法(符合执行条件的)开始执行,并且把EV当做实参传递给每个方法,所以在每个方法中得到的事件对象其实是一个
* ......
* 3.后面再重新触发这个事件行为,会重新获取本次操作的信息,用新的信息替换老的信息,然后继续之前的步骤...
*/
let obj = null;
box.addEventListener('click', function (ev) {
console.log(ev);
obj = ev;
});
box.addEventListener('click', function (ev) {
console.log(ev === obj); //=>true
});
document.body.onclick = function (ev) {
console.log(ev === obj); //=>true
}
</script>
</body>
</html>
1.阻止事件的默认行为
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>珠峰培训</title>
<link rel="stylesheet" href="css/reset.min.css">
</head>
<body>
<!--
A标签的默认行为1:页面跳转
A标签的默认行为2:锚点定位(后面加“#”号)
-->
<!-- <a href="http://www.zhufengpeixun.cn/#free_course">珠峰培训</a> -->
<!--
阻止它的默认行为
1. href="javascript:;"
2. 点击A标签:先触发click行为,然后再去执行href的跳转
-->
<!-- <a href="http://www.zhufengpeixun.cn/" id="link">我就是一个按钮</a> -->
<script>
/* link.onclick = function (ev) {
//=>返回一个FALSE,相当于结束后面即将执行的步骤
return false;
}
link.onclick = function (ev) {
ev.preventDefault();
} */
</script>
<!-- <style>
.context {
display: none;
position: fixed;
top: 0;
left: 0;
box-sizing: border-box;
padding: 0 5px;
width: 100px;
border: 1px solid lightcoral;
}
.context li {
line-height: 35px;
border-bottom: 1px dashed #EEE;
}
.context li:nth-last-child(1) {
border-bottom: none;
}
</style>
<ul class="context">
<li>哇咔咔~~</li>
<li>比卡丘~~</li>
</ul>
<script>
let context = document.querySelector('.context');
window.addEventListener('contextmenu', function (ev) {
ev.preventDefault();
context.style.display = 'block';
context.style.top = ev.clientY + 'px';
context.style.left = ev.clientX + 'px';
});
</script> -->
<input type="text" id="cardInp">
<script>
//=>键盘事件对象
//code & key:存储的都是按键,code更细致
//keyCode & which:存储的是键盘按键对应的码值
// 方向键:37 38 39 40 =>左上右下
// 空格SPACE 32
// 回车ENTER 13
// 回退BACK 8
// 删除DEL 46
// SHIFT 16
// CTRL 17
// ALT 18
// ......
cardInp.onkeydown = cardInp.onkeyup = function (ev) {
let val = this.value,
reg = /[^0-9X]/g;
this.value = val.replace(reg, '');
//=>超过18位禁止输入
if (this.value.length >= 18) {
let arr = [8, 13, 37, 38, 39, 40, 46];
if (!arr.includes(ev.keyCode)) {
ev.preventDefault();
}
}
//=>按ENTER弹出输入的内容
if (ev.keyCode === 13) {
alert(this.value);
}
}
</script>
</body>
</html>
2.小案例:推盒子
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>珠峰培训</title>
<link rel="stylesheet" href="css/reset.min.css">
<style>
html,
body {
height: 100%;
}
.container {
box-sizing: border-box;
position: absolute;
top: 50%;
left: 50%;
margin: -200px 0 0 -200px;
width: 400px;
height: 400px;
background: lightblue;
}
.container .box {
position: absolute;
top: 0;
left: 0;
width: 100px;
height: 100px;
background: lightcoral;
/* CSS3动画 */
transition: .3s linear;
}
</style>
</head>
<body>
<div class="container">
<div class="box"></div>
</div>
<script src="js/jquery.min.js"></script>
<script>
let $container = $('.container'),
$box = $container.children('.box');
$(document).on('keydown keypress', function (ev) {
//1.首先获取按键码和元素当前的TOP/LEFT
let code = ev.keyCode,
T = parseFloat($box.css('top')),
L = parseFloat($box.css('left')),
step = 30;
//2.根据键盘码计算移动的距离
switch (code) {
case 37:
L -= step;
break;
case 38:
T -= step;
break;
case 39:
L += step;
break;
case 40:
T += step;
break;
}
//4.边界判断
let minL = 0,
maxL = $container.outerWidth() - $box.outerWidth(),
minT = 0,
maxT = $container.outerHeight() - $box.outerHeight();
L = L < minL ? minL : (L > maxL ? maxL : L);
T = T < minT ? minT : (T > maxT ? maxT : T);
//3.把最新的样式重新赋值为盒子
$box.css({
left: L,
top: T
});
});
</script>
</body>
</html>
3.事件的传播机制【重点】
事件的传播机制(三个阶段)
- 捕获阶段:从最外层向最里层事件源依次进行查找(目的:是为冒泡阶段事先计算好传播的层级路径) =>CAPTURING_PHASE:1
- 目标阶段:当前元素的相关事件行为触发 =>AT_TARGET:2
- 冒泡传播:触发当前元素的某一个事件行为,不仅它的这个行为被触发了,而且它所有的祖先元素(一直到window)相关的事件行为都会被依次触发(从内到外的顺序)
=>BUBBLING_PHASE:3 (Event.prototype)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>珠峰培训</title>
<link rel="stylesheet" href="css/reset.min.css">
<style>
html,
body {
height: 100%;
}
#outer {
margin: 20px auto;
width: 300px;
height: 300px;
background: lightblue;
}
#inner {
margin: 20px auto;
width: 200px;
height: 200px;
background: lightcoral;
}
#center {
margin: 20px auto;
width: 100px;
height: 100px;
background: lightgreen;
}
</style>
</head>
<body>
<div id="outer">
<div id="inner">
<div id="center"></div>
</div>
</div>
<script>
document.body.onclick = function (ev) {
console.log('BODY', ev);
}
outer.onclick = function (ev) {
console.log('OUTER', ev);
// ev.stopPropagation();
}
inner.onclick = function (ev) {
console.log('INNER', ev);
}
/* outer.addEventListener('click', function (ev) {
console.log('OUTER', ev);
}, true); //先输出outer,因为他捕获阶段的时候就已经先执行了;但是真实项目中,一般不会写true,第三个参数不写会默认是false
inner.addEventListener('click', function (ev) {
console.log('INNER', ev);
}, false);
//【注:false:代表在冒泡阶段执行此方法;true:代表在捕获阶段执行此方法】
*/
center.onclick = function (ev) {
console.log('CENTER', ev);
//=>阻止冒泡传播
// ev.stopPropagation();
}
</script>
</body>
</html>
4.mouseover和mouseenter的本质区别
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>珠峰培训</title>
<link rel="stylesheet" href="css/reset.min.css">
<style>
html,
body {
height: 100%;
}
#outer {
margin: 20px auto;
width: 300px;
height: 300px;
background: lightblue;
}
#inner {
margin: 20px auto;
width: 200px;
height: 200px;
background: lightcoral;
}
#center {
margin: 20px auto;
width: 100px;
height: 100px;
background: lightgreen;
}
</style>
</head>
<body>
<div id="outer">
<div id="inner">
<div id="center"></div>
</div>
</div>
<!-- 一般不使用mouseover,因为它的机制过于混乱,如果需要基于冒泡传播做什么事,只能用mouseover,因为mouseenter默认阻止了事件的冒泡传播 -->
<!-- <script>
center.onmouseover = function () {
console.log("center over");
}
center.onmouseout = function () {
console.log("center out");
}
inner.onmouseover = function () {
console.log("inner over");
}
inner.onmouseout = function () {
console.log("inner out");
}
</script> -->
<script>
center.onmouseenter = function () {
console.log("center enter");
}
center.onmouseleave = function () {
console.log("center leave");
}
inner.onmouseenter = function () {
console.log("inner enter");
}
inner.onmouseleave = function () {
console.log("inner leave");
}
</script>
</body>
</html>
三、事件委托
事件委托:
- 1.基于事件的冒泡传播机制完成
- 2.如果一个容器中很多元素都要在触发某一事件的时候做一些事情(原始方案:给元素每一个都单独进行事件绑定),我们只需要给当前容器的这个事件行为绑定方法,这样不论是触发后代中哪一个元素的相关事件行为,由于冒泡传播机制,当前容器绑定的方法也都要被触发执行
- 3.想知道点击的是谁(根据是谁做不同的事情),只需要基于事件对象中的ev.target事件源获取即可
=>基于事件委托实现,整体性能要比一个个的绑定方法高出50%左右
=>如果多元素触发,业务逻辑属于一体的,基于事件委托来处理更加好
=>某些业务场景只能基于事件委托处理
【补充】:如果detail是box的子元素,直接用css伪类(hover)来处理即可。
<div class="box">
<span>购物车</span>
<div class="detail">
暂无购物车内容
</div>
</div>
1.案例:详情区域处理【方案1】
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>珠峰培训-事件委托</title>
<!-- IMPORT CSS -->
<link rel="stylesheet" href="css/reset.min.css">
<style>
.container {
margin: 20px auto;
width: 200px;
}
.container .box {
box-sizing: border-box;
float: right;
width: 100px;
height: 35px;
line-height: 35px;
text-align: center;
font-size: 16px;
border: 1px solid #AAA;
position: relative;
top: 1px;
}
.container .detail {
/* display: none; */
box-sizing: border-box;
float: right;
width: 200px;
height: 70px;
line-height: 70px;
text-align: center;
font-size: 14px;
border: 1px solid #AAA;
}
</style>
</head>
<body>
<div class="container clearfix">
<div class="box"><span>购物车</span></div>
<div class="detail">
暂无购物车内容
</div>
</div>
<!-- IMPORT JS -->
<script>
let box = document.querySelector('.box'),
detail = document.querySelector('.detail');
document.onmouseover = function (ev) {
let target = ev.target;
if (target.tagName === "SPAN") {
//=>如果事件源是SPAN,我们让其变为其父元素
target = target.parentNode;
}
if (/^(box|detail)$/.test(target.className)) {
//=>如果事件源的CLASS是BOX/DETAIL
detail.style.display = 'block';
return;
}
detail.style.display = 'none';
}
// 真实项目不会这么搞,我们可以利用事件委托来完成
/*
box.onmouseover = function (ev) {
detail.style.display = 'block';
}
box.onmouseout = function (ev) {
detail.style.display = 'none';
}
detail.onmouseover = function (ev) {
detail.style.display = 'block';
}
detail.onmouseout = function (ev) {
detail.style.display = 'none';
}
*/
</script>
</body>
</html>
2.案例:详情区域处理【方案2】
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>珠峰培训-事件委托</title>
<!-- IMPORT CSS -->
<link rel="stylesheet" href="css/reset.min.css">
<style>
.container {
margin: 20px auto;
width: 200px;
}
.container .box {
box-sizing: border-box;
float: right;
width: 100px;
height: 35px;
line-height: 35px;
text-align: center;
font-size: 16px;
border: 1px solid #AAA;
position: relative;
top: 1px;
}
.container .detail {
display: none;
box-sizing: border-box;
float: right;
width: 200px;
height: 70px;
line-height: 70px;
text-align: center;
font-size: 14px;
border: 1px solid #AAA;
}
</style>
</head>
<body>
<div class="container clearfix">
<div class="box"><span>购物车</span></div>
<div class="detail">
暂无购物车内容
</div>
</div>
<!-- IMPORT JS -->
<script>
let box = document.querySelector('.box'),
detail = document.querySelector('.detail');
document.onclick = function (ev) {
/* let target = ev.target;
target.tagName === "SPAN" ? target = target.parentNode : null;
if (/^box$/.test(target.className)) {
//=>如果是BOX让其显示
detail.style.display = 'block';
return;
} */
/* if (/^detail$/.test(target.className)) {
//=>如果是DETAIL啥也不干
return;
} */
//=>剩下的都是隐藏
detail.style.display = 'none';
}
box.onclick = function (ev) {
//=>如果是BOX让其显示
detail.style.display = 'block';
ev.stopPropagation();
}
detail.onclick = function (ev) {
//=>如果是DETAIL啥也不干
ev.stopPropagation();
}
</script>
</body>
</html>
3.案例:给动态元素绑定事件
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>珠峰培训-事件委托</title>
<!-- IMPORT CSS -->
<link rel="stylesheet" href="css/reset.min.css">
<style>
.box {
margin: 20px auto;
width: 200px;
}
.newsList {
box-sizing: border-box;
padding: 5px;
border: 2px solid lightcoral;
}
.newsList li {
line-height: 35px;
border-bottom: 1px dashed #BBB;
}
.createBtn {
box-sizing: border-box;
margin-top: 10px;
width: 80px;
height: 30px;
border: 1px solid #AAA;
}
</style>
</head>
<body>
<div class="box">
<ul class="newsList">
<li>我是第1个LI</li>
<li>我是第2个LI</li>
<li>我是第3个LI</li>
<li>我是第4个LI</li>
<li>我是第5个LI</li>
</ul>
<button class="createBtn">新增</button>
</div>
<!-- IMPORT JS -->
<script src="js/jquery.min.js"></script>
<script>
let $newsList = $('.newsList'),
$createBtn = $('.createBtn'),
count = 5;
$newsList.click(function (ev) {
let target = ev.target,
$target = $(target);
if (target.tagName === 'LI') {
alert(`我是第${$target.index()+1}个LI`);
}
});
$createBtn.click(function () {
let str = ``;
for (let i = 0; i < 5; i++) {
count++;
str += `<li>我是第${count}个LI</li>`;
}
$newsList.append(str);
});
</script>
<!-- <script>
let $newsList = $('.newsList'),
$createBtn = $('.createBtn'),
$lis = null;
function handle() {
$lis = $newsList.children('li');
$lis.each(function (index, item) {
$(item).click(function () {
alert(`我是第${index+1}个LI`);
});
});
}
handle();
let count = 5;
$createBtn.click(function () {
let str = ``;
for (let i = 0; i < 5; i++) {
count++;
str += `<li>我是第${count}个LI</li>`;
}
$newsList.append(str);
handle();
});
</script> -->
</body>
</html>
四、拖拽
1.拖拽小案例
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>DRAG-拖拽</title>
<!-- IMPORT CSS -->
<link rel="stylesheet" href="css/reset.min.css">
<style>
html,
body {
height: 100%;
overflow: hidden;
}
.box {
position: absolute;
top: 0;
left: 0;
width: 100px;
height: 100px;
background: red;
cursor: move;
}
</style>
</head>
<body>
<div class="box" id="box"></div>
<script>
//真实项目中,一般是用DOM2事件绑定来做
box.addEventListener('mousedown', down);
function down(ev) {
this.startX = ev.pageX;
this.startY = ev.pageY;
this.startL = this.offsetLeft;
this.startT = this.offsetTop;
//=>把执行BIND处理后的函数存储到盒子的自定义属性上,绑定的时候绑定存储的这个方法,移除的时候也基于自定义属性获取到这个方法移除
this._MOVE = move.bind(this);
this._UP = up.bind(this);
document.addEventListener('mousemove', this._MOVE);
document.addEventListener('mouseup', this._UP);
}
function move(ev) {
let curL = ev.pageX - this.startX + this.startL,
curT = ev.pageY - this.startY + this.startT;
let minL = 0,
minT = 0,
maxL = document.documentElement.clientWidth - this.offsetWidth,
maxT = document.documentElement.clientHeight - this.offsetHeight;
curL = curL < minL ? minL : (curL > maxL ? maxL : curL);
curT = curT < minT ? minT : (curT > maxT ? maxT : curT);
this.style.left = curL + 'px';
this.style.top = curT + 'px';
}
function up(ev) {
//up中的this改为box
document.removeEventListener('mousemove', this._MOVE);
document.removeEventListener('mouseup', this._UP);
}
</script>
<script>
/*
box.onmousedown = down;
function down(ev) {
//=>把鼠标起始位置信息和盒子起始位置信息存储到盒子的自定义属性上
this.startX = ev.pageX;
this.startY = ev.pageY;
this.startL = this.offsetLeft;
this.startT = this.offsetTop;
//=>按下来在给盒子绑定MOVE方法
//=>谷歌浏览器中解决鼠标焦点丢失的问题(别绑定给盒子了,绑定给document),但是要注意move中的this已经是document了,而不是之前的box,我们需要处理一下
document.onmousemove = move.bind(this);//用call会立即把move方法执行,用bind就是移动的时候才让他执行move方法
document.onmouseup = up.bind(this);
}
function move(ev) {
//=>随时获取当前鼠标的信息,计算盒子最新的位置
let curL = ev.pageX - this.startX + this.startL,
curT = ev.pageY - this.startY + this.startT;
//=>边界判断
let minL = 0,
minT = 0,
maxL = document.documentElement.clientWidth - this.offsetWidth,
maxT = document.documentElement.clientHeight - this.offsetHeight;
curL = curL < minL ? minL : (curL > maxL ? maxL : curL);
curT = curT < minT ? minT : (curT > maxT ? maxT : curT);
this.style.left = curL + 'px';
this.style.top = curT + 'px';
}
function up(ev) {
//=>鼠标抬起,把MOVE移除掉【DOM0事件绑定的方法,移除的时候直接赋值为null就可以了】
document.onmousemove = null;
document.onmouseup = null;
}
*/
/*
* //=>把鼠标和当前盒子拿绳子捆在一起
this.setCapture();
//=>把鼠标和盒子解绑
this.releaseCapture();
=>【解决鼠标移动过快,鼠标焦点丢失问题,但是谷歌不支持】
*/
</script>
</body>
</html>
拖拽原理图:
2.H5中的拖拽事件【不常用】
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>珠峰培训</title>
<link rel="stylesheet" href="css/reset.min.css">
<style>
html,
body {
height: 100%;
overflow: hidden;
}
#box {
position: absolute;
top: 0;
left: 0;
z-index: 10;
width: 100px;
height: 100px;
background: red;
cursor: move;
}
#container {
position: relative;
box-sizing: border-box;
margin: 30px auto;
width: 300px;
height: 200px;
border: 2px solid lightseagreen;
}
</style>
</head>
<body>
<div id="box" draggable="true"></div>
<div id="container"></div>
<!--
HTML5中DRAG事件:可以把一个元素从当前位置拖拽到指定的容器中
dragstart
drag
dragend
dragover 拖动元素到指定的目标区域上
drop 可以把拖动元素放到目标区域中了
1.给要拖拽的元素设置可被拖拽属性
draggable="true"
2.在拖拽开始的时候,记录一些信息
-->
<script>
box.ondragstart = function (ev) {
//=>DragEvent:拖拽事件对象
// dataTransfer(DataTransfer):setData/getData/clearData 设置的内容最后都会变为字符串 setData(类型标识,对应的值)
ev.dataTransfer.setData('@A', ev.target.id);
}
container.ondragover = function (ev) {
ev.preventDefault();
}
container.ondrop = function (ev) {
ev.preventDefault();
let _ID = ev.dataTransfer.getData('@A'),
_ELE = document.getElementById(_ID);
container.appendChild(_ELE);
}
</script>
</body>
</html>
3.拖拽插件封装
/*
* 简易的拖拽插件
* new Drag([selector],[options]);
* SELECTOR
* 按住谁来实现拖拽
* OPTIONS = {}
* element:拖拽中要移动的元素(默认值:当前按住的元素)
* boundary:是否进行边界校验 (默认值:true,不能超过要移动元素所在容器的范围,需要开发者保证:当前移动的元素是相对于它所在容器定位的)
*
* 生命周期函数(钩子函数)
* dragstart:拖拽开始
* dragmove:拖拽中
* dragend:拖拽结束
*/
~ function () {
/*
* 拖拽插件封装
*/
class Drag {
constructor(selector, options) {
this.initParams(selector, options);
this._selector.addEventListener('mousedown', this.down.bind(this));
}
//=>参数初始化(尽可能把一切信息都挂载到实例上,这样在其它方法中,只要能获取到实例,这些信息都可以调用 =>我们尽可能保证每个方法中的THIS都是实例)
initParams(selector, options = {}) {
this._selector = document.querySelector(selector);
//=>配置项的默认值信息
let defaultParams = {
element: this._selector,
boundary: true,
dragstart: null,
dragmove: null,
dragend: null
};
defaultParams = Object.assign(defaultParams, options);
//=>把配置项信息都挂载到实例上
Drag.each(defaultParams, (value, key) => {
this['_' + key] = value;
});
}
//=>实现拖拽的效果
down(ev) {
let {
_element
} = this;
this.startX = ev.pageX;
this.startY = ev.pageY;
this.startL = Drag.queryCss(_element, 'left');
this.startT = Drag.queryCss(_element, 'top');
this._move = this.move.bind(this);
this._up = this.up.bind(this);
document.addEventListener('mousemove', this._move);
document.addEventListener('mouseup', this._up);
//=>钩子函数处理
this._dragstart && this._dragstart(this, ev);
}
move(ev) {
let {
_element,
_boundary,
startX,
startY,
startL,
startT
} = this;
let curL = ev.pageX - startX + startL,
curT = ev.pageY - startY + startT;
if (_boundary) {
//=>处理边界
let parent = _element.parentNode,
minL = 0,
minT = 0,
maxL = parent.offsetWidth - _element.offsetWidth,
maxT = parent.offsetHeight - _element.offsetHeight;
curL = curL < minL ? minL : (curL > maxL ? maxL : curL);
curT = curT < minT ? minT : (curT > maxT ? maxT : curT);
}
_element.style.left = curL + 'px';
_element.style.top = curT + 'px';
//=>钩子函数处理
this._dragmove && this._dragmove(this, curL, curT, ev);
}
up(ev) {
document.removeEventListener('mousemove', this._move);
document.removeEventListener('mouseup', this._up);
//=>钩子函数处理
this._dragend && this._dragend(this, ev);
}
//=>设置工具类的方法(把它当做类[普通对象]的私有属性)
static each(arr, callback) {
if ('length' in arr) {
//=>数组||类数组
for (let i = 0; i < arr.length; i++) {
callback && callback(arr[i], i);
}
return;
}
//=>普通对象
for (let key in arr) {
if (!arr.hasOwnProperty(key)) break;
callback && callback(arr[key], key);
}
}
static queryCss(curEle, attr) {
return parseFloat(window.getComputedStyle(curEle)[attr]);
}
}
window.Drag = Drag;
}();