1.事件和事件绑定
1. 什么是事件?
事件是浏览器赋予元素的默认行为,也可以理解为事件是天生具备的,不论我们是否为其绑定方法,当某些行为触发的时候,相关的事件都会被触发执行!
「浏览器赋予元素的事件行为」
https://developer.mozilla.org/zh-CN/docs/Web/Events
- 鼠标事件
+ click 点击事件(PC:频繁点击N次,触发N次点击事件) 单击事件(移动端:300ms内没有发生第二次点击操作,算作单击事件行为,所以click在移动端有300ms延迟)
+ dblclick 双击事件
+ contextmenu 鼠标右键点击触发
+ mousedown 鼠标按下
+ mouseup 鼠标抬起
+ mousemove 鼠标移动
+ mouseover 鼠标滑入
+ mouseover 鼠标滑出
+ mouseenter 鼠标进入
+ mouseleave 鼠标离开
+ wheel 鼠标滚轮滚动
+ ...
- 键盘事件
+ keydown 键盘按下
+ keyup 键盘抬起
+ keypress 长按(除了Shift/Fn/CapsLock键之外)
+ ...
- 手指事件
「Touch Event 单手指事件模型」
+ touchstart 手指按下
+ touchmove 手指移动
+ touchend 手指松开
「Gesture Event 多手指事件模型」
- 表单事件
+ focus 获取焦点
+ blur 失去焦点
+ submit 表单提交(前提:表单元素都包含在form中,并且点击的按钮是submit)
+ reset 表单重置(前提:表单元素都包含在form中,并且点击的按钮是reset)
+ select 下拉框内容选中
+ change 内容改变
+ input 移动端中经常使用的,监控文本框中的内容随着输入的改变而触发
+ ...
- 资源事件
+ load 加载成功(window.onload / img.onload)
+ error 加载失败
+ beforeunload 资源卸载之前(window.onbeforeunload 页面关闭之前触发)
+ ...
- CSS3动画事件
+ transitionend transition动画结束
+ transitionstart transition动画开始
+ transitionrun transition动画运行中
+ ...
- 视图事件
+ resize 元素(浏览器)大小改变
+ scroll 滚动条滚动
+ ...
- ...
2. 什么是事件绑定?
给元素默认的事件行为绑定方法,这样可以在行为触发的时候,执行这个方法!
- DOM0级事件绑定
语法:[元素].on[事件]=[函数]
document.body.οnclick=function(){}
移除绑定:赋值为null或者其他非函数值皆可
document.body.οnclick=null
原理:每一个DOM元素对象的私有属性上都有很多类似于“onxxx”的私有属性,我们给这些代表事件的私有属性赋值,就是DOM0事件绑定
+ 如果没有对应事件的私有属性值(例如:DOMContentLoaded)则无法基于这种办法实现事件绑定
+ 只能给当前元素的某个事件行为绑定一个方法,绑定多个方法,最后一个操作会覆盖以往的
+ 好处是执行效率快,而且开发者使用起来方便
- DOM2级事件绑定
语法:[元素].addEventListener([事件],[方法],[捕获/冒泡])
document.body.addEventListener('click',fn1,false);
移除:[元素].removeEventListener([事件],[方法],[捕获/冒泡]) 但是需要参数和绑定的时候一样
document.body.removeEventListener('click',fn1,false);
原理:每一个DOM元素都会基于__proto__,查找到EventTarget.prototype上的addEventListener/removeEventListener等方法,基于这些方法实现事件的绑定和移除;DOM2事件绑定采用事件池机制;
+ DOM2事件绑定,绑定的方法一般不是匿名函数,主要目的是方便移除事件绑定的时候使用
+ 凡是浏览器提供的事件行为,都可以基于这种模式完成事件的绑定和移除(例如:window.onDOMContentLoaded是不行的,因为没有这个私有的事件属性,但是我么可以 window.addEventListener('DOMContentLoaded',func) 这样是可以的)
+ 可以给当前元素的某个事件类型绑定多个“不同”的方法(进入到事件池),这样当事件行为触发,会从事件池中依次(按照绑定的顺序)取出
对应的方法然后执行
2. 事件对象和阻止默认行为
给当前元素的某个事件行为绑定方法,当事件行为触发,不仅会把绑定的方法执行,而且还会给方法默认传递一个实参,而这个实参就是事件对象
-
事件对象中常用的属性
-
target & srcElement
-
type
-
code & key
-
keyCode & which
-
which / keyCode
-
clientX / clientY
-
pageX / ev.pageY
-
preventDefault
-
stopPropagation
-
* 事件对象:存储当前事件操作及触发的相关信息的(浏览器本身记录的,记录的是当前这次操作的信息,和在哪个函数中无关)
* + 鼠标事件对象 MouseEvent
* + clientX/clientY 鼠标触发点距离当前窗口的X/Y轴坐标
* + pageX/pageY 鼠标触发点距离BODY的X/Y轴坐标
* + type 事件类型
* + target / srcElement 获取当前事件源(当前操作的元素)
* + path 传播路径
* + ev.preventDefault() / ev.returnValue=false 阻止默认行为
* + ev.stopPropagation() / ev.cancelBubble=true 阻止冒泡传播
* + ...
*
* + 键盘事件对象 KeyboardEvent
* + key / code 存储按键名字
* + which / keyCode 获取按键的键盘码
* + 方向键 “左37 上38 右39 下40”
* + Space 32
* + BackSpace 8
* + Del 46 MAC电脑中没有BackSpace,delete键是 8
* + Enter 13
* + Shift 16
* + Ctrl 17
* + Alt 18
* + ...
* + altKey 是否按下alt键(组合按键)
* + ctrlKey 是否按下ctrl键(组合按键)
* + shiftKey 是否按下shift键(组合按键)
*
* + TouchEvent 手指事件对象(移动端)
* + changedTouches / touches 都是用来记录手指的信息的,平时常用的是changedTouches
* + 手指按下、移动、离开屏幕 changedTouches都存储了对应的手指信息,哪怕离开屏幕后,存储的也是最后一次手指在屏幕中的信息;而touches在手指离开屏幕后,就没有任何的信息了;=>获取的结果都是一个TouchList集合,记录每一根手指的信息;
* + ev.changedTouches[0] 第一根手指的信息
* + clientX/clientY
* + pageX/pageY
* + ...
*
* + Event 普通事件对象
3.事件的传播机制
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>珠峰在线预科班</title>
<!-- IMPORT CSS -->
<link rel="stylesheet" href="css/reset.min.css">
<style>
.box {
box-sizing: border-box;
margin: 20px auto;
padding-top: 20px;
width: 300px;
height: 300px;
background: lightblue;
}
.outer {
box-sizing: border-box;
margin: 0 auto;
padding-top: 20px;
width: 200px;
height: 200px;
background: lightcoral;
}
.inner {
margin: 0 auto;
width: 100px;
height: 100px;
background: lightgreen;
}
</style>
</head>
<body>
<div class="box">
<div class="outer">
<div class="inner">
</div>
</div>
</div>
<!-- IMPORT JS -->
<script>
因为点击事件行为存在冒泡传播机制,所以不论点击INNER/OUTER/BOX,最后都会传播到BODY上,触发BODY的CLICK事件行为,把为其绑定的方法执行
在方法执行接受到的事件对象中,有一个target/srcElement属性(事件源),可以知道当前操作的是谁,我们此时方法中完全可以根据事件源的不同,做不同的处理
上述机制就是 “事件委托/事件代理”:利用事件的冒泡传播机制(核心/前提),我们可以把一个“容器A”中所有后代元素的某个“事件行为E”触发要做的操作,委托给A的事件行为E,这样后期只要触发A中任何元素的E行为,都会传播到A上,把给A绑定的方法执行;在方法执行的时候,基于事件源不同做不同的处理;
+ 性能高 60%左右
+ 可以操作动态绑定的元素
+ 某些需求必须基于它完成
+ ...
document.body.onclick = function (ev) {
let target = ev.target,
targetClass = target.className;
if (targetClass === "inner") {
console.log('INNER');
return;
}
if (targetClass === "outer") {
console.log('OUTER');
return;
}
if (targetClass === "box") {
console.log('BOX');
return;
}
};
let box = document.querySelector('.box'),
outer = document.querySelector('.outer'),
inner = document.querySelector('.inner');
inner.onclick = function (ev) {
console.log('INNER');
ev.stopPropagation();
};
outer.onclick = function (ev) {
console.log('OUTER');
ev.stopPropagation();
};
box.onclick = function (ev) {
console.log('BOX');
ev.stopPropagation();
};
* DOM0事件绑定中给元素事件行为绑定的方法,都是在目标阶段/冒泡阶段触发的
*
* DOM2事件绑定可以控制在绑定的方法在捕获阶段触发(虽然没有什么实际的意义)
* + 元素.addEventListener(事件,方法,false/true)
* + 最后一个参数默认是false:控制方法是在冒泡阶段触发执行的,如设置为true可以控制在捕获阶段触发执行
inner.onclick = function (ev) {
console.log('INNER==>', ev);
ev.stopPropagation();
};
outer.onclick = function (ev) {
console.log('OUTER==>', ev);
};
box.addEventListener('click', function (ev) {
console.log('BOX==>', ev);
}, true);
box.onclick = function (ev) {
console.log('BOX==>', ev);
};
document.body.onclick = function (ev) {
console.log('BODY==>', ev);
};
</script>
</body>
</html>
4、 mouseover和mouseenter区别
推荐mouseenter和mouseleave配合
5.图片延迟加载思路1(兼容IE)
<!DOCTYPE html>
<html>
<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>图片懒加载</title>
<!-- IMPORT CSS -->
<link rel="stylesheet" href="css/reset.min.css">
<style>
/*
图片懒加载的思路:
+ 最开始加载页面的时候,IMG的SRC不赋值(这样就不会加载真实图片,把真实图片的地址赋值给IMG的自定义属性,方便后期想要加载真实图片时候获取)
+ 如果SRC不赋值或者加载图片是错误的,会显示“碎图”,这样样式不美观,所以:我们最开始让IMG是隐藏的「可以设置display、也可以设置透明度为0(透明度改变可以设置过渡效果)」
+ 给图片所在的盒子设置背景占位图(或者背景颜色),在真实图片没有加载之前,用其占位「盒子宽高事先设置好的」
------
啥时候加载?
+ 当页面第一次渲染完(其它资源加载完成,例如:window.onload)
+ 把出现在当前可视窗口内的图片进行加载
------
如何加载?
+ 获取图片的自定义属性值,拿到真实图片地址
+ 给图片的SRC赋值真实地址:如果图片可以正常加载成功,则让IMG显示
*/
html,
body {
height: 500%;
}
.pic-box {
box-sizing: border-box;
position: absolute;
top: 1000px;
left: 100px;
width: 600px;
height: 337px;
background: #DDD;
}
.pic-box img {
display: block;
width: 100%;
height: 100%;
opacity: 0;
transition: opacity 1s;
}
</style>
</head>
<body>
<div class="pic-box">
<img src="" data-img="images/girl.webp" alt="">
</div>
<!-- IMPORT JS -->
<script src="js/utils.js"></script>
<script>
const picBox = document.querySelector('.pic-box'),
imgBox = picBox.querySelector('img'),
HTML = document.documentElement;
// 图片懒加载
const lazy = function lazy() {
let trueImg = imgBox.getAttribute('data-img');
imgBox.src = trueImg;
imgBox.onload = () => {
imgBox.style.opacity = 1;
};
// 设置自定义属性:当前图片已经处理过延迟加载了
picBox.isLoad = true;
};
// 计算何时加载{完全出现}
const computed = function computed() {
// 如果之前已经处理过,则无需再次处理了
if (picBox.isLoad) return;
let C = HTML.clientHeight,
{ top: B, bottom: A } = picBox.getBoundingClientRect();
if (A <= C && B >= 0) {
lazy();
}
};
// 第一次其它资源加载完计算一次 & 页面滚动中随时计算
// + scroll事件会在浏览器滚动条滚动中进行触发,并且按照浏览器最快反应时间(一般5~7ms)的频率进行触发!!例如:我们滚动100ms,按照5ms触发一次,一共触发20次!! “触发频率太快了,造成了没必要的计算和性能消耗”
// + 此时我们需要“降低”触发频率「我们不是降低浏览器的触发频率,而是把computed函数执行的频率降下来」,我们此操作称之为“函数节流”!!
window.onload = computed;
window.onscroll = utils.throttle(computed, 100);
</script>
<script>
/*
const picBox = document.querySelector('.pic-box'),
imgBox = picBox.querySelector('img');
// 图片懒加载
const lazy = function lazy() {
/!*
// 如果页面中的IMG没有设置隐藏,此时的操作可以确保图片地址是正确的情况下,再给页面中的IMG赋值,防止因图片加载失败,出现“裂图”的情况!!
let trueImg = imgBox.getAttribute('data-img');
let img = new Image(); //document.createElement('img')
img.src = trueImg;
img.onload = () => {
imgBox.src = trueImg;
imgBox.style.opacity = 1;
};
*!/
let trueImg = imgBox.getAttribute('data-img');
imgBox.src = trueImg;
imgBox.onload = () => {
// 真实图片加载成功
imgBox.style.opacity = 1;
};
};
// 当页面其它资源都加载完成后,执行延迟加载
window.onload = lazy;
// setTimeout(lazy, 1000);
*/
</script>
</body>
</html>
6.图片延迟加载2(IntersectionObserver[es6的语法])
IntersectionObserver介绍:
/*
IntersectionObserver:ES6新增的一个内置类
+ 不兼容IE浏览器
+ new IntersectionObserver(callback) 创建它的一个实例:创建一个监听器,用来监听一个或者多个DOM元素和浏览器可视窗口的交叉状态和信息
+ 当交叉状态「出现在可视窗口中、离开可视窗口」发生改变,都会触发监听器的回调函数callback执行
+ 在回调函数中可以获取所有监听的DOM和可视窗口的交叉信息
*/
let box1 = document.querySelector('#box1'),
box2 = document.querySelector('#box2');
// 创建监听器
let ob = new IntersectionObserver((changes) => {
/*
回调函数执行:
+ 创建监听器、且监听了DOM元素会立即执行一次(连续监听多个DOM只触发一次,但是如果监听是分隔开的,每新监听一个元素都会触发执行一次)
+ 当监听的元素和可视窗口交叉状态改变,也会触发执行「默认是“一露头”或者“完全出去”,会触发;当然可以基于第二个Options配置项中的threshold来指定规则;」
+ threshold: [0] 一露头&完全出去
+ ...
+ threshold: [1] 完全出现&出去一点
----
changes:是一个数组,记录了每一个监听元素和可视窗口的交叉信息
+ boundingClientRect:记录当前监听元素的getBoundingClientRect获取的值
+ isIntersecting:true/false true代表出现在可视窗口中,false则反之
+ target:存储当前监听的这个DOM元素对象
+ ...
*/
console.log(changes);
}, { threshold: [1] });
// 监听某个DOM元素和可视窗口的交叉状态改变;unobserve移除监听;
ob.observe(box1);
ob.observe(box2);
IntersectionObserver实现延迟加载
<!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>
* {
margin: 0;
padding: 0;
}
html,
body {
height: 500%;
}
.pic-box {
position: absolute;
width: 1200px;
height: 675px;
background-color: #ccc;
top: 300%;
left: 50%;
transform: translate(-50%, -50%);
}
.pic-img {
display: block;
width: 100%;
height: 100%;
opacity: 0;
transition: opacity 0.5s;
}
</style>
</head>
<body>
<div class="pic-box">
<img src="" data-img="./images/girl.webp" alt="" class="pic-img">
</div>
<script src="./utils.js"></script>
<script>
const picBox = document.querySelector('.pic-box'),
imgBox = picBox.querySelector('img')
// 延迟加载
const lazy = function lazy() {
let trueImg = imgBox.getAttribute('data-img')
imgBox.src = trueImg
imgBox.onload = () => {
imgBox.style.opacity = 1
}
console.log('ok');
}
// 创建监听器监听pic-box,控制延迟加载: 无需再进行复杂运算、也无需考虑函数节流
const ob = new IntersectionObserver(([item]) => {
if (item.isIntersecting) {
// 完全出现在视口中: 延迟架子啊
lazy()
// 处理过的需要移除监听
ob.unobserve(item.target)
}
}, { threshold: [1] })
ob.observe(picBox)
</script>
</body>
</html>
7.京东放大镜效果
jquery写法:
<!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>
.magnifier {
margin: 20px auto;
width: 500px;
display: flex;
justify-content: flex-start;
align-items: flex-start;
}
.magnifier .abbre {
position: relative;
box-sizing: border-box;
width: 200px;
height: 200px;
}
.magnifier .abbre img {
width: 100%;
height: 100%;
}
.magnifier .abbre .mark {
display: none;
position: absolute;
top: 0;
left: 0;
z-index: 10;
width: 80px;
height: 80px;
background: rgba(255, 0, 0, .4);
cursor: move;
}
.magnifier .detail {
display: none;
position: relative;
box-sizing: border-box;
width: 300px;
height: 300px;
overflow: hidden;
}
.magnifier .detail img {
position: absolute;
top: 0;
left: 0;
z-index: 10;
}
</style>
</head>
<body>
<section class="magnifier">
<!-- 缩略图 -->
<div class="abbre">
<img src="images/1.jpg" alt="">
<!-- MARK遮罩层 -->
<div class="mark"></div>
</div>
<!-- 详情图 -->
<div class="detail">
<img src="images/2.jpg" alt="">
</div>
</section>
<script src="js/jquery.min.js"></script>
<script>
$(function() {
// 1. 获取元素
let $magnifier = $('.magnifier'),
$abbre = $magnifier.find('.abbre'),
$mark = $abbre.find('.mark'),
$detail = $magnifier.find('.detail'),
$detailIMG = $detail.find('img');
// 动态计算出大图的大小
let abbreW = $abbre.width(),
abbreH = $abbre.height(),
abbreOffset = $abbre.offset(),
markW = $mark.width(),
markH = $mark.height(),
detailW = $detail.width(),
detailH = $detail.height(),
detailIMGW = 0,
detailIMGH = 0
detailIMGW = detailW / (markW / abbreW) // 图片的宽高= 父盒子的宽高/(缩略图的宽高/)
detailIMGH = detailH / (markH / abbreH)
$detailIMG.css({
width: detailIMGW,
height: detailIMGH
})
// 计算“mark/大图”移动的位置
const computed = function computed(ev) {
let curL = ev.pageX - abbreOffset.left - markW / 2,
curT = ev.pageY - abbreOffset.top - markH / 2
// 边界处理
let minL = 0,
minT = 0,
maxL = abbreW - markW,
maxT = abbreH - markH
curL = curL < minL ? minL : (curL > maxL ? maxL : curL)
curT = curT < minT ? minT : (curT > maxT ? maxT : curT)
$mark.css({
left: curL,
top: curT
});
$detailIMG.css({
left: -curL / abbreW * detailIMGW,
top: -curT / abbreH * detailIMGH
});
}
// 2. 事件触发
$abbre.mouseenter(function(ev) {
$mark.css('display', 'block');
$detail.css('display', 'block');
computed(ev)
}).mousemove(function(ev) {
computed(ev)
}).mouseleave(function(ev) {
$mark.css('display', 'none');
$detail.css('display', 'none');
});
})
</script>
</body>
</html>