1 - WEB API介绍
1.1 API的概念
API(Application Programming Interface,应⽤程序编程接⼝)是⼀些预先定义的函数,⽬的是提
供应⽤程序与开发⼈员基于某软件或硬件得以访问⼀组例程的能⼒,⽽⼜⽆需访问源码,⽆需理解其内
部⼯作机制细节,只需直接调⽤使⽤即可。
举例解释什么是API。
例如,
C语⾔中有⼀个函数 fopen()可以打开硬盘上的⽂件,这个函数对于我们来说,就是⼀个C语⾔提
供的打开⽂件的⼯具。
javascript中有⼀个函数alert()可以在⻚⾯弹⼀个提示框,这个函数就是js提供的⼀个弹框⼯具。
这些⼯具(函数)由编程语⾔提供,内部的实现已经封装好了,我们只要学会灵活的使⽤这些⼯具
即可。
1.2 Web API的概念
Web API 是浏览器提供的⼀套操作浏览器功能和⻚⾯元素的 API ( BOM 和 DOM )。
现阶段我们主要针对于浏览器讲解常⽤的 API , 主要针对浏览器做交互效果。⽐如我们想要浏览器弹出
⼀个警示框, 直接使⽤ alert(‘弹出’)
MDN 详细 API : https://developer.mozilla.org/zh-CN/docs/Web/API
因为 Web API 很多,所以我们将这个阶段称为 Web APIs。
此处的 Web API 特指浏览器提供的⼀系列API(很多函数或对象⽅法),即操作⽹⻚的⼀系列⼯具。例
如:操作html标签、操作⻚⾯地址的⽅法。
1.3 API 和 Web API 总结
-
API 是为我们程序员提供的⼀个接⼝,帮助我们实现某种功能,我们会使⽤就可以了,不必纠结内部如何实现
-
Web API 主要是针对于浏览器提供的接⼝,主要针对于浏览器做交互效果。
-
Web API ⼀般都有输⼊和输出(函数的传参和返回值),Web API 很多都是⽅法(函数)
-
学习 Web API 可以结合前⾯学习内置对象⽅法的思路学习
2 - DOM
DOM 就是把⻚⾯看成是⼀个⽂档,学习DOM就是学习操作⽂档⾥的元素。DOM⼜称为⽂档树模型
DOM树:把html⻚⾯或者是xml⽂件看成是⼀个⽂档,⽂档就是⼀个对象,这个⽂档中所有的标签都是
元素,元素也可以看成是对象,标签(元素、对象)有很多,还有嵌套关系组成的这种层次结构,可以
模拟成树形结构图,简称:树状图,就是dom树
DOM对象:通过DOM⽅式获取的元素得到的对象
⽂档 ⼀个⽹⻚可以称为⽂档
节点(node):⻚⾯中所有的内容都是节点(包括标签、属性、⽂本(⽂字、换⾏、空格、回⻋)、注释
等)
元素(elementt):⻚⾯中所有的标签都是元素,元素可以看成是对象。
根元素:html标签
⻚⾯中的顶级对象:document
属性 标签的属性
HTML⽂件是⽤来展示信息,展示数据的 XML:侧重于存储数据
HTML ⽂件看成是⼀个⽂档(document),把这个⽂档也可以看成是⼀个对象,⽂档中所有的标签都
可以看成是⼀个对象
DOM结构树(继承关系)
DOM结构树上相关联的有这个继承的关系,例如在 HTMLElement.abc=123 ,那么在它的分⽀下的任何
⼀个拥有继承关系的分⽀上,都会有这个属性。⽐如在⻚⾯中选中⼀个元素,那么这个元素身上也有
abc=123这个属性。
document.__proto__ ==> HTMLDocument
HTMLDocument.__proto__ ==> Document
Document.__proto__ ==> Node
Node.__proto__ ==> EventTarget
EventTarget.__proto__ ==> Object
2.1 DOM 常⻅的操作简介
- 获取元素
- 动态创建元素
- 对元素进⾏操作(设置其属性或调⽤其⽅法)
- 事件(什么时机做相应的操作)
1、 getElementByid ⽅法定义在 Document.prototype 上,即 Element 节点上不能使⽤。
2、 getElementsByName ⽅法定义在 HTMLDocument.prototype 上,即⾮ html 中的 document 不能使⽤
( xml documentElement )
3、 getElementsByTagName ⽅法定义在 Document.prototype 和 Element.prototype 上,换句话说就
是获取到的元素可以继续调⽤ getElementsByTagName 这个⽅法。
4、 HTMLDocument.prototype 定义了⼀些常⽤的属性, body.head 分别指代 HTML ⽂档中的
标签5、 Document.prototype 上定义了 documentElement 属性,指代⽂档的根元素,在 HTML ⽂档中,他总
是指代 元素。
6 、 getELementsByClassName 、 querySelectorAll 、 querySelector
在 Docuement.prototype , Element.prototype 类中均有定义。
2.2 获取⻚⾯元素的⽅法
除了id,其他的选择器取出来的都是⼀组的,基本上全部是类数组
1、根据id属性获取元素 返回的是⼀个元素对象 getElementById("id")
2、根据标签名获取元素 返回的是元素对象组成的伪数组 getElementsByTagName("标签的名⼦")
下⾯的⼏个,有的浏览器不⽀持
3 、根据表单的 name 属性获取元素,返回的是元素对象组成的⼀个伪数组
getElementsByName("name属性")
<body> <input type="button" value="更改value值" id="btn"> <input type="text" name="name1" value="我很好"> <br/>
<input type="text" name="name2" value="我很好"> <br/>
<input type="text" name="name1" value="我很好"> <br/>
<input type="text" name="name3" value="我很好"> <br/>
<input type="text" name="name3" value="我很好"> <br/>
<script >
// 根据表单的name属性获取元素 把name是1的获取出来
document.getElementById("btn").onclick = function () {
// document.getElementsByName("name属性值")是根据表单的name属性获取元
素,返回的也是⼀个伪数组
var nameList = document.getElementsByName("name1");
for (var i=0; nameList.length; i++) {
nameList[i].value = "你好呀";
}
};
</script>
</body>
4、根据类样式的名⼦来获取元素,返回的是元素对象组成的伪数组 getElementsByClassName("类名⼦")
<head><meta charset="UTF-8"> <title>Title</title>
<style>
div {
100px;
height: 100px;
background-color: #f40;
}
span {
display: block;
100px;
height: 100px;
background-color: yellow;
}
</style>
</head>
<body> <div class="cls">这是第⼀个div</div>
<div class="cls">这是第⼆个div</div>
<span class="cls">span</span>
<input type="button" value="变颜⾊" id="btn"> <script src="./common.js"></script>
<script >
// 根据类样式获取元素 注意ie8及以下都不⽀持
my$("btn").onclick = function () {
// document.getElementsByClassName("类样式名⼦") 根据类样式获取元素,返
回的也是⼀个伪数组
var classList = document.getElementsByClassName("cls");
for (var i=0; i<classList.length; i++) {
classList[i].style.backgroundColor="pink";
}
};
</script>
</body>
5、根据css选择器获取元素,返回来的是⼀个元素对象 querySelector("选择器名字"),并不常⽤,选择
出来的东⻄不是时事的zz(H5新增的)
6、根据css选择器获取元素,返回的是元素对象组成的伪数组 querySelectorAll("选择器的名字") 注意
⾥⾯的选择器要加符号。⽐如 document.queryselector(".class/#id")
7、获取特殊的元素(body, html)
获取 body 元素 : document.body
获取 html 元素 : document.documentElement
3 - 事件 基础
3.1. 事件概述
JavaScript 使我们有能⼒创建动态⻚⾯,⽽事件是可以被 JavaScript 侦测到的⾏为。
简单理解: 触发--- 响应机制。
⽹⻚中的每个元素都可以产⽣某些可以触发 JavaScript 的事件,例如,我们可以在⽤户点击某按钮时
产⽣⼀个 事件,然后去执⾏某些操作。
3.2 事件的三要素
- 事件源(谁):触发事件的元素
- 事件类型(什么事件): 例如 click 点击事件
- 事件处理程序(做啥):事件触发后要执⾏的代码(函数形式),事件处理函数
<body><button id="btn">唐伯⻁</button>
<script>
// 点击⼀个按钮,弹出对话框
// 1. 事件是有三部分组成 事件源 事件类型 事件处理程序 我们也称
为事件三要素
//(1) 事件源 事件被触发的对象 谁 按钮
var btn = document.getElementById('btn');
//(2) 事件类型 如何触发 什么事件 ⽐如⿏标点击(onclick) 还是⿏标经过
还是键盘按下
//(3) 事件处理程序 通过⼀个函数赋值的⽅式 完成
btn.onclick = function() {
alert('点秋⾹');
}
</script>
</body>
3.3 事件执⾏的步骤
-
获取事件源
-
注册事件
-
添加事件处理程序
案例代码
<body><div>123</div>
<script>
// 执⾏事件步骤
// 点击div 控制台输出 我被选中了
// 1. 获取事件源
var div = document.querySelector('div');
// 2.绑定事件 注册事件
// div.onclick
// 3.添加事件处理程序
div.onclick = function() {
console.log('我被选中了');
}
</script>
</body>
3.4 常⻅的⿏标事件
3.5 分析事件三要素
-
下拉菜单三要素
-
关闭⼴告三要素
4 - 操作属性
JavaScript的 DOM 操作可以改变⽹⻚内容、结构和样式,我们可以利⽤ DOM 操作元素来改变元素⾥
⾯的内容、属性等。(注意:这些操作都是通过元素对象的属性实现的)
4.1. 改变元素内容(获取或设置)
innerText改变元素内容
<body><button>显示当前系统时间</button>
<div>某个时间</div>
<p>1123</p>
<script>
// 当我们点击了按钮, div⾥⾯的⽂字会发⽣变化
// 1. 获取元素
var btn = document.querySelector('button');
var div = document.querySelector('div');
// 2.注册事件
btn.onclick = function() {
// div.innerText = '2019-6-6';
div.innerHTML = getDate();
}
function getDate() {
var date = new Date();
// 我们写⼀个 2019年 5⽉ 1⽇ 星期三
var year = date.getFullYear();
var month = date.getMonth() + 1;
var dates = date.getDate();
var arr = ['星期⽇', '星期⼀', '星期⼆', '星期三', '星期四', '星期
五', '星期六'];
var day = date.getDay();
return '今天是:' + year + '年' + month + '⽉' + dates + '⽇ ' +
arr[day];
}
</script>
</body>
innerText和innerHTML的区别
- innerText主要是设置⽂本的,设置标签内容,是没有标签的效果的,同时会把标签内的所有内容(包括包含的标签)都替换为新的内容 ⽂本的形式
- innerHTML 可以设置⽂本内容,主要作⽤是在标签中设置新的html标签内容,是有标签的效果的,同时把标签内的所有内容(包括包含的标签)都替换成
- 新的内容 解析成html代码的形式
- innerText 获取的时候,会把标签内的所有的⽂本内容到取到。包括嵌套的标签⾥的内容,⽂本的形式
- innerHTML 获取的时候,会把标签内的所有内容,原样获取到,html代码的形式
案例代码
<body><div></div>
<p>
我是⽂字
<span>123</span>
</p>
<script>
// innerText 和 innerHTML的区别
// 1. innerText 不识别html标签 ⾮标准 去除空格和换⾏
var div = document.querySelector('div');
// div.innerText = '<strong>今天是:</strong> 2019';
// 2. innerHTML 识别html标签 W3C标准 保留空格和换⾏的
div.innerHTML = '<strong>今天是:</strong> 2019';
// 这两个属性是可读写的 可以获取元素⾥⾯的内容
var p = document.querySelector('p');
console.log(p.innerText);
console.log(p.innerHTML);
</script>
</body>
innerText改变元素内容
<body><button>显示当前系统时间</button>
<div>某个时间</div>
<p>1123</p>
<script>
// 当我们点击了按钮, div⾥⾯的⽂字会发⽣变化
// 1. 获取元素
var btn = document.querySelector('button');
var div = document.querySelector('div');
// 2.注册事件
btn.onclick = function() {
// div.innerText = '2019-6-6';
div.innerHTML = getDate();
}
function getDate() {
var date = new Date();
// 我们写⼀个 2019年 5⽉ 1⽇ 星期三
var year = date.getFullYear();
var month = date.getMonth() + 1;
var dates = date.getDate();
var arr = ['星期⽇', '星期⼀', '星期⼆', '星期三', '星期四', '星期
五', '星期六'];
var day = date.getDay();
return '今天是:' + year + '年' + month + '⽉' + dates + '⽇ ' +
arr[day];
}
</script>
</body>
innerText和innerHTML的区别
- 获取内容时的区别:
innerText会去除空格和换⾏,⽽innerHTML会保留空格和换⾏
- 设置内容时的区别:
innerText不会识别html,⽽innerHTML会识别
案例代码
<body><div></div>
<p>
我是⽂字
<span>123</span>
</p>
<script>
// innerText 和 innerHTML的区别
// 1. innerText 不识别html标签 ⾮标准 去除空格和换⾏
var div = document.querySelector('div');
// div.innerText = '<strong>今天是:</strong> 2019';
// 2. innerHTML 识别html标签 W3C标准 保留空格和换⾏的
div.innerHTML = '<strong>今天是:</strong> 2019';
// 这两个属性是可读写的 可以获取元素⾥⾯的内容
var p = document.querySelector('p');
console.log(p.innerText);
console.log(p.innerHTML);
</script>
</body>
innerText和textContent兼容性
- 设置标签中的⽂本内容,应该使⽤textContent属性,⾕歌、⽕狐⽀持,ie8不⽀持
- 设置标签中的⽂本内容,应该使⽤innerText属性,⾕歌、ie8⽀持,⾼版本的⽕狐⽀持,低版本的不⽀持
- 如果这个属性在浏览器中不⽀持,那么这个属性的类型是undefined,判断这个属性的类型是不是undefined,就知道浏览器⽀不⽀持。
<body><input type="button" value="设置" id="btn"> <input type="button" value="获取" id="btn1">
<div id="dv">螺蛳粉就算了</div>
<script src="./common.js"></script>
<script>// 设置标签中的⽂本内容,应该使⽤textContent属性,⾕歌、⽕狐⽀持,ie8不⽀持// 设置标签中的⽂本内容,应该使⽤innerText属性,⾕歌、ie8⽀持,⾼版本的⽕狐⽀持,低版本的不⽀持// 如果这个属性在浏览器中不⽀持,那么这个属性的类型是undefined,判断这个属性的类型是不是undefined,就知道浏览器⽀不⽀持。// ** 写解决兼容性的函数// 1、设置标签中的⽂本内容function setInnerText (element, text) {if (typeof element.textContent=="undefined") {//说明不⽀持element.innerText = text; }else {element.textContent = text; } }// 2、获取标签中的⽂本内容function getInnerText (element) {if (typeof element.textContent == "undefined") {return element.innerText; } else {return element.textContent; } }// 测试设置my$("btn").onclick = function () {// 设置div中的⽂本内容setInnerText(my$("dv"), "⽼司机服务"); };// 测试获取my$("btn1").onclick = function () {// 获取div中的内容var out = getInnerText(my$("dv"));console.log(out); };</script>
</body>
4.2 常⽤元素的属性操作
-
src、href
-
id、 alt、title
获取属性的值
元素对象.属性名
设置属性的值
元素对象.属性名 = 值
案例代码
<body><button id="ldh">刘德华</button>
<button id="zxy">张学友</button> <br>
<img src="images/ldh.jpg" alt="" title="刘德华"> <script>
// 修改元素属性 src
// 1. 获取元素
var ldh = document.getElementById('ldh');
var zxy = document.getElementById('zxy');
var img = document.querySelector('img');
// 2. 注册事件 处理程序
zxy.onclick = function() {
img.src = 'images/zxy.jpg';
img.title = '张学友思密达';
}
ldh.onclick = function() {
img.src = 'images/ldh.jpg';
img.title = '刘德华';
}
</script>
</body>
4.2.1. 案例:分时问候
4.3 表单元素的属性操作
获取属性的值
元素对象.属性名
设置属性的值
元素对象.属性名 = 值
表单元素中有⼀些属性如:disabled、checked、selected,元素对象的这些属性的值是布尔
型。
案例代码
<body><button>按钮</button>
<input type="text" value="输⼊内容"> <script>
// 1. 获取元素
var btn = document.querySelector('button');
var input = document.querySelector('input');
// 2. 注册事件 处理程序
btn.onclick = function() {
// 表单⾥⾯的值 ⽂字内容是通过 value 来修改的
input.value = '被点击了';
// 如果想要某个表单被禁⽤ 不能再点击 disabled 我们想要这个按钮
button禁⽤
// btn.disabled = true;
this.disabled = true;
// this 指向的是事件函数的调⽤者 btn
}
</script>
</body>
4.3.1. 案例:仿京东显示密码
4.4 样式属性操作
我们可以通过 JS 修改元素的⼤⼩、颜⾊、位置等样式。
常⽤⽅式
4.4.1. ⽅式1:通过操作style属性
元素对象的style属性也是⼀个对象!
元素对象.style.样式属性 = 值;
案例代码
<body><div></div>
<script>
// 1. 获取元素
var div = document.querySelector('div');
// 2. 注册事件 处理程序
div.onclick = function() {
// div.style⾥⾯的属性 采取驼峰命名法
this.style.backgroundColor = 'purple';
this.style.width = '250px';
}
</script>
</body>
案例:淘宝点击关闭⼆维码
案例:显示隐藏⽂本框内容
4.4.2. ⽅式2:通过操作className属性
元素对象.className = 值;
因为class是关键字,所有使⽤className。
案例代码
<body><div class="first">⽂本</div>
<script>
// 1. 使⽤ element.style 获得修改元素样式 如果样式⽐较少 或者 功能简单的
情况下使⽤
var test = document.querySelector('div');
test.onclick = function() {
// this.style.backgroundColor = 'purple';
// this.style.color = '#fff';
// this.style.fontSize = '25px';
// this.style.marginTop = '100px';
// 2. 我们可以通过 修改元素的className更改元素的样式 适合于样式较多或
者功能复杂的情况
// 3. 如果想要保留原先的类名,我们可以这么做 多类名选择器
// this.className = 'change';
this.className = 'first change';
}
</script>
</body>
4.5 ⼩结
4.6. 排他操作
4.6.1. 排他思想
如果有同⼀组元素,我们想要某⼀个元素实现某种样式, 需要⽤到循环的排他思想算法:
-
所有元素全部清除样式(⼲掉其他⼈)
-
给当前元素设置样式 (留下我⾃⼰)
-
注意顺序不能颠倒,⾸先⼲掉其他⼈,再设置⾃⼰
<button>按钮1</button>
<button>按钮2</button>
<button>按钮3</button>
<button>按钮4</button>
<button>按钮5</button>
<script>
// 1. 获取所有按钮元素
var btns = document.getElementsByTagName('button');
// btns得到的是伪数组 ⾥⾯的每⼀个元素 btns[i]
for (var i = 0; i < btns.length; i++) {
btns[i].onclick = function() {
// (1) 我们先把所有的按钮背景颜⾊去掉 ⼲掉所有⼈
for (var i = 0; i < btns.length; i++) {
btns[i].style.backgroundColor = '';
}
// (2) 然后才让当前的元素背景颜⾊为pink 留下我⾃⼰
this.style.backgroundColor = 'pink';
}
}
</script>
<body><ul class="baidu"> <li><img src="images/1.jpg"></li>
<li><img src="images/2.jpg"></li>
<li><img src="images/3.jpg"></li>
<li><img src="images/4.jpg"></li>
</ul>
<script>
// 1. 获取元素
var imgs = document.querySelector('.baidu').querySelectorAll('img');
// console.log(imgs);
// 2. 循环注册事件
for (var i = 0; i < imgs.length; i++) {
imgs[i].onclick = function() {
// this.src 就是我们点击图⽚的路径 images/2.jpg
// console.log(this.src);
// 把这个路径 this.src 给body 就可以了
document.body.style.backgroundImage = 'url(' + this.src +
')';
}
}
</script>
</body>
<script>
// 1. 全选和取消全选做法: 让下⾯所有复选框的checked属性(选中状态) 跟随
全选按钮即可
// 获取元素
var j_cbAll = document.getElementById('j_cbAll');
var j_tbs =
document.getElementById('j_tb').getElementsByTagName('input');
// 全选按钮注册事件
j_cbAll.onclick = function() {
// this.checked 当前复选框的选中状态
console.log(this.checked);
for (var i = 0; i < j_tbs.length; i++) {
j_tbs[i].checked = this.checked;
}
}
// 给所有的⼦复选框注册单击事件
for (var i = 0; i < j_tbs.length; i++) {
j_tbs[i].onclick = function() {
// flag 控制全选按钮是否选中
var flag = true;
// 每次点击下⾯的复选框都要循环检查者4个⼩按钮是否全被选中
for (var i = 0; i < j_tbs.length; i++) {
if (!j_tbs[i].checked) {
flag = false;
break;
}
}
// 设置全选按钮的状态
j_cbAll.checked = flag;
}
}
</script>
案例:表格隔⾏变⾊
<script>
// 1.获取元素 获取的是 tbody ⾥⾯所有的⾏
var trs = document.querySelector('tbody').querySelectorAll('tr');
// 2. 利⽤循环绑定注册事件
for (var i = 0; i < trs.length; i++) {
// 3. ⿏标经过事件 onmouseover
trs[i].onmouseover = function() {
// console.log(11);
this.className = 'bg';
}
// 4. ⿏标离开事件 onmouseout
trs[i].onmouseout = function() {
this.className = '';
}
}</script>
4.7. ⾃定义属性操作
4.7.1. 获取属性值
<div id="demo" index="1" class="nav"></div>
<script>
var div = document.querySelector('div');
// 1. 获取元素的属性值
// (1) element.属性
console.log(div.id);
//(2) element.getAttribute('属性') get得到获取 attribute 属性的意思 我
们程序员⾃⼰添加的属性我们称为⾃定义属性 index
console.log(div.getAttribute('id'));
console.log(div.getAttribute('index'));
</script>
4.7.2. 设置属性值
// 2. 设置元素属性值
// (1) element.属性= '值'
div.id = 'test';
div.className = 'navs';
// (2) element.setAttribute('属性', '值'); 主要针对于⾃定义属性
div.setAttribute('index', 2);
div.setAttribute('class', 'footer'); // class 特殊 这⾥⾯写的就是
4.7.3. 移出属性
// class 不是className
// 3 移除属性 removeAttribute(属性)
div.removeAttribute('index');
案例:tab栏
<script>
// 获取元素
var tab_list = document.querySelector('.tab_list');
var lis = tab_list.querySelectorAll('li');
var items = document.querySelectorAll('.item');
// for循环,给选项卡绑定点击事件
for (var i = 0; i < lis.length; i++) {
// 开始给5个⼩li 设置索引号
lis[i].setAttribute('index', i);
lis[i].onclick = function() {
// 1. 上的模块选项卡,当前这⼀个底⾊会是红⾊,其余不变(排他思想)
// ⼲掉所有⼈ 其余的li清除 class 这个类
for (var i = 0; i < lis.length; i++) {
lis[i].className = '';
}
// 留下我⾃⼰
this.className = 'current';
// 2. 下⾯的显示内容模块
var index = this.getAttribute('index');
console.log(index);
// ⼲掉所有⼈ 让其余的item 这些div 隐藏
for (var i = 0; i < items.length; i++) {
items[i].style.display = 'none';
}
// 留下我⾃⼰ 让对应的item 显示出来
items[index].style.display = 'block';
}
}
</script>
4.8. H5⾃定义属性
⾃定义属性⽬的:是为了保存并使⽤数据。有些数据可以保存到⻚⾯中⽽不⽤保存到数据库中。
⾃定义属性获取是通过getAttribute(‘属性’) 获取。
但是有些⾃定义属性很容易引起歧义,不容易判断是元素的内置属性还是⾃定义属性。
H5给我们新增了⾃定义属性:
<div getTime="20" data-index="2" data-list-name="andy"></div>
<script>
var div = document.querySelector('div');
// console.log(div.getTime);
console.log(div.getAttribute('getTime'));
div.setAttribute('data-time', 20);
console.log(div.getAttribute('data-index'));
console.log(div.getAttribute('data-list-name'));
// h5新增的获取⾃定义属性的⽅法 它只能获取data-开头的
// dataset 是⼀个集合⾥⾯存放了所有以data开头的⾃定义属性
console.log(div.dataset);
console.log(div.dataset.index);
console.log(div.dataset['index']);
// 如果⾃定义属性⾥⾯有多个-链接的单词,我们获取的时候采取 驼峰命名法
console.log(div.dataset.listName);
console.log(div.dataset['listName']);
</script>
5 - 节点操作
5.1. 节点概述
⽹⻚中的所有内容都是节点(标签、属性、⽂本、注释等),在DOM 中,节点使⽤ node 来表示。
HTML DOM 树中的所有节点均可通过 JavaScript 进⾏访问,所有 HTML 元素(节点)均可被修改,也
可以创建或删除。
⼀般地,节点⾄少拥有nodeType(节点类型)、nodeName(节点名称)和nodeValue(节点值)
这三个基本属性。
5.2. 节点层级
利⽤ DOM 树可以把节点划分为不同的层级关系,常⻅的是⽗⼦兄层级关系
5.3. ⽗级节点
<div class="demo"> <div class="box"> <span class="erweima">×</span>
</div>
</div>
<script>
// 1. ⽗节点 parentNode
var erweima = document.querySelector('.erweima');
// var box = document.querySelector('.box');
// 得到的是离元素最近的⽗级节点(亲爸爸) 如果找不到⽗节点就返回为 null
console.log(erweima.parentNode);
</script>
5.4. ⼦节点
所有⼦节点
⼦元素节点
<ul><li>我是li</li>
<li>我是li</li>
<li>我是li</li>
<li>我是li</li>
</ul>
<script>
// DOM 提供的⽅法(API)获取
var ul = document.querySelector('ul');
var lis = ul.querySelectorAll('li');
// 1. ⼦节点 childNodes 所有的⼦节点 包含 元素节点 ⽂本节点等等
console.log(ul.childNodes);
console.log(ul.childNodes[0].nodeType);
console.log(ul.childNodes[1].nodeType);
// 2. children 获取所有的⼦元素节点 也是我们实际开发常⽤的
console.log(ul.children);
</script>
第1个⼦节点
最后1个⼦节点
第1个⼦元素节点
最后1个⼦元素节点
实际开发中, firstChild 和 lastChild 包含其他节点,操作不⽅便,⽽ firstElementChild 和
lastElementChild ⼜有兼容性问题,那么我们如何获取第⼀个⼦元素节点或最后⼀个⼦元素节点呢?
<ol> <li>我是li1</li>
<li>我是li2</li>
<li>我是li3</li>
<li>我是li4</li>
<li>我是li5</li>
</ol>
<script>
var ol = document.querySelector('ol');
// 1. firstChild 第⼀个⼦节点 不管是⽂本节点还是元素节点
console.log(ol.firstChild);
console.log(ol.lastChild);
// 2. firstElementChild 返回第⼀个⼦元素节点 ie9才⽀持
console.log(ol.firstElementChild);
console.log(ol.lastElementChild);
// 3. 实际开发的写法 既没有兼容性问题⼜返回第⼀个⼦元素
console.log(ol.children[0]);
console.log(ol.children[ol.children.length - 1]);
</script>
案例:新浪下拉菜单
<script>
// 1. 获取元素
var nav = document.querySelector('.nav');
var lis = nav.children; // 得到4个⼩li
// 2.循环注册事件
for (var i = 0; i < lis.length; i++) {
lis[i].onmouseover = function() {
this.children[1].style.display = 'block';
}
lis[i].onmouseout = function() {
this.children[1].style.display = 'none';
}
}
</script>
5.5. 兄弟节点
下⼀个兄弟节点
上⼀个兄弟节点
<div>我是div</div>
<span>我是span</span>
<script>
var div = document.querySelector('div');
// 1.nextSibling 下⼀个兄弟节点 包含元素节点或者 ⽂本节点等等
console.log(div.nextSibling);
console.log(div.previousSibling);
// 2. nextElementSibling 得到下⼀个兄弟元素节点
console.log(div.nextElementSibling);
console.log(div.previousElementSibling);
</script>
下⼀个兄弟元素节点(有兼容性问题)
上⼀个兄弟元素节点(有兼容性问题)
function getNextElementSibling(element) {
var el = element;
while (el = el.nextSibling) {
if (el.nodeType === 1) {
return el;
}
}
return null;
}
5.6. 创建节点
5.7. 添加节点
<ul><li>123</li>
</ul>
<script>
// 1. 创建节点元素节点
var li = document.createElement('li');
// 2. 添加节点 node.appendChild(child) node ⽗级 child 是⼦级 后⾯追加
元素
var ul = document.querySelector('ul');
ul.appendChild(li);
// 3. 添加节点 node.insertBefore(child, 指定元素);
var lili = document.createElement('li');
ul.insertBefore(lili, ul.children[0]);
// 4. 我们想要⻚⾯添加⼀个新的元素 : 1. 创建元素 2. 添加元素
</script>
案例:简单版发布留⾔
<body><textarea name="" id=""></textarea>
<button>发布</button>
<ul> </ul>
<script>
// 1. 获取元素
var btn = document.querySelector('button');
var text = document.querySelector('textarea');
var ul = document.querySelector('ul');
// 2. 注册事件
btn.onclick = function() {
if (text.value == '') {
alert('您没有输⼊内容');
return false;
} else {
// console.log(text.value);
// (1) 创建元素
var li = document.createElement('li');
// 先有li 才能赋值
li.innerHTML = text.value;
// (2) 添加元素
// ul.appendChild(li);
ul.insertBefore(li, ul.children[0]);
}
}
</script>
</body>
5.8. 删除节点
<button>删除</button>
<ul><li>熊⼤</li>
<li>熊⼆</li>
<li>光头强</li>
</ul>
<script>
// 1.获取元素
var ul = document.querySelector('ul');
var btn = document.querySelector('button');
// 2. 删除元素 node.removeChild(child)
// ul.removeChild(ul.children[0]);
// 3. 点击按钮依次删除⾥⾯的孩⼦
btn.onclick = function() {
if (ul.children.length == 0) {
this.disabled = true;
} else {
ul.removeChild(ul.children[0]);
}
}
</script>
案例:删除留⾔
<textarea name="" id=""></textarea>
<button>发布</button>
<ul> </ul>
<script>
// 1. 获取元素
var btn = document.querySelector('button');
var text = document.querySelector('textarea');
var ul = document.querySelector('ul');
// 2. 注册事件
btn.onclick = function() {
if (text.value == '') {
alert('您没有输⼊内容');
return false;
} else {
// console.log(text.value);
// (1) 创建元素
var li = document.createElement('li');
// 先有li 才能赋值
li.innerHTML = text.value + "<a href='javascript:;'>删除</a>";
// (2) 添加元素
// ul.appendChild(li);
ul.insertBefore(li, ul.children[0]);
// (3) 删除元素 删除的是当前链接的li 它的⽗亲
var as = document.querySelectorAll('a');
for (var i = 0; i < as.length; i++) {
as[i].onclick = function() {
// 删除的是 li 当前a所在的li this.parentNode;
ul.removeChild(this.parentNode);
}
}
}
}
</script>
5.9. 复制(克隆)节点
<ul><li>1111</li>
<li>2</li>
<li>3</li>
</ul>
<script>
var ul = document.querySelector('ul');
// 1. node.cloneNode(); 括号为空或者⾥⾯是false 浅拷⻉ 只复制标签不复制⾥
⾯的内容
// 2. node.cloneNode(true); 括号为true 深拷⻉ 复制标签复制⾥⾯的内容
var lili = ul.children[0].cloneNode(true);
ul.appendChild(lili);
</script>
案例:动态⽣成表格
<script>
// 1.先去准备好学⽣的数据
var datas = [{
name: '魏璎珞',
subject: 'JavaScript',
score: 100
}, {
name: '弘历',
subject: 'JavaScript',
score: 98
}, {
name: '傅恒',
subject: 'JavaScript',
score: 99
}, {
name: '明⽟',
subject: 'JavaScript',
score: 88
}, {
name: '⼤猪蹄⼦',
subject: 'JavaScript',
score: 0
}];
// 2. 往tbody ⾥⾯创建⾏: 有⼏个⼈(通过数组的⻓度)我们就创建⼏⾏
var tbody = document.querySelector('tbody');
// 遍历数组
for (var i = 0; i < datas.length; i++) {
// 1. 创建 tr⾏
var tr = document.createElement('tr');
tbody.appendChild(tr);
// 2. ⾏⾥⾯创建单元格td 单元格的数量取决于每个对象⾥⾯的属性个数
// 使⽤for in遍历学⽣对象
for (var k in datas[i]) {
// 创建单元格
var td = document.createElement('td');
// 把对象⾥⾯的属性值 datas[i][k] 给 td
td.innerHTML = datas[i][k];
tr.appendChild(td);
}
// 3. 创建有删除2个字的单元格
var td = document.createElement('td');
td.innerHTML = '<a href="javascript:;">删除 </a>';
tr.appendChild(td);
}
// 4. 删除操作 开始
var as = document.querySelectorAll('a');
for (var i = 0; i < as.length; i++) {
as[i].onclick = function() {
// 点击a 删除 当前a 所在的⾏(链接的爸爸的爸爸)
node.removeChild(child)
tbody.removeChild(this.parentNode.parentNode)
}
}
</script>
5.10. 创建元素的三种⽅式
<script>
// 三种创建元素⽅式区别
// 1. document.write() 创建元素 如果⻚⾯⽂档流加载完毕,再调⽤这句话会导
致⻚⾯重绘
var btn = document.querySelector('button');
btn.onclick = function() {
document.write('<div>123</div>');
}
// 2. innerHTML 创建元素
var inner = document.querySelector('.inner');
for (var i = 0; i <= 100; i++) {
inner.innerHTML += '<a href="#">百度</a>'
}
var arr = [];
for (var i = 0; i <= 100; i++) {
arr.push('<a href="#">百度</a>');
}
inner.innerHTML = arr.join('');
// 3. document.createElement() 创建元素
var create = document.querySelector('.create');
for (var i = 0; i <= 100; i++) {
var a = document.createElement('a');
create.appendChild(a);
}
</script>
5.11. innerTHML和createElement效率对⽐
innerHTML字符串拼接⽅式(效率低)
<script>
function fn() {
var d1 = +new Date();
var str = '';
for (var i = 0; i < 1000; i++) {
document.body.innerHTML += '<div style="100px; height:2px;
border:1px solid blue;"></div>';
}
var d2 = +new Date();
console.log(d2 - d1);
}
fn();
</script>
createElement⽅式(效率⼀般)
<script>
function fn() {
var d1 = +new Date();
for (var i = 0; i < 1000; i++) {
var div = document.createElement('div');
div.style.width = '100px';
div.style.height = '2px';
div.style.border = '1px solid red';
document.body.appendChild(div);
}
var d2 = +new Date();
console.log(d2 - d1);
}
fn();
</script>
innerHTML数组⽅式(效率⾼)
<script>
function fn() {
var d1 = +new Date();
var array = [];
for (var i = 0; i < 1000; i++) {
array.push('<div style="100px; height:2px; border:1px solid
blue;"></div>');
}
document.body.innerHTML = array.join('');
var d2 = +new Date();
console.log(d2 - d1);
}
fn();
</script>
6 - DOM的核⼼总结
6.1. 创建
6.2. 增加
6.3. 删
6.4. 改
6.5. 查
6.6. 属性操作
7 - 事件⾼级
7.1. 注册事件(2种⽅式)
7.2 事件监听
addEventListener()事件监听(IE9以后⽀持)
eventTarget.addEventListener()⽅法将指定的监听器注册到 eventTarget(⽬标对象)上,当该对象
触发指定的事件时,就会执⾏事件处理函数。
attacheEvent()事件监听(IE678⽀持)
eventTarget.attachEvent()⽅法将指定的监听器注册到 eventTarget(⽬标对象) 上,当该对象触发
指定的事件时,指定的回调函数就会被执⾏。
<button>传统注册事件</button>
<button>⽅法监听注册事件</button>
<button>ie9 attachEvent</button>
<script>
var btns = document.querySelectorAll('button');
// 1. 传统⽅式注册事件
btns[0].onclick = function() {
alert('hi');
}
btns[0].onclick = function() {
alert('hao a u');
}
// 2. 事件侦听注册事件 addEventListener
// (1) ⾥⾯的事件类型是字符串 必定加引号 ⽽且不带on
// (2) 同⼀个元素 同⼀个事件可以添加多个侦听器(事件处理程序)
btns[1].addEventListener('click', function() {
alert(22);
})
btns[1].addEventListener('click', function() {
alert(33);
})
// 3. attachEvent ie9以前的版本⽀持
btns[2].attachEvent('onclick', function() {
alert(11);
})
</script>
事件监听兼容性解决⽅案
封装⼀个函数,函数中判断浏览器的类型:
7.3. 删除事件(解绑事件)
<div>1</div>
<div>2</div>
<div>3</div>
<script>
var divs = document.querySelectorAll('div');
divs[0].onclick = function() {
alert(11);
// 1. 传统⽅式删除事件
divs[0].onclick = null;
}
// 2. removeEventListener 删除事件
divs[1].addEventListener('click', fn) // ⾥⾯的fn 不需要调⽤加⼩括号
function fn() {
alert(22);
divs[1].removeEventListener('click', fn);
}
// 3. detachEvent
divs[2].attachEvent('onclick', fn1);
function fn1() {
alert(33);
divs[2].detachEvent('onclick', fn1);
}
</script>
删除事件兼容性解决⽅案
7.4. DOM事件流
html中的标签都是相互嵌套的,我们可以将元素想象成⼀个盒⼦装⼀个盒⼦,document是
最外⾯的⼤盒⼦。
当你单击⼀个div时,同时你也单击了div的⽗元素,甚⾄整个⻚⾯。
那么是先执⾏⽗元素的单击事件,还是先执⾏div的单击事件 ???
⽐如:我们给⻚⾯中的⼀个div注册了单击事件,当你单击了div时,也就单击了body,单击了html,单击了document。
当时的2⼤浏览器霸主谁也不服谁!
IE 提出从⽬标元素开始,然后⼀层⼀层向外接收事件并响应,也就是冒泡型事件流。
Netscape(⽹景公司)提出从最外层开始,然后⼀层⼀层向内接收事件并响应,也就是捕获
型事件流。
江湖纷争,武林盟主也脑壳疼!!!
最终,w3c 采⽤折中的⽅式,平息了战⽕,制定了统⼀的标准 —--— 先捕获再冒泡。
现代浏览器都遵循了此标准,所以当事件发⽣时,会经历3个阶段。
DOM 事件流会经历3个阶段:
-
捕获阶段
-
当前⽬标阶段
-
冒泡阶段
我们向⽔⾥⾯扔⼀块⽯头,⾸先它会有⼀个下降的过程,这个过程就可以理解为从最顶层向事件发⽣的
最具体元素(⽬标点)的捕获过程;之后会产⽣泡泡,会在最低点( 最具体元素)之后漂浮到⽔⾯上,
这个过程相当于事件冒泡。
事件冒泡
<div class="father"> <div class="son">son盒⼦</div>
</div>
<script>
// onclick 和 attachEvent(ie) 在冒泡阶段触发
// 冒泡阶段 如果addEventListener 第三个参数是 false 或者 省略
// son -> father ->body -> html -> document
var son = document.querySelector('.son');
// 给son注册单击事件
son.addEventListener('click', function() {
alert('son');
}, false);
// 给father注册单击事件
var father = document.querySelector('.father');
father.addEventListener('click', function() {
alert('father');
}, false);
// 给document注册单击事件,省略第3个参数
document.addEventListener('click', function() {
alert('document');
})
</script>
事件捕获
<div class="father"> <div class="son">son盒⼦</div>
</div>
<script>
// 如果addEventListener() 第三个参数是 true 那么在捕获阶段触发
// document -> html -> body -> father -> son
var son = document.querySelector('.son');
// 给son注册单击事件,第3个参数为true
son.addEventListener('click', function() {
alert('son');
}, true);
var father = document.querySelector('.father');
// 给father注册单击事件,第3个参数为true
father.addEventListener('click', function() {
alert('father');
}, true);
// 给document注册单击事件,第3个参数为true
document.addEventListener('click', function() {
alert('document');
}, true) </script>
7.5. 事件对象
什么是事件对象
事件发⽣后,跟事件相关的⼀系列信息数据的集合都放到这个对象⾥⾯,这个对象就是事件对象。
⽐如:
-
谁绑定了这个事件。
-
⿏标触发事件的话,会得到⿏标的相关信息,如⿏标位置。
-
键盘触发事件的话,会得到键盘的相关信息,如按了哪个键。
事件对象的使⽤
事件触发发⽣时就会产⽣事件对象,并且系统会以实参的形式传给事件处理函数。所以,在事件处理函数中声明1个形参⽤来接收事件对象。
事件对象的兼容性处理
事件对象本身的获取存在兼容问题:
-
标准浏览器中是浏览器给⽅法传递的参数,只需要定义形参 e 就可以获取到。
-
在 IE6~8 中,浏览器不会给⽅法传递参数,如果需要的话,需要到 window.event 中获取查找。
只要“||”前⾯为false, 不管“||”后⾯是true 还是 false,都返回 “||” 后⾯的值。
只要“||”前⾯为true, 不管“||”后⾯是true 还是 false,都返回 “||” 前⾯的值。
<div>123</div>
<script>
var div = document.querySelector('div');
div.onclick = function(e) {
// 事件对象
e = e || window.event;
console.log(e);
}
</script>
事件对象的属性和⽅法
e.target 和 this 的区别
-
this 是事件绑定的元素(绑定这个事件处理函数的元素) 。
-
e.target 是事件触发的元素。
常情况下terget 和 this是⼀致的, 但有⼀种情况不同,那就是在事件冒泡时(⽗⼦元素有相同事 件,单击⼦元素,⽗元素的事件处理函数也会被触发执⾏), 这时候this指向的是⽗元素,因为它 是绑定事件的元素对象, ⽽target指向的是⼦元素,因为他是触发事件的那个具体元素对象。
<div>123</div>
<script>
var div = document.querySelector('div');
div.addEventListener('click', function(e) {
// e.target 和 this指向的都是div
console.log(e.target);
console.log(this);
});
</script>
事件冒泡下的e.target和this
<ul><li>abc</li>
<li>abc</li>
<li>abc</li>
</ul>
<script>
var ul = document.querySelector('ul');
ul.addEventListener('click', function(e) {
// 我们给ul 绑定了事件 那么this 就指向ul
console.log(this); // ul
// e.target 触发了事件的对象 我们点击的是li e.target 指向的就是li
console.log(e.target); // li
});
</script>
7.6 阻⽌默认⾏为
html中⼀些标签有默认⾏为,例如a标签被单击后,默认会进⾏⻚⾯跳转。
href="javacript:void(0);"
<a href="http://www.baidu.com">百度</a>
<script>
// 2. 阻⽌默认⾏为 让链接不跳转
var a = document.querySelector('a');
a.addEventListener('click', function(e) {
e.preventDefault(); // dom 标准写法
});
// 3. 传统的注册⽅式
a.onclick = function(e) {
// 普通浏览器 e.preventDefault(); ⽅法
e.preventDefault();
// 低版本浏览器 ie678 returnValue 属性
e.returnValue = false;
// 我们可以利⽤return false 也能阻⽌默认⾏为 没有兼容性问题
return false;
}
</script>
7.7 阻⽌事件冒泡
事件冒泡本身的特性,会带来的坏处,也会带来的好处。
<div class="father"> <div class="son">son⼉⼦</div>
</div>
<script>
var son = document.querySelector('.son');
// 给son注册单击事件
son.addEventListener('click', function(e) {
alert('son');
e.stopPropagation(); // stop 停⽌ Propagation 传播
window.event.cancelBubble = true; // ⾮标准 cancel 取消 bubble 泡 泡
}, false);
var father = document.querySelector('.father');
// 给father注册单击事件
father.addEventListener('click', function() {
alert('father');
}, false);
// 给document注册单击事件
document.addEventListener('click', function() {
alert('document');
})
</script>
阻⽌事件冒泡的兼容性处理
7.8 事件委托
事件冒泡本身的特性,会带来的坏处,也会带来的好处。
什么是事件委托
把事情委托给别⼈,代为处理。
事件委托也称为事件代理,在 jQuery ⾥⾯称为事件委派。
说⽩了就是,不给⼦元素注册事件,给⽗元素注册事件,把处理代码在⽗元素的事件中执⾏。
⽣活中的代理:
js事件中的代理:
事件委托的原理
给⽗元素注册事件,利⽤事件冒泡,当⼦元素的事件触发,会冒泡到⽗元素,然后去控制相应的⼦元素。
事件委托的作⽤
- 我们只操作了⼀次 DOM ,提⾼了程序的性能。
- 动态新创建的⼦元素,也拥有事件。
<ul><li>知否知否,点我应有弹框在⼿!</li>
<li>知否知否,点我应有弹框在⼿!</li>
<li>知否知否,点我应有弹框在⼿!</li>
<li>知否知否,点我应有弹框在⼿!</li>
<li>知否知否,点我应有弹框在⼿!</li>
</ul>
<script>
// 事件委托的核⼼原理:给⽗节点添加侦听器, 利⽤事件冒泡影响每⼀个⼦节点
var ul = document.querySelector('ul');
ul.addEventListener('click', function(e) {
// e.target 这个可以得到我们点击的对象
e.target.style.backgroundColor = 'pink';
})
</script>
8 - 常⽤⿏标事件
8.1 mouseenter 和mouseover的区别
- 当⿏标移动到元素上时就会触发mouseenter 事件
- 类似 mouseover,它们两者之间的差别是
- mouseover ⿏标经过⾃身盒⼦会触发,经过⼦盒⼦还会触发。mouseenter 只会经过⾃身盒⼦触发
- 之所以这样,就是因为mouseenter不会冒泡
- 跟mouseenter搭配⿏标离开 mouseleave 同样不会冒泡
8.2 禁⽌选中⽂字和禁⽌右键菜单
<body>
我是⼀段不愿意分享的⽂字
<script>
// 1. contextmenu 我们可以禁⽤右键菜单
document.addEventListener('contextmenu', function(e) {
e.preventDefault();
})
// 2. 禁⽌选中⽂字 selectstart
document.addEventListener('selectstart', function(e) {
e.preventDefault();
})
</script>
</body>
8.3 ⿏标事件对象
8.4 获取⿏标在⻚⾯的坐标
<script>
// ⿏标事件对象 MouseEvent
document.addEventListener('click', function(e) {
// 1. client ⿏标在可视区的x和y坐标
console.log(e.clientX);
console.log(e.clientY);
console.log('---------------------');
// 2. page ⿏标在⻚⾯⽂档的x和y坐标
console.log(e.pageX);
console.log(e.pageY);
console.log('---------------------');
// 3. screen ⿏标在电脑屏幕的x和y坐标
console.log(e.screenX);
console.log(e.screenY);
})
</script>
<img src="images/angel.gif" alt=""> <script>
var pic = document.querySelector('img');
document.addEventListener('mousemove', function(e) {
// 1. mousemove只要我们⿏标移动1px 就会触发这个事件
// 2.核⼼原理: 每次⿏标移动,我们都会获得最新的⿏标坐标,
// 把这个x和y坐标做为图⽚的top和left 值就可以移动图⽚
var x = e.pageX;
var y = e.pageY;
console.log('x坐标是' + x, 'y坐标是' + y);
//3 . 千万不要忘记给left 和top 添加px 单位
pic.style.left = x - 50 + 'px';
pic.style.top = y - 40 + 'px';
});
</script>
9 - 常⽤的键盘事件
9.1 键盘事件
<script>
// 常⽤的键盘事件
//1. keyup 按键弹起的时候触发
document.addEventListener('keyup', function() {
console.log('我弹起了');
})
//3. keypress 按键按下的时候触发 不能识别功能键 ⽐如 ctrl shift 左右箭头
啊
document.addEventListener('keypress', function() {
console.log('我按下了press');
})
//2. keydown 按键按下的时候触发 能识别功能键 ⽐如 ctrl shift 左右箭头啊
document.addEventListener('keydown', function() {
console.log('我按下了down');
})
// 4. 三个事件的执⾏顺序 keydown -- keypress -- keyup
</script>
9.2 键盘事件对象
使⽤keyCode属性判断⽤户按下哪个键
<script>
// 键盘事件对象中的keyCode属性可以得到相应键的ASCII码值
document.addEventListener('keyup', function(e) {
console.log('up:' + e.keyCode);
// 我们可以利⽤keycode返回的ASCII码值来判断⽤户按下了那个键
if (e.keyCode === 65) {
alert('您按下的a键');
} else {
alert('您没有按下a键')
}
})
document.addEventListener('keypress', function(e) {
// console.log(e);
console.log('press:' + e.keyCode);
})
</script>
案例:模拟京东按键输⼊内容
当我们按下 s 键, 光标就定位到搜索框(⽂本框获得焦点)。
<input type="text"> <script>
// 获取输⼊框
var search = document.querySelector('input');
// 给document注册keyup事件
document.addEventListener('keyup', function(e) {
// 判断keyCode的值
if (e.keyCode === 83) {
// 触发输⼊框的获得焦点事件
search.focus();
}
})
</script>
案例:模拟京东快递单号查询
要求:当我们在⽂本框中输⼊内容时,⽂本框上⾯⾃动显示⼤字号的内容。
<div class="search"> <div class="con">123</div>
<input type="text" placeholder="请输⼊您的快递单号" class="jd"> </div>
<script>
// 获取要操作的元素
var con = document.querySelector('.con');
var jd_input = document.querySelector('.jd');
// 给输⼊框注册keyup事件
jd_input.addEventListener('keyup', function() {
// 判断输⼊框内容是否为空
if (this.value == '') {
// 为空,隐藏放⼤提示盒⼦
con.style.display = 'none';
} else {
// 不为空,显示放⼤提示盒⼦,设置盒⼦的内容
con.style.display = 'block';
con.innerText = this.value;
}
})
// 给输⼊框注册失去焦点事件,隐藏放⼤提示盒⼦
jd_input.addEventListener('blur', function() {
con.style.display = 'none';
})
// 给输⼊框注册获得焦点事件
jd_input.addEventListener('focus', function() {
// 判断输⼊框内容是否为空
if (this.value !== '') {
// 不为空则显示提示盒⼦
con.style.display = 'block';
}
})
</script>
10 - BOM
10.1. 什么是BOM
BOM(Browser Object Model)即浏览器对象模型,它提供了独⽴于内容⽽与浏览器窗⼝进⾏交互的对象,其核⼼对象是 window。
BOM 由⼀系列相关的对象构成,并且每个对象都提供了很多⽅法与属性。
BOM 缺乏标准,JavaScript 语法的标准化组织是 ECMA,DOM 的标准化组织是 W3C,BOM 最初是Netscape 浏览器标准的⼀部分。
10.2. BOM的构成
BOM ⽐ DOM 更⼤,它包含 DOM。
10.3. 顶级对象window
10.4. window对象的常⻅事件
⻚⾯(窗⼝)加载事件(3种)
第1种
window.onload 是窗⼝ (⻚⾯)加载事件,当⽂档内容完全加载完成会触发该事件(包括图像、脚本⽂
件、CSS ⽂件等), 就调⽤的处理函数。
第2种
DOMContentLoaded 事件触发时,仅当DOM加载完成,不包括样式表,图⽚,flash等等。
IE9以上才⽀持!!!
如果⻚⾯的图⽚很多的话, 从⽤户访问到onload触发可能需要较⻓的时间, 交互效果就不能实现,必然影响⽤户的体验,此时⽤ DOMContentLoaded 事件⽐较合适。
<script>
window.addEventListener('load', function() {
var btn = document.querySelector('button');
btn.addEventListener('click', function() {
alert('点击我');
})
})
window.addEventListener('load', function() {
alert(22);
})
document.addEventListener('DOMContentLoaded', function() {
alert(33);
})
</script>
第3种
pageshow 事件,事件在⽤户浏览⽹⻚时触发。onpageshow 事件类似于 onload 事件,onload 事件在⻚⾯第⼀次加载时触发, onpageshow 事件在每次加载⻚⾯时触发,即 onload 事件在⻚⾯从浏览器缓存中读取时不触发。
调整窗⼝⼤⼩事件
window.onresize 是调整窗⼝⼤⼩加载事件, 当触发时就调⽤的处理函数。
注意:
-
只要窗⼝⼤⼩发⽣像素变化,就会触发这个事件。
-
我们经常利⽤这个事件完成响应式布局。 window.innerWidth 当前屏幕的宽度
<script>
// 注册⻚⾯加载事件
window.addEventListener('load', function() {
var div = document.querySelector('div');
// 注册调整窗⼝⼤⼩事件
window.addEventListener('resize', function() {
// window.innerWidth 获取窗⼝⼤⼩
console.log('变化了');
if (window.innerWidth <= 800) {
div.style.display = 'none';
} else {
div.style.display = 'block';
}
})
})
</script>
<div></div>
10.5. 定时器(两种)
window 对象给我们提供了 2 个⾮常好⽤的⽅法-定时器。
- setTimeout()
- setInterval()
setTimeout() 炸弹定时器
开启定时器
普通函数是按照代码顺序直接调⽤。
简单理解: 回调,就是回头调⽤的意思。上⼀件事⼲完,再回头再调⽤这个函数。
例如:定时器中的调⽤函数,事件处理函数,也是回调函数。
以前我们讲的 element.onclick = function(){} 或者
element.addEventListener(“click”, fn); ⾥⾯的 函数也是回调函数。
<script>
// 回调函数是⼀个匿名函数
setTimeout(function() {
console.log('时间到了');
}, 2000);
function callback() {
console.log('爆炸了');
}
// 回调函数是⼀个有名函数
var timer1 = setTimeout(callback, 3000);
var timer2 = setTimeout(callback, 5000);
</script>
案例:5秒后关闭⼴告
<body><img src="images/ad.jpg" alt="" class="ad"> <script>
// 获取要操作的元素
var ad = document.querySelector('.ad');
// 开启定时器
setTimeout(function() {
ad.style.display = 'none';
}, 5000);
</script>
</body>
停⽌定时器
<button>点击停⽌定时器</button>
<script>
var btn = document.querySelector('button');
// 开启定时器
var timer = setTimeout(function() {
console.log('爆炸了');
}, 5000);
// 给按钮注册单击事件
btn.addEventListener('click', function() {
// 停⽌定时器
clearTimeout(timer);
})
</script>
setInterval() 闹钟定时器
开启定时器
<script>
// 1. setInterval
setInterval(function() {
console.log('继续输出');
}, 1000);
</script>
案例:倒计时
<div><span class="hour">1</span>
<span class="minute">2</span>
<span class="second">3</span>
</div>
<script>
// 1. 获取元素(时分秒盒⼦)
var hour = document.querySelector('.hour'); // ⼩时的⿊⾊盒⼦
var minute = document.querySelector('.minute'); // 分钟的⿊⾊盒⼦
var second = document.querySelector('.second'); // 秒数的⿊⾊盒⼦
var inputTime = +new Date('2019-5-1 18:00:00'); // 返回的是⽤户输⼊时间
总的毫秒数
countDown(); // 我们先调⽤⼀次这个函数,防⽌第⼀次刷新⻚⾯有空⽩
// 2. 开启定时器
setInterval(countDown, 1000);
function countDown() {
var nowTime = +new Date(); // 返回的是当前时间总的毫秒数
var times = (inputTime - nowTime) / 1000; // times是剩余时间总的秒
数
var h = parseInt(times / 60 / 60 % 24); //时 h = h < 10 ? '0' + h : h;
hour.innerHTML = h; // 把剩余的⼩时给 ⼩时⿊⾊盒⼦
var m = parseInt(times / 60 % 60); // 分 m = m < 10 ? '0' + m : m;
minute.innerHTML = m;
var s = parseInt(times % 60); // 当前的秒
s = s < 10 ? '0' + s : s;
second.innerHTML = s;
}
</script>
停⽌定时器
案例:发送短信倒计时
点击按钮后,该按钮60秒之内不能再次点击,防⽌重复发送短信。
⼿机号码: <input type="number"> <button>发送</button>
<script>
var btn = document.querySelector('button');
// 全局变量,定义剩下的秒数
var time = 3;
// 注册单击事件
btn.addEventListener('click', function() {
// 禁⽤按钮
btn.disabled = true;
// 开启定时器
var timer = setInterval(function() {
// 判断剩余秒数
if (time == 0) {
// 清除定时器和复原按钮
clearInterval(timer);
btn.disabled = false;
btn.innerHTML = '发送';
} else {
btn.innerHTML = '还剩下' + time + '秒';
time--;
}
}, 1000);
});
</script>
10.6. this指向问题
this的指向在函数定义的时候是确定不了的,只有函数执⾏的时候才能确定this到底指向谁,⼀般情况下this的最终指向的是那个调⽤它的对象。
现阶段,我们先了解⼀下⼏个this指向
-
全局作⽤域或者普通函数中this指向全局对象window(注意定时器⾥⾯的this指向window)
-
⽅法调⽤中谁调⽤this指向谁
-
构造函数中this指向构造函数的实例
<button>点击</button>
<script>
// this 指向问题 ⼀般情况下this的最终指向的是那个调⽤它的对象
// 1. 全局作⽤域或者普通函数中this指向全局对象window( 注意定时器⾥⾯的
this指向window)
console.log(this);
function fn() {
console.log(this);
}
window.fn();
window.setTimeout(function() {
console.log(this);
}, 1000);
// 2. ⽅法调⽤中谁调⽤this指向谁
var o = {
sayHi: function() {
console.log(this); // this指向的是 o 这个对象
}
}
o.sayHi();
var btn = document.querySelector('button');
btn.addEventListener('click', function() {
console.log(this); // 事件处理函数中的this指向的是btn这个按钮对
象
})
// 3. 构造函数中this指向构造函数的实例
function Fun() {
console.log(this); // this 指向的是fun 实例对象
}
var fun = new Fun();
</script>
10.7. location对象
什么是 location 对象
URL
location 对象的属性
案例:5分钟⾃动跳转⻚⾯
<button>点击</button>
<div></div>
<script>
var btn = document.querySelector('button');
var div = document.querySelector('div');
btn.addEventListener('click', function() {
// console.log(location.href);
location.href = 'http://www.itcast.cn';
})
var timer = 5;
setInterval(function() {
if (timer == 0) {
location.href = 'http://www.itcast.cn';
} else {
div.innerHTML = '您将在' + timer + '秒钟之后跳转到⾸⻚';
timer--;
}
}, 1000);
</script>
案例:获取URL参数
<div></div>
<script>
console.log(location.search); // ?uname=andy
// 1.先去掉? substr('起始的位置',截取⼏个字符);
var params = location.search.substr(1); // uname=andy
console.log(params);
// 2. 利⽤=把字符串分割为数组 split('=');
var arr = params.split('=');
console.log(arr); // ["uname", "ANDY"]
var div = document.querySelector('div');
// 3.把数据写⼊div中
div.innerHTML = arr[1] + '欢迎您'; </script>
location对象的常⻅⽅法
<button>点击</button>
<script>
var btn = document.querySelector('button');
btn.addEventListener('click', function() {
// 记录浏览历史,所以可以实现后退功能
// location.assign('http://www.itcast.cn');
// 不记录浏览历史,所以不可以实现后退功能
// location.replace('http://www.itcast.cn');
location.reload(true);
})
</script>
10.8. navigator对象
navigator 对象包含有关浏览器的信息,它有很多属性,我们最常⽤的是 userAgent,该属性可以返回由客户机发送服务器的 user-agent 头部的值。
下⾯前端代码可以判断⽤户那个终端打开⻚⾯,实现跳转
if((navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mo
bile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Sym
bian|Windows Phone)/i))) {
window.location.href = ""; //⼿机
} else {
window.location.href = ""; //电脑
}
10.9 history对象
window对象给我们提供了⼀个 history对象,与浏览器历史记录进⾏交互。该对象包含⽤户(在浏览器
窗⼝中)访问过的URL。
history对象⼀般在实际开发中⽐较少⽤,但是会在⼀些 OA 办公系统中⻅到。
11 - JS执⾏机制
以下代码执⾏的结果是什么?
console.log(1);
setTimeout(function () {
console.log(3);
}, 1000);
console.log(2);
以下代码执⾏的结果是什么?
console.log(1);
setTimeout(function () {
console.log(3);
}, 0);
console.log(2);
11.1 JS 是单线程
单线程就意味着,所有任务需要排队,前⼀个任务结束,才会执⾏后⼀个任务。如果前⼀个任
务耗时很⻓,后⼀个任务就不得不⼀直等着。
这样所导致的问题是: 如果 JS 执⾏的时间过⻓,这样就会造成⻚⾯的渲染不连贯,导致⻚
⾯渲染加载阻塞的感觉。
11.2 同步任务和异步任务
单线程导致的问题就是后⾯的任务等待前⾯任务完成,如果前⾯任务很耗时(⽐如读取⽹络数据),后⾯任务不得不⼀直等待!!
为了解决这个问题,利⽤多核 CPU 的计算能⼒,HTML5 提出 Web Worker 标准,允许 JavaScript 脚本创建多个线程,但是⼦线程完全受主线程控制。于是,JS 中出现了同步任务和异步任务。
同步
前⼀个任务结束后再执⾏后⼀个任务,程序的执⾏顺序与任务的排列顺序是⼀致的、同步的。⽐如做饭的同步做法:我们要烧⽔煮饭,等⽔开了(10分钟之后),再去切菜,炒菜。
异步
你在做⼀件事情时,因为这件事情会花费很⻓时间,在做这件事的同时,你还可以去处理其他事情。⽐如做饭的异步做法,我们在烧⽔的同时,利⽤这10分钟,去切菜,炒菜。
JS中所有任务可以分成两种,⼀种是同步任务(synchronous),另⼀种是异步任务
(asynchronous)。
同步任务指的是:
在主线程上排队执⾏的任务,只有前⼀个任务执⾏完毕,才能执⾏后⼀个任务;
异步任务指的是:
不进⼊主线程、⽽进⼊”任务队列”的任务,当主线程中的任务运⾏完了,才会从”任务队
列”取出异步任务放⼊主线程执⾏。
11.3 JS执⾏机制(事件循环)
11.4 代码思考题
console.log(1);
document.onclick = function() {
console.log('click');
}
setTimeout(function() {
console.log(3)
}, 3000)
console.log(2);
12 - 元素偏移量 OFFSET 系列
12.1 offset 概述
offset 翻译过来就是偏移量, 我们使⽤ offset系列相关属性可以动态的得到该元素的位置(偏移)、⼤⼩等。
-
获得元素距离带有定位⽗元素的位置
-
获得元素⾃身的⼤⼩(宽度⾼度)
-
注意:返回的数值都不带单位
12.2 offset 与 style 区别
offset
- offset 可以得到任意样式表中的样式值
- offset 系列获得的数值是没有单位的
- offsetWidth 包含padding+border+width
- offsetWidth 等属性是只读属性,只能获取不能赋值
- 所以,我们想要获取元素⼤⼩位置,⽤offset更合适
style
- style 只能得到⾏内样式表中的样式值
- style.width 获得的是带有单位的字符串
- style.width 获得不包含padding和border 的值
- style.width 是可读写属性,可以获取也可以赋值
- 所以,我们想要给元素更改值,则需要⽤style改变
- 因为平时我们都是给元素注册触摸事件,所以重点记住 targetTocuhes
案例:获取⿏标在盒⼦内的坐标
-
我们在盒⼦内点击,想要得到⿏标距离盒⼦左右的距离。
-
⾸先得到⿏标在⻚⾯中的坐标(e.pageX, e.pageY)
-
其次得到盒⼦在⻚⾯中的距离 ( box.offsetLeft, box.offsetTop)
-
⽤⿏标距离⻚⾯的坐标减去盒⼦在⻚⾯中的距离,得到 ⿏标在盒⼦内的坐标
-
如果想要移动⼀下⿏标,就要获取最新的坐标,使⽤⿏标移动
var box = document.querySelector('.box');
box.addEventListener('mousemove', function(e) {
var x = e.pageX - this.offsetLeft;
var y = e.pageY - this.offsetTop;
this.innerHTML = 'x坐标是' + x + ' y坐标是' + y;
})
案例:模态框拖拽
弹出框,我们也称为模态框。
1.点击弹出层,会弹出模态框, 并且显示灰⾊半透明的遮挡层。
2.点击关闭按钮,可以关闭模态框,并且同时关闭灰⾊半透明遮挡层。
3.⿏标放到模态框最上⾯⼀⾏,可以按住⿏标拖拽模态框在⻚⾯中移动。
4.⿏标松开,可以停⽌拖动模态框移动
案例分析:
-
点击弹出层, 模态框和遮挡层就会显示出来 display:block;
-
点击关闭按钮,模态框和遮挡层就会隐藏起来 display:none;
-
在⻚⾯中拖拽的原理:⿏标按下并且移动, 之后松开⿏标
-
触发事件是⿏标按下mousedown,⿏标移动mousemove ⿏标松开 mouseup
-
拖拽过程: ⿏标移动过程中,获得最新的值赋值给模态框的left和top值,这样模态框可以跟着⿏标⾛了
-
⿏标按下触发的事件源是最上⾯⼀⾏,就是 id 为 title
-
⿏标的坐标减去 ⿏标在盒⼦内的坐标, 才是模态框真正的位置。
-
⿏标按下,我们要得到⿏标在盒⼦的坐标。
-
⿏标移动,就让模态框的坐标 设置为 :⿏标坐标 减去盒⼦坐标即可,注意移动事件写到按下事件⾥⾯。
-
⿏标松开,就停⽌拖拽,就是可以让⿏标移动事件解除
// 1. 获取元素
var login = document.querySelector('.login');
var mask = document.querySelector('.login-bg');
var link = document.querySelector('#link');
var closeBtn = document.querySelector('#closeBtn');
var title = document.querySelector('#title');
// 2. 点击弹出层这个链接 link 让mask 和login 显示出来
link.addEventListener('click', function() {
mask.style.display = 'block';
login.style.display = 'block';
})
// 3. 点击 closeBtn 就隐藏 mask 和 login
closeBtn.addEventListener('click', function() {
mask.style.display = 'none';
login.style.display = 'none';
})
// 4. 开始拖拽
// (1) 当我们⿏标按下, 就获得⿏标在盒⼦内的坐标
title.addEventListener('mousedown', function(e) {
var x = e.pageX - login.offsetLeft;
var y = e.pageY - login.offsetTop;
// (2) ⿏标移动的时候,把⿏标在⻚⾯中的坐标,减去 ⿏标在盒⼦内的坐标就
是模态框的left和top值
document.addEventListener('mousemove', move)
function move(e) {
login.style.left = e.pageX - x + 'px';
login.style.top = e.pageY - y + 'px';
}
// (3) ⿏标弹起,就让⿏标移动事件移除
document.addEventListener('mouseup', function() {
document.removeEventListener('mousemove', move);
})
})
案例:仿京东放⼤镜
-
整个案例可以分为三个功能模块
-
⿏标经过⼩图⽚盒⼦, ⻩⾊的遮挡层 和 ⼤图⽚盒⼦显示,离开隐藏2个盒⼦功能
-
⻩⾊的遮挡层跟随⿏标功能。
-
移动⻩⾊遮挡层,⼤图⽚跟随移动功能。
案例分析:
-
⻩⾊的遮挡层跟随⿏标功能。
-
把⿏标坐标给遮挡层不合适。因为遮挡层坐标以⽗盒⼦为准。
-
⾸先是获得⿏标在盒⼦的坐标。
-
之后把数值给遮挡层做为left 和top值。
-
此时⽤到⿏标移动事件,但是还是在⼩图⽚盒⼦内移动。
-
发现,遮挡层位置不对,需要再减去盒⼦⾃身⾼度和宽度的⼀半。
-
遮挡层不能超出⼩图⽚盒⼦范围。
-
如果⼩于零,就把坐标设置为0
-
如果⼤于遮挡层最⼤的移动距离,就把坐标设置为最⼤的移动距离
-
遮挡层的最⼤移动距离:⼩图⽚盒⼦宽度 减去 遮挡层盒⼦宽度
window.addEventListener('load', function() {
var preview_img = document.querySelector('.preview_img');
var mask = document.querySelector('.mask');
var big = document.querySelector('.big');
// 1. 当我们⿏标经过 preview_img 就显示和隐藏 mask 遮挡层 和 big ⼤盒⼦
preview_img.addEventListener('mouseover', function() {
mask.style.display = 'block';
big.style.display = 'block';
})
preview_img.addEventListener('mouseout', function() {
mask.style.display = 'none';
big.style.display = 'none';
})
// 2. ⿏标移动的时候,让⻩⾊的盒⼦跟着⿏标来⾛
preview_img.addEventListener('mousemove', function(e) {
// (1). 先计算出⿏标在盒⼦内的坐标
var x = e.pageX - this.offsetLeft;
var y = e.pageY - this.offsetTop;
// console.log(x, y);
// (2) 减去盒⼦⾼度 300的⼀半 是 150 就是我们mask 的最终 left 和top值了
// (3) 我们mask 移动的距离
var maskX = x - mask.offsetWidth / 2;
var maskY = y - mask.offsetHeight / 2;
// (4) 如果x 坐标⼩于了0 就让他停在0 的位置
// 遮挡层的最⼤移动距离
var maskMax = preview_img.offsetWidth - mask.offsetWidth;
if (maskX <= 0) {
maskX = 0;
} else if (maskX >= maskMax) {
maskX = maskMax;
}
if (maskY <= 0) {
maskY = 0;
} else if (maskY >= maskMax) {
maskY = maskMax;
}
mask.style.left = maskX + 'px';
mask.style.top = maskY + 'px';
// 3. ⼤图⽚的移动距离 = 遮挡层移动距离 * ⼤图⽚最⼤移动距离 / 遮挡层的最⼤
移动距离
// ⼤图
var bigIMg = document.querySelector('.bigImg');
// ⼤图⽚最⼤移动距离
var bigMax = bigIMg.offsetWidth - big.offsetWidth;
// ⼤图⽚的移动距离 X Y
var bigX = maskX * bigMax / maskMax;
var bigY = maskY * bigMax / maskMax;
bigIMg.style.left = -bigX + 'px';
bigIMg.style.top = -bigY + 'px';
})
})
13 - 元素可视区 CLIENT 系列
13.1 client概述
client 翻译过来就是客户端,我们使⽤ client 系列的相关属性来获取元素可视区的相关信息。通过client 系列的相关属性可以动态的得到该元素的边框⼤⼩、元素⼤⼩等。
13.2. 淘宝 flexible.js 源码分析
⽴即执⾏函数 (function(){})() 或者 (function(){}())
主要作⽤: 创建⼀个独⽴的作⽤域。 避免了命名冲突问题
下⾯三种情况都会刷新⻚⾯都会触发 load 事件。
1.a标签的超链接
2.F5或者刷新按钮(强制刷新)
3.前进后退按钮
但是 ⽕狐中,有个特点,有个“往返缓存”,这个缓存中不仅保存着⻚⾯数据,还保存了DOM和JavaScript的状态;实际上是将整个⻚⾯都保存在了内存⾥。
所以此时后退按钮不能刷新⻚⾯。
此时可以使⽤ pageshow事件来触发。,这个事件在⻚⾯显示时触发,⽆论⻚⾯是否来⾃缓存。在重新加载⻚⾯中,pageshow会在load事件触发后触发;根据事件对象中的persisted来判断是否是缓存中的⻚⾯触发的pageshow事件
注意这个事件给window添加。
14 - 元素滚动 SCROLL 系列
14.1. scroll 概述
scroll 翻译过来就是滚动的,我们使⽤ scroll 系列的相关属性可以动态的得到该元素的⼤⼩、滚动距离
等。
14.2. ⻚⾯被卷去的头部
如果浏览器的⾼(或宽)度不⾜以显示整个⻚⾯时,会⾃动出现滚动条。当滚动条向下滚动时,⻚⾯上⾯被隐藏掉的⾼度,我们就称为⻚⾯被卷去的头部。滚动条在滚动时会触发 onscroll事件。
查看⻚⾯被卷出的部分⽤ window.pageYoffset 和 window.pageXoffset 来 查 看 , 和
scrollTop 、 scrollLeft 的区别是⼀个是针对⻚⾯ document 的⼀个是针对⻚⾯元素的。
案例:仿淘宝固定右侧侧边栏
-
原先侧边栏是绝对定位
-
当⻚⾯滚动到⼀定位置,侧边栏改为固定定位
-
⻚⾯继续滚动,会让 返回顶部显示出来
案例分析:
-
需要⽤到⻚⾯滚动事件 scroll 因为是⻚⾯滚动,所以事件源是document
-
滚动到某个位置,就是判断⻚⾯被卷去的上部值。
-
⻚ ⾯ 被 卷 去 的 头 部 : 可 以 通 过 window.pageYOffset 获 得 如果是被卷去的左侧window.pageXOffset
-
注 意 , 元 素 被 卷 去 的 头 部 是 element.scrollTop , 如果是⻚⾯被卷去的头部 则 是window.pageYOffset
-
其实这个值 可以通过盒⼦的 offsetTop可以得到,如果⼤于等于这个值,就可以让盒⼦固定定位了
//1. 获取元素
var sliderbar = document.querySelector('.slider-bar');
var banner = document.querySelector('.banner');
// banner.offestTop 就是被卷去头部的⼤⼩ ⼀定要写到滚动的外⾯
var bannerTop = banner.offsetTop
// 当我们侧边栏固定定位之后应该变化的数值
var sliderbarTop = sliderbar.offsetTop - bannerTop;
// 获取main 主体元素
var main = document.querySelector('.main');
var goBack = document.querySelector('.goBack');
var mainTop = main.offsetTop;
// 2. ⻚⾯滚动事件 scroll
document.addEventListener('scroll', function() {
// console.log(11);
// window.pageYOffset ⻚⾯被卷去的头部
// console.log(window.pageYOffset);
// 3 .当我们⻚⾯被卷去的头部⼤于等于了 172 此时 侧边栏就要改为固定定位
if (window.pageYOffset >= bannerTop) {
sliderbar.style.position = 'fixed';
sliderbar.style.top = sliderbarTop + 'px';
} else {
sliderbar.style.position = 'absolute';
sliderbar.style.top = '300px';
}
// 4. 当我们⻚⾯滚动到main盒⼦,就显示 goback模块
if (window.pageYOffset >= mainTop) {
goBack.style.display = 'block';
} else {
goBack.style.display = 'none';
}
})
14.3. ⻚⾯被卷去的头部兼容性解决⽅案
需要注意的是,⻚⾯被卷去的头部,有兼容性问题,因此被卷去的头部通常有如下⼏种写法:
-
声明了 DTD,使⽤ document.documentElement.scrollTop
-
未声明 DTD,使⽤ document.body.scrollTop
-
新⽅法 window.pageYOffset和 window.pageXOffset,IE9 开始⽀持
function getScroll() {
return {
left: window.pageXOffset || document.documentElement.scrollLeft ||
document.body.scrollLeft||0,
top: window.pageYOffset || document.documentElement.scrollTop ||
document.body.scrollTop || 0
};
}
使⽤的时候 getScroll().left
15 - 三⼤系列总结
他们主要⽤法:
1.offset系列 经常⽤于获得元素位置 offsetLeft offsetTop
2.client经常⽤于获取元素⼤⼩ clientWidth clientHeight
3.scroll 经常⽤于获取滚动距离 scrollTop scrollLeft
4.注意⻚⾯滚动的距离通过 window.pageXOffset 获得
16 - 动画函数封装
动画实现原理
核⼼原理:通过定时器 setInterval() 不断移动盒⼦位置。
实现步骤:
-
获得盒⼦当前位置
-
让盒⼦在当前位置加上1个移动距离
-
利⽤定时器不断重复这个操作
-
加⼀个结束定时器的条件
-
注意此元素需要添加定位,才能使⽤element.style.left
动画函数给不同元素记录不同定时器
如果多个元素都使⽤这个动画函数,每次都要var 声明定时器。我们可以给不同的元素使⽤不同的定时器(⾃⼰专⻔⽤⾃⼰的定时器)。
核⼼原理:利⽤ JS 是⼀⻔动态语⾔,可以很⽅便的给当前对象添加属性。
function animate(obj, target) {
// 当我们不断的点击按钮,这个元素的速度会越来越快,因为开启了太多的定时
器
// 解决⽅案就是 让我们元素只有⼀个定时器执⾏
// 先清除以前的定时器,只保留当前的⼀个定时器执⾏
clearInterval(obj.timer);
obj.timer = setInterval(function() {
if (obj.offsetLeft >= target) {
// 停⽌动画 本质是停⽌定时器
clearInterval(obj.timer);
}
obj.style.left = obj.offsetLeft + 1 + 'px';
}, 30);
}
缓动效果原理
缓动动画就是让元素运动速度有所变化,最常⻅的是让速度慢慢停下来
思路:
-
让盒⼦每次移动的距离慢慢变⼩,速度就会慢慢落下来。
-
核⼼算法: (⽬标值 - 现在的位置) / 10 做为每次移动的距离步⻓
-
停⽌的条件是: 让当前盒⼦位置等于⽬标位置就停⽌定时器
-
注意步⻓值需要取整
动画函数多个⽬标值之间移动
可以让动画函数从 800 移动到 500。
当我们点击按钮时候,判断步⻓是正值还是负值
1.如果是正值,则步⻓往⼤了取整
2.如果是负值,则步⻓ 向⼩了取整
动函数添加回调函数
回调函数原理:函数可以作为⼀个参数。将这个函数作为参数传到另⼀个函数⾥⾯,当那个函数执⾏完之后,再执⾏传进去的这个函数,这个过程就叫做回调。
回调函数写的位置:定时器结束的位置。
动画完整版代码
function animate(obj, target, callback) {
// console.log(callback); callback = function() {} 调⽤的时候 callback()
// 先清除以前的定时器,只保留当前的⼀个定时器执⾏
clearInterval(obj.timer);
obj.timer = setInterval(function() {
// 步⻓值写到定时器的⾥⾯
// 把我们步⻓值改为整数 不要出现⼩数的问题
// var step = Math.ceil((target - obj.offsetLeft) / 10);
var step = (target - obj.offsetLeft) / 10;
step = step > 0 ? Math.ceil(step) : Math.floor(step);
if (obj.offsetLeft == target) {
// 停⽌动画 本质是停⽌定时器
clearInterval(obj.timer);
// 回调函数写到定时器结束⾥⾯
// if (callback) {
// // 调⽤函数
// callback();
// }
callback && callback();
}
// 把每次加1 这个步⻓值改为⼀个慢慢变⼩的值 步⻓公式:(⽬标值 - 现在的位
置) / 10
obj.style.left = obj.offsetLeft + step + 'px';
}, 15);
}
17 - 常⻅⽹⻚特效案例
17.1. ⽹⻚轮播图
轮播图也称为焦点图,是⽹⻚中⽐较常⻅的⽹⻚特效。功能需求:
1.⿏标经过轮播图模块,左右按钮显示,离开隐藏左右按钮。
2.点击右侧按钮⼀次,图⽚往左播放⼀张,以此类推,左侧按钮同理。
3.图⽚播放的同时,下⾯⼩圆圈模块跟随⼀起变化。
4.点击⼩圆圈,可以播放相应图⽚。
5.⿏标不经过轮播图,轮播图也会⾃动播放图⽚。
6.⿏标经过,轮播图模块, ⾃动播放停⽌。
window.addEventListener('load', function() {
// 1. 获取元素
var arrow_l = document.querySelector('.arrow-l');
var arrow_r = document.querySelector('.arrow-r');
var focus = document.querySelector('.focus');
var focusWidth = focus.offsetWidth;
// 2. ⿏标经过focus 就显示隐藏左右按钮
focus.addEventListener('mouseenter', function() {
arrow_l.style.display = 'block';
arrow_r.style.display = 'block';
clearInterval(timer);
timer = null; // 清除定时器变量
});
focus.addEventListener('mouseleave', function() {
arrow_l.style.display = 'none';
arrow_r.style.display = 'none';
timer = setInterval(function() {
//⼿动调⽤点击事件
arrow_r.click();
}, 2000);
});
// 3. 动态⽣成⼩圆圈 有⼏张图⽚,我就⽣成⼏个⼩圆圈
var ul = focus.querySelector('ul');
var ol = focus.querySelector('.circle');
// console.log(ul.children.length);
for (var i = 0; i < ul.children.length; i++) {
// 创建⼀个⼩li
var li = document.createElement('li');
// 记录当前⼩圆圈的索引号 通过⾃定义属性来做
li.setAttribute('index', i);
// 把⼩li插⼊到ol ⾥⾯
ol.appendChild(li);
// 4. ⼩圆圈的排他思想 我们可以直接在⽣成⼩圆圈的同时直接绑定点击事件
li.addEventListener('click', function() {
// ⼲掉所有⼈ 把所有的⼩li 清除 current 类名
for (var i = 0; i < ol.children.length; i++) {
ol.children[i].className = '';
}
// 留下我⾃⼰ 当前的⼩li 设置current 类名
this.className = 'current';
// 5. 点击⼩圆圈,移动图⽚ 当然移动的是 ul
// ul 的移动距离 ⼩圆圈的索引号 乘以 图⽚的宽度 注意是负值
// 当我们点击了某个⼩li 就拿到当前⼩li 的索引号
var index = this.getAttribute('index');
// 当我们点击了某个⼩li 就要把这个li 的索引号给 num
num = index;
// 当我们点击了某个⼩li 就要把这个li 的索引号给 circle
circle = index;
// num = circle = index;
console.log(focusWidth);
console.log(index);
animate(ul, -index * focusWidth);
})
}
// 把ol⾥⾯的第⼀个⼩li设置类名为 current
ol.children[0].className = 'current';
// 6. 克隆第⼀张图⽚(li)放到ul 最后⾯
var first = ul.children[0].cloneNode(true);
ul.appendChild(first);
// 7. 点击右侧按钮, 图⽚滚动⼀张
var num = 0;
// circle 控制⼩圆圈的播放
var circle = 0;
// flag 节流阀
var flag = true;
arrow_r.addEventListener('click', function() {
if (flag) {
flag = false; // 关闭节流阀
// 如果⾛到了最后复制的⼀张图⽚,此时 我们的ul 要快速复原 left 改为 0
if (num == ul.children.length - 1) {
ul.style.left = 0;
num = 0;
}
num++;
animate(ul, -num * focusWidth, function() {
flag = true; // 打开节流阀
});
// 8. 点击右侧按钮,⼩圆圈跟随⼀起变化 可以再声明⼀个变量控制⼩圆圈的播
放
circle++;
// 如果circle == 4 说明⾛到最后我们克隆的这张图⽚了 我们就复原
if (circle == ol.children.length) {
circle = 0;
}
// 调⽤函数
circleChange();
}
});
// 9. 左侧按钮做法
arrow_l.addEventListener('click', function() {
if (flag) {
flag = false;
if (num == 0) {
num = ul.children.length - 1;
ul.style.left = -num * focusWidth + 'px';
}
num--;
animate(ul, -num * focusWidth, function() {
flag = true;
});
// 点击左侧按钮,⼩圆圈跟随⼀起变化 可以再声明⼀个变量控制⼩圆圈的播放
circle--;
// 如果circle < 0 说明第⼀张图⽚,则⼩圆圈要改为第4个⼩圆圈(3)
// if (circle < 0) {
// circle = ol.children.length - 1;
// }
circle = circle < 0 ? ol.children.length - 1 : circle;
// 调⽤函数
circleChange();
}
});
function circleChange() {
// 先清除其余⼩圆圈的current类名
for (var i = 0; i < ol.children.length; i++) {
ol.children[i].className = '';
}
// 留下当前的⼩圆圈的current类名
ol.children[circle].className = 'current';
}
// 10. ⾃动播放轮播图
var timer = setInterval(function() {
//⼿动调⽤点击事件
arrow_r.click();
}, 2000);
})
17.2. 节流阀
防⽌轮播图按钮连续点击造成播放过快。
节流阀⽬的:当上⼀个函数动画内容执⾏完毕,再去执⾏下⼀个函数动画,让事件⽆法连续触发。
核⼼实现思路:利⽤回调函数,添加⼀个变量来控制,锁住函数和解锁函数。
开始设置⼀个变量var flag= true;If(flag){flag = false; do something} 关闭⽔⻰头
利⽤回调函数动画执⾏完毕, flag = true 打开⽔⻰头
17.3. 返回顶部
-
带有动画的返回顶部
-
此时可以继续使⽤我们封装的动画函数
-
只需要把所有的left 相关的值改为 跟 ⻚⾯垂直滚动距离相关就可以了
-
⻚⾯滚动了多少,可以通过 window.pageYOffset 得到
-
最后是⻚⾯滚动,使⽤ window.scroll(x,y)
//1. 获取元素
var sliderbar = document.querySelector('.slider-bar');
var banner = document.querySelector('.banner');
// banner.offestTop 就是被卷去头部的⼤⼩ ⼀定要写到滚动的外⾯
var bannerTop = banner.offsetTop
// 当我们侧边栏固定定位之后应该变化的数值
var sliderbarTop = sliderbar.offsetTop - bannerTop;
// 获取main 主体元素
var main = document.querySelector('.main');
var goBack = document.querySelector('.goBack');
var mainTop = main.offsetTop;
// 2. ⻚⾯滚动事件 scroll
document.addEventListener('scroll', function() {
// console.log(11);
// window.pageYOffset ⻚⾯被卷去的头部
// console.log(window.pageYOffset);
// 3 .当我们⻚⾯被卷去的头部⼤于等于了 172 此时 侧边栏就要改为固定
定位
if (window.pageYOffset >= bannerTop) {
sliderbar.style.position = 'fixed';
sliderbar.style.top = sliderbarTop + 'px';
} else {
sliderbar.style.position = 'absolute';
sliderbar.style.top = '300px';
}
// 4. 当我们⻚⾯滚动到main盒⼦,就显示 goback模块
if (window.pageYOffset >= mainTop) {
goBack.style.display = 'block';
} else {
goBack.style.display = 'none';
}
})
// 3. 当我们点击了返回顶部模块,就让窗⼝滚动的⻚⾯的最上⽅
goBack.addEventListener('click', function() {
// ⾥⾯的x和y 不跟单位的 直接写数字即可
// window.scroll(0, 0);
// 因为是窗⼝滚动 所以对象是window
animate(window, 0);
});
案例:筋头云案例
-
利⽤动画函数做动画效果
-
原先筋⽃云的起始位置是0
-
⿏标经过某个⼩li,把当前⼩li的offsetLeft 位置做为⽬标值即可
-
⿏标离开某个⼩li,就把⽬标值设为 0
-
如果点击了某个⼩li, 就把li当前的位置存储起来,做为筋⽃云的起始位置
window.addEventListener('load', function() {
// 1. 获取元素
var cloud = document.querySelector('.cloud');
var c_nav = document.querySelector('.c-nav');
var lis = c_nav.querySelectorAll('li');
// 2. 给所有的⼩li绑定事件
// 这个current 做为筋⽃云的起始位置
var current = 0;
for (var i = 0; i < lis.length; i++) {
// (1) ⿏标经过把当前⼩li 的位置做为⽬标值
lis[i].addEventListener('mouseenter', function() {
animate(cloud, this.offsetLeft);
});
// (2) ⿏标离开就回到起始的位置
lis[i].addEventListener('mouseleave', function() {
animate(cloud, current);
});
// (3) 当我们⿏标点击,就把当前位置做为⽬标值
lis[i].addEventListener('click', function() {
current = this.offsetLeft;
});
}
})
18 - 触屏事件
18.1. 触屏事件概述
移动端浏览器兼容性较好,我们不需要考虑以前 JS 的兼容性问题,可以放⼼的使⽤原⽣ JS 书写效果,但是移动端也有⾃⼰独特的地⽅。⽐如触屏事件 touch(也称触摸事件),Android和 IOS 都有。
touch 对象代表⼀个触摸点。触摸点可能是⼀根⼿指,也可能是⼀根触摸笔。触屏事件可响应⽤户⼿指(或触控笔)对屏幕或者触控板操作。
常⻅的触屏事件如下:
18.2. 触摸事件对象(TouchEvent)
TouchEvent 是⼀类描述⼿指在触摸平⾯(触摸屏、触摸板等)的状态变化的事件。这类事件⽤于描述⼀个或多个触点,使开发者可以检测触点的移动,触点的增加和减少,等等
touchstart、touchmove、touchend 三个事件都会各⾃有事件对象。
触摸事件对象重点我们看三个常⻅对象列表:
因为平时我们都是给元素注册触摸事件,所以重点记住 targetTocuhes
案例:移动端拖动元素
-
touchstart、touchmove、touchend可以实现拖动元素
-
但是拖动元素需要当前⼿指的坐标值 我们可以使⽤ targetTouches[0] ⾥⾯的pageX 和pageY
-
移动端拖动的原理: ⼿指移动中,计算出⼿指移动的距离。然后⽤盒⼦原来的位置 + ⼿指移动的距离
-
⼿指移动的距离: ⼿指滑动中的位置 减去 ⼿指刚开始触摸的位置
拖动元素三步曲:
(1) 触摸元素 touchstart: 获取⼿指初始坐标,同时获得盒⼦原来的位置
(2) 移动⼿指 touchmove: 计算⼿指的滑动距离,并且移动盒⼦
(3) 离开⼿指 touchend:
注意: ⼿指移动也会触发滚动屏幕所以这⾥要阻⽌默认的屏幕滚动 e.preventDefault();
19 - 移动端常⻅特效
19.1 移动轮播图
移动端轮播图功能和基本PC端⼀致。
-
可以⾃动播放图⽚
-
⼿指可以拖动播放轮播图
案例分析:
-
⾃动播放功能
-
开启定时器
-
移动端移动,可以使⽤translate 移动
-
想要图⽚优雅的移动,请添加过渡效果
-
⾃动播放功能-⽆缝滚动
-
注意,我们判断条件是要等到图⽚滚动完毕再去判断,就是过渡完成后判断
-
此时需要添加检测过渡完成事件 transitionend
-
判断条件:如果索引号等于 3 说明⾛到最后⼀张图⽚,此时 索引号要复原为 0
-
此时图⽚,去掉过渡效果,然后移动
-
如果索引号⼩于0, 说明是倒着⾛, 索引号等于2
-
此时图⽚,去掉过渡效果,然后移动
20 - CLASSLIST 属性
classList属性是HTML5新增的⼀个属性,返回元素的类名。但是ie10以上版本⽀持。
该属性⽤于在元素中添加,移除及切换 CSS 类。有以下⽅法
添加类:
element.classList.add(’类名’);
focus.classList.add('current');
移除类:
element.classList.remove(’类名’);
focus.classList.remove('current');
切换类:
element.classList.toggle(’类名’);
focus.classList.toggle('current');
注意:以上⽅法⾥⾯,所有类名都不带点
案例分析
-
⼩圆点跟随变化效果
-
把ol⾥⾯li带有current类名的选出来去掉类名 remove
-
让当前索引号的⼩li 加上 current add
-
但是,是等着过渡结束之后变化,所以这个写到 transitionend 事件⾥⾯
-
⼿指滑动轮播图
-
本质就是ul跟随⼿指移动,简单说就是移动端拖动元素
-
触摸元素touchstart: 获取⼿指初始坐标
-
移动⼿指touchmove: 计算⼿指的滑动距离,并且移动盒⼦
-
离开⼿指touchend: 根据滑动的距离分不同的情况
-
如果移动距离⼩于某个像素 就回弹原来位置
-
如果移动距离⼤于某个像素就上⼀张下⼀张滑动。
-
滑动也分为左滑动和右滑动判断的标准是 移动距离正负 如果是负值就是左滑 反之右滑
-
如果是左滑就播放下⼀张 (index++)
-
如果是右滑就播放上⼀张 (index--)
案例:返回顶部
当⻚⾯滚动某个地⽅,就显示,否则隐藏
点击可以返回顶部
案例分析
-
滚动某个地⽅显示
-
事件:scroll⻚⾯滚动事件
-
如果被卷去的头部(window.pageYOffset )⼤于某个数值
-
点击,window.scroll(0,0) 返回顶部
21 - CLICK 延时解决⽅案
移动端 click 事件会有 300ms 的延时,原因是移动端屏幕双击会缩放(double tap to zoom) ⻚⾯。
解决⽅案:
- 禁⽤缩放。 浏览器禁⽤默认的双击缩放⾏为并且去掉300ms 的点击延迟。
<meta name="viewport" content="user-scalable=no">
2.利⽤touch事件⾃⼰封装这个事件解决300ms 延迟。
原理就是:
-
当我们⼿指触摸屏幕,记录当前触摸时间
-
当我们⼿指离开屏幕, ⽤离开的时间减去触摸的时间
-
如果时间⼩于150ms,并且没有滑动过屏幕, 那么我们就定义为点击
-
代码如下:
//封装tap,解决click 300ms 延时
function tap (obj, callback) {
var isMove = false;
var startTime = 0; // 记录触摸时候的时间变量
obj.addEventListener('touchstart', function (e) {
startTime = Date.now(); // 记录触摸时间
});
obj.addEventListener('touchmove', function (e) {
isMove = true; // 看看是否有滑动,有滑动算拖拽,不算点击
});
obj.addEventListener('touchend', function (e) {
if (!isMove && (Date.now() - startTime) < 150) { // 如果⼿指触摸
和离开时间⼩于150ms 算点击
callback && callback(); // 执⾏回调函数
}
isMove = false; // 取反 重置
startTime = 0;
});
}
//调⽤
tap(div, function(){ // 执⾏代码 });
- 使⽤插件。fastclick 插件解决300ms 延迟。
22 - 移动端常⽤开发插件
22.1. 什么是插件
移动端要求的是快速开发,所以我们经常会借助于⼀些插件来帮我完成操作,那么什么是插件呢?
JS 插件是 js ⽂件,它遵循⼀定规范编写,⽅便程序展示效果,拥有特定功能且⽅便调⽤。如轮播图和瀑
布流插件。
特点:它⼀般是为了解决某个问题⽽专⻔存在,其功能单⼀,并且⽐较⼩。
我们以前写的animate.js 也算⼀个最简单的插件
fastclick 插件解决 300ms 延迟。 使⽤延时
GitHub官⽹地址: https://github.com/ftlabs/fastclick
22.2. 插件的使⽤
-
引⼊ js 插件⽂件。
-
按照规定语法使⽤。
-
fastclick 插件解决 300ms 延迟。 使⽤延时
-
GitHub官⽹地址: https://github.com/ftlabs/fastclick
if ('addEventListener' in document) {
document.addEventListener('DOMContentLoaded', function() {
FastClick.attach(document.body);
}, false);
}
22.3. Swiper 插件的使⽤
中⽂官⽹地址: https://www.swiper.com.cn/
1. 引⼊插件相关⽂件。
2. 按照规定语法使⽤
22.4. 其他移动端常⻅插件
lsuperslide: http://www.superslide2.com/
l iscroll: https://github.com/cubiq/iscroll
22.5. 插件的使⽤总结
1.确认插件实现的功能
2.去官⽹查看使⽤说明
3.下载插件
4.打开demo实例⽂件,查看需要引⼊的相关⽂件,并且引⼊
5.复制demo实例⽂件中的结构html,样式css以及js代码
22.6. 移动端视频插件 zy.media.js
H5 给我们提供了 video 标签,但是浏览器的⽀持情况不同。
不同的视频格式⽂件,我们可以通过source解决。
但是外观样式,还有暂停,播放,全屏等功能我们只能⾃⼰写代码解决。
这个时候我们可以使⽤插件⽅式来制作。
我们可以通过 JS 修改元素的⼤⼩、颜⾊、位置等样式。
23 - 移动端常⽤开发框架
23.1. 移动端视频插件 zy.media.js
框架,顾名思义就是⼀套架构,它会基于⾃身的特点向⽤户提供⼀套较为完整的解决⽅案。框架的控制
权在框架本身,使⽤者要按照框架所规定的某种规范进⾏开发。插件⼀般是为了解决某个问题⽽专⻔存在,其功能单⼀,并且⽐较⼩。
前端常⽤的框架有 Bootstrap、Vue、Angular、React 等。既能开发PC端,也能开发移动端
前端常⽤的移动端插件有 swiper、superslide、iscroll等。
框架: ⼤⽽全,⼀整套解决⽅案
插件: ⼩⽽专⼀,某个功能的解决⽅案
23.2. Bootstrap
Bootstrap 是⼀个简洁、直观、强悍的前端开发框架,它让 web 开发更迅速、简单。
它能开发PC端,也能开发移动端
Bootstrap JS插件使⽤步骤:
1.引⼊相关js ⽂件
2.复制HTML 结构
3.修改对应样式
4.修改相应JS 参数
24 - 本地存储
随着互联⽹的快速发展,基于⽹⻚的应⽤越来越普遍,同时也变的越来越复杂,为了满⾜各种各样的需
求,会经常性在本地存储⼤量的数据,HTML5规范提出了相关解决⽅案。
24.1.本地存储特性
1、数据存储在⽤户浏览器中
2、设置、读取⽅便、甚⾄⻚⾯刷新不丢失数据
3、容量较⼤,sessionStorage约5M、localStorage约20M
4、只能存储字符串,可以将对象JSON.stringify() 编码后存储
24.2.window.sessionStorage
1、⽣命周期为关闭浏览器窗⼝
2、在同⼀个窗⼝(⻚⾯)下数据可以共享
3、以键值对的形式存储使⽤
存储数据:
sessionStorage.setItem(key, value)
获取数据:
sessionStorage.getItem(key)
删除数据:
sessionStorage.removeItem(key)
清空数据:(所有都清除掉)
sessionStorage.clear()
24.3.window.localStorage
1、声明周期永久⽣效,除⾮⼿动删除 否则关闭⻚⾯也会存在
2、可以多窗⼝(⻚⾯)共享(同⼀浏览器可以共享)
3,以键值对的形式存储使⽤
存储数据:
localStorage.setItem(key, value)
获取数据:
localStorage.getItem(key)
删除数据:
localStorage.removeItem(key)
清空数据:(所有都清除掉)
localStorage.clear()
案例:记住⽤户名
如果勾选记住⽤户名, 下次⽤户打开浏览器,就在⽂本框⾥⾯⾃动显示上次登录的⽤户名
案例分析
-
把数据存起来,⽤到本地存储
-
关闭⻚⾯,也可以显示⽤户名,所以⽤到localStorage
-
打开⻚⾯,先判断是否有这个⽤户名,如果有,就在表单⾥⾯显示⽤户名,并且勾选复选框
-
当复选框发⽣改变的时候change事件
-
如果勾选,就存储,否则就移除