1 - Web APIs
学习目标:
能够通过ID来获取元素
能够通过标签名来获取元素
能够通过class来获取元素
能够通过选择器来获取元素
能够获取body和html元素
能够给元素注册事件
能够修改元素的内容
能够区分innerText和innerHTML的区别
能够修改像div这类普通元素的属性
能够修改表单元素的属性
能够修改元素的样式属性
1. Web API介绍
1.1 API的概念
- API(Application Programming Interface,应用程序编程接口)是一些预先定义的函数
- 目的是提供应用程序与开发人员基于某软件或硬件得以访问一组例程的能力
- 而又无需访问源码,无需理解其内部工作机制细节,只需直接调用使用即可。
- 举例解释什么是API,例如:
- C语言中有一个函数 fopen()可以打开硬盘上的文件,这个函数对于我们来说,就是一个C语言提供的打开文件的工具。
- javascript中有一个函数alert()可以在页面弹一个提示框,这个函数就是js提供的一个弹框工具。
- 这些工具(函数)由编程语言提供,内部的实现已经封装好了,我们只要学会灵活的使用这些工具即可。
- 总结:API : 接口,工具,方法/属性
- Math,Date,Array,String : 工具类,提供的是方法和属性
- 接口:电脑上各种插口,有充电的插口,插usb的插口,插耳机的插口,这些插口就可以理解为接口
- 如果想充电就找到插电的插口,这个插口帮你完成充电工作,内部是如何处理的你不用关心
- 如果你想计算一组数的最大值,那么你就找到Math的max()这个方法,直接使用即可,不需要他内部是如何实现的
1.2 Web API的概念
- Web API 是浏览器提供的一套操作浏览器功能和页面元素的 API
- Web 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 介绍
2.1 什么是DOM
-
文档对象模型(Document Object Model,简称DOM),是 W3C 组织推荐的处理可扩展标记语言(html或者xhtml)的标准编程接口
-
W3C 已经定义了一系列的 DOM 接口,通过这些 DOM 接口可以改变网页的内容、结构和样式
-
DOM是W3C组织制定的一套处理 html和xml文档的规范,所有的浏览器都遵循了这套标准
2.2. DOM树
-
DOM树 又称为文档树模型,把文档映射成树形结构,通过节点对象对其处理,处理的结果可以加入到当前的页面
-
DOM树如下:
-
文档:一个页面就是一个文档,DOM中使用document表示
-
节点:网页中的所有内容,在文档树中都是节点(标签、属性、文本、注释等),使用node表示
-
标签节点:网页中的所有标签,通常称为元素节点,又简称为“元素”,使用element表示
2.3 总结
- DOM: 将html文档,转换为了一个document对象,并且封装了各种各样的属性和方法。
- 我们通过这些属性和方法来操作html文档:
- 操作网页内容
- 操作界面结构
- 操作样式
3. 获取元素
-
为什么要获取页面元素?
-
例如:我们想要操作页面上的某部分(显示/隐藏,动画),需要先获取到该部分对应的元素,再对其进行操作。
3.1. 根据ID获取
-
语法:
document.getElementById(id)
-
作用:根据ID获取元素对象
-
参数:id值,区分大小写的字符串
-
返回值:元素对象 或 null
-
案例代码
<body>
<div id="time">2019-9-9</div>
<script>
// 因为我们文档页面从上往下加载,所以先得有标签 所以我们script写到标签的下面
var timer = document.getElementById('time');
console.log(timer);
console.log(typeof timer);
// console.dir 打印我们返回的元素对象 更好的查看里面的属性和方法
console.dir(timer);
</script>
</body>
- 问题:document哪来的?
- document是DOM提供的一个用于操作文档的对象(JS的内置对象)
- console.dir()理解:
- directory:目录。(电话号码簿)
- 元素对象的属性也有些是的对象,所以一级一级的,像是一个目录
3.2. 根据标签名获取元素
-
语法:
document.getElementsByTagName('标签名')
或者element.getElementsByTagName('标签名')
-
作用:根据标签名获取元素对象
-
参数:标签名
-
返回值:元素对象集合(伪数组,数组元素是元素对象)
-
案例代码
<body>
<ul>
<li>知否知否,应是等你好久11</li>
<li>知否知否,应是等你好久22</li>
<li>知否知否,应是等你好久33</li>
<li>知否知否,应是等你好久44</li>
<li>知否知否,应是等你好久55</li>
</ul>
<ul id="nav">
<li>生僻字</li>
<li>生僻字</li>
<li>生僻字</li>
<li>生僻字</li>
<li>生僻字</li>
</ul>
<script>
// 1.返回的是 获取过来元素对象的集合 以伪数组的形式存储的
var lis = document.getElementsByTagName('li');
console.log(lis);
console.log(lis[0]);
// 2. 我们想要依次打印里面的元素对象我们可以采取遍历的方式
for (var i = 0; i < lis.length; i++) {
console.log(lis[i]);
}
// 3. element.getElementsByTagName() 可以得到这个元素里面的某些标签
var nav = document.getElementById('nav'); // 这个获得nav 元素
var navLis = nav.getElementsByTagName('li');
console.log(navLis);
</script>
</body>
-
注意:getElementsByTagName()获取到的是动态集合,即:当页面增加了标签,这个集合中也就增加了元素
-
总结:
-
DOM提供的获取元素的方法,都是父亲获取后代(儿子/孙子…) ***
-
DOM提供的获取元素的方法,不仅可以通过document调用(document为DOM树中的顶级对象,所以通过document可以获取整个界面的任何元素),也可以通过普通元素对象调用(限定范围查找)
-
3.3. H5新增获取元素方式
- 根据类名返回元素对象集合:
document.getElementsByClassName(‘类名’);
- 根据指定选择器返回第一个元素对:
document.querySelector('选择器');
***- query:查询,selector:选择器。querySelector:根据选择器查询元素
- 根据指定选择器返回:
document.querySelectorAll('选择器');
***- all:所有
- 注意: querySelector 和 querySelectorAll里面的选择器需要加符号
- 比如:document.querySelector(’#nav’);
- 这两个方法重点掌握
案例代码
<body>
<div class="box">盒子1</div>
<div class="box">盒子2</div>
<div id="nav">
<ul>
<li>首页</li>
<li>产品</li>
</ul>
</div>
<script>
// 1. getElementsByClassName 根据类名获得某些元素集合
var boxs = document.getElementsByClassName('box');
console.log(boxs);
// 2. querySelector 返回指定选择器的第一个元素对象 切记 里面的选择器需要加符号 .box #nav
var firstBox = document.querySelector('.box');
console.log(firstBox);
var nav = document.querySelector('#nav');
console.log(nav);
var li = document.querySelector('li');
console.log(li);
// 3. querySelectorAll()返回指定选择器的所有元素对象集合
var allBox = document.querySelectorAll('.box');
console.log(allBox);
var lis = document.querySelectorAll('li');
console.log(lis);
</script>
</body>
3.4 获取元素方式总结
- js选定元素的方式,与css选定元素的方式一样。 ***
- 如下表:
选择器 | js普通方式 | js通用方式 | css选择器 |
---|---|---|---|
id选择器 | getElementById(‘id值’) | querySelector(’#id值’) | #id值 |
标签选择器 | getElementsByTagName(‘标签名’) | querySelector(‘标签名’) | 标签名 |
类选择器 | getElementsByClassName(‘类名’) | querySelector(’.类名’) | .类名 |
- 对比理解:
- 为啥普通方式不需要添加符号:#.,因为方法名已经指明是哪种选择器
- 为啥js通用方式需要添加符号,因为没有指明是哪种选择器,只是说根据选择器查询。
- 问题:返回值的对比:
- nodelist : 节点列表 (querySelectorAll返回的类型)
- htmlcollection: html标签集合 (getElementsByTagName,getElementsByClassName返回类型)
- 这两种类型,都是可以存储多个数据的对象,都是伪数组。
3.5 获取特殊元素(body,html)
- 获取body:
doucumnet.body; // 返回body元素对象
- 获取html:
document.documentElement ; // 返回html元素对象
- 注意:不是document.html
4. 事件基础***
4.1. 事件概述
- JavaScript 使我们有能力创建动态页面,而事件是可以被 JavaScript 侦测到的行为
- 简单理解: 触发— 响应机制
- 网页中的每个元素都可以产生某些可以触发 JavaScript 的事件
- 例如,我们可以在用户点击某按钮时产生一个 事件,然后去执行某些操作
4.2. 事件三要素
- 事件源(谁):触发事件的元素
- 事件类型(什么事件): 例如 click 点击事件
- 事件处理程序(做啥):事件触发后要执行的代码(函数形式),事件处理函数
- 语法:
element.on事件名=function(){}
- 案例代码
<body>
<button id="btn">唐伯虎</button>
<script>
// 点击一个按钮,弹出对话框
// 1. 事件是有三部分组成 事件源 事件类型 事件处理程序 我们也称为事件三要素
//(1) 事件源 事件被触发的对象 谁 按钮
var btn = document.getElementById('btn');
//(2) 事件类型 如何触发 什么事件 比如鼠标点击(onclick) 还是鼠标经过 还是键盘按下
//(3) 事件处理程序 通过一个函数赋值的方式 完成
btn.onclick = function() {
alert('点秋香');
}
</script>
</body>
- 理解:
- onclick 是一个比较特殊的方法,叫做事件
- 事件理解:当什么时候,做什么事情
- 这里的onclick就是:当点击的时候,执行事件处理程序
- on:理解为当的意思
- 注意:
- 事件处理程序不是程序员主动调用的(并没有写:
btn.onclick()
) - 当用户点击btn的时候,由js引擎调用的,执行类似这个逻辑:
btn.onclick()
- 事件处理程序不是程序员主动调用的(并没有写:
4.3. 执行事件的步骤
-
添加事件过程
- 获取事件源
- 注册事件(绑定事件)
- 添加事件处理程序(采取函数赋值形式)
-
事件是触发响应机制理解:
- 用户触发:由用户触发事件
- js引擎响应:由js引擎调用事件处理程序
-
案例代码
<body>
<div>123</div>
<script>
// 执行事件步骤
// 点击div 控制台输出 我被选中了
// 1. 获取事件源
var div = document.querySelector('div');
// 2.绑定事件 注册事件
// div.onclick
// 3.添加事件处理程序
div.onclick = function() {
console.log('我被选中了');
}
</script>
</body>
4.4. 常见的鼠标事件
- 常见的鼠标事件如下:
- 这些事件,后边我们陆陆续续会介绍
5. 操作元素
- JavaScript的 DOM 操作可以改变网页内容、结构和样式
- 我们可以利用 DOM 操作元素来改变元素里面的内容、属性等
- 注意:这些操作都是通过元素对象的属性实现的
5.1. 改变元素内容
-
内部文本:
element.innerText
- 识别文本
-
内部标签:
element.innerHTML
***- 识别标签和文本
-
改变元素内容–案例
<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是W3C标准的
-
获取内容时的区别:
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>
- 总结:
- 元素对象的属性/方法(事件),都支持获取,修改(读写)
5.2. 常用元素的属性操作
-
还有一些其他的元素常见属性
- src、href
- id、alt、title
-
操作语法
- 获取:
element.属性名
- 设置:
element.属性名=属性值
- 获取:
-
案例代码
<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>
5.3. 案例:分时问候
-
需求:
- 根据不同时间,页面显示不同图片,同时显示不同的问候语。
- 如果上午时间打开页面,显示上午好,显示上午的图片。
- 如果下午时间打开页面,显示下午好,显示下午的图片。
- 如果晚上时间打开页面,显示晚上好,显示晚上的图片。
-
分析:
- 根据系统不同时间来判断,所以需要用到日期内置对象
- 利用多分支语句来设置不同的图片
- 需要一个图片,并且根据时间修改图片,就需要用到操作元素src属性
- 需要一个div元素,显示不同问候语,修改元素内容即可
-
代码:
<img src="images/s.gif" alt=""> <div>上午好</div> <script> // 根据系统不同时间来判断,所以需要用到日期内置对象 // 利用多分支语句来设置不同的图片 // 需要一个图片,并且根据时间修改图片,就需要用到操作元素src属性 // 需要一个div元素,显示不同问候语,修改元素内容即可 // 1.获取元素 var img = document.querySelector('img'); var div = document.querySelector('div'); // 2. 得到当前的小时数 var date = new Date(); var h = date.getHours(); // 3. 判断小时数改变图片和文字信息 if (h < 12) { img.src = 'images/s.gif'; div.innerHTML = '亲,上午好,好好写代码'; } else if (h < 18) { img.src = 'images/x.gif'; div.innerHTML = '亲,下午好,好好写代码'; } else { img.src = 'images/w.gif'; div.innerHTML = '亲,晚上好,好好写代码'; } </script>
5.4. 表单元素的属性操作
-
利用 DOM 可以操作如下表单元素的属性:
-
type、value、checked、selected、disabled
-
操作语法
- 获取:
element.属性名
- 设置:
element.属性名=属性值
- 获取:
-
案例代码
<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>
-
注意:
- html标签中的属性值 , 如果是可用,不可用,两种可选状态的,
- 那么元素对象中的属性值就是boolean类型:true,false。
- 比如: checked、selected、disabled
- 具体写法如下:
//html中如下书写: <button disabled="disabled" disabled>按钮</button> //js中如下书写: btn.disabled = true
5.5. 案例:仿京东显示密码
-
需求:点击按钮将密码框切换为文本框,并可以查看密码明文。
-
效果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6XT2My9i-1575684662498)(images\jd.jpg)]
-
分析:
- 核心思路: 点击眼睛按钮,把密码框类型改为文本框就可以看见里面的密码
- 一个按钮两个状态,点击一次,切换为文本框,继续点击一次切换为密码框
- 算法:利用一个flag变量,来判断flag的值,如果是1 就切换为文本框,flag 设置为0,如果是0 就切换为密码框,flag设置为1
-
代码:
<body> <div class="box"> <label for=""> <img src="images/close.png" alt="" id="eye"> </label> <input type="password" name="" id="pwd"> </div> <script> // 1. 获取元素 var eye = document.getElementById('eye'); var pwd = document.getElementById('pass'); // 2. 注册事件 处理程序 //var flag = 0;//这里用flag=0不太妥当 var isOpen = false; //是否眼睛睁开,默认是闭着的 eye.onclick = function() { // 点击一次之后, flag 一定要变化 // if(flag==0){ // if (isOpen == false) {//if (!isOpen) { // pwd.type = 'text'; // eye.src = 'images/open.png'; // // flag = 1; // 赋值操作 // isOpen = true; // } else { // else if (isOpen == true) // else if(isOpen) // pwd.type = 'password'; // eye.src = 'images/close.png'; // // flag = 0; // isOpen = false; // } // 另外一种思路,不利用标记变量 // 如果type为password,则改为text // 否则,反之 if (pwd.type == 'password') { //千万要记住属性可读可写 pwd.type = 'text'; eye.src = 'images/open.png'; } else { pwd.type = 'password'; eye.src = 'images/close.png'; } } </script> </body>
5.6. 样式属性操作
- 我们可以通过 JS 修改元素的大小、颜色、位置等样式
- 两种方式
- 行内样式操作:
element.style
- 类名样式操作:
element.className
- 行内样式操作:
5.6.1 方式1:通过操作style属性
-
我们先来看第一种方式,行内样式操作:
element.style
-
案例代码
<div style="background-color:red;width:100px;"></div> <script> // 1. 获取元素 var div = document.querySelector('div'); console.dir(div); // 2. 注册事件 处理程序 div.onclick = function() { // div.style里面的属性 采取驼峰命名法 // this.style = "background-color:red;width:100px;";//js中不这么写,太麻烦 this.style.backgroundColor = 'purple'; this.style.width = '250px'; } </script>
-
注意:
- JS 里面的样式采取驼峰命名法 比如 fontSize、 backgroundColor
- JS 修改 style 样式操作,产生的是行内样式,CSS 权重比较高
5.6.2 案例:淘宝点击关闭二维码
-
需求:当鼠标点击二维码关闭按钮的时候,则关闭整个二维码
-
效果:
-
分析:
- 核心思路: 利用样式的显示和隐藏完成, display:none 隐藏元素、
- display:block 显示元素 点击按钮,就让这个二维码盒子隐藏起来即可
-
代码:
<body> <div class="box"> 淘宝二维码 <img src="images/tao.png" alt=""> <i class="close-btn">×</i> </div> <script> // 1. 获取元素 var btn = document.querySelector('.close-btn'); var box = document.querySelector('.box'); // 2.注册事件 程序处理 btn.onclick = function() { box.style.display = 'none'; } </script> </body>
5.6.3 案例:循环精灵图背景
-
需求:可以利用 for 循环设置一组元素的精灵图背景
-
效果:
-
分析:
- 首先精灵图图片排列有规律的
- 核心思路: 利用for循环 修改精灵图片的 背景位置 background-position
- 剩下的就是考验你的数学功底了
- 让循环里面的 i 索引号 * 44 就是每个图片的y坐标
-
代码:
<body> <div class="box"> <ul> <li></li> <li></li> <li></li> <li></li> <li></li> <li></li> <li></li> <li></li> <li></li> <li></li> <li></li> <li></li> </ul> </div> <script> // 1. 获取元素 所有的小li var lis = document.querySelectorAll('li'); for (var i = 0; i < lis.length; i++) { // 让索引号 乘以 44 就是每个li 的背景y坐标 index就是我们的y坐标 var index = i * 44; lis[i].style.backgroundPosition = '0 -' + index + 'px'; //lis[i].style.backgroundPositionY = '-' + index + 'px'; } </script> </body>
5.6.4 案例:显示隐藏文本框内容
-
需求:当鼠标点击文本框时,里面的默认文字隐藏,当鼠标离开文本框时,里面的文字显示。
-
效果:
-
分析:
- 首先表单需要2个新事件,获得焦点 onfocus 失去焦点 onblur
- 如果获得焦点, 判断表单里面内容是否为默认文字,如果是默认文字,就清空表单内容
- 如果失去焦点, 判断表单内容是否为空,如果为空,则表单内容改为默认文字
-
代码:
<body> <input type="text" value="手机"> <script> // 1.获取元素 var text = document.querySelector('input'); // 2.注册事件 获得焦点事件 onfocus text.onfocus = function() { // console.log('得到了焦点'); if (this.value === '手机') { this.value = ''; } // 获得焦点需要把文本框里面的文字颜色变黑 this.style.color = '#333'; } // 3. 注册事件 失去焦点事件 onblur text.onblur = function() { // console.log('失去了焦点'); if (this.value === '') { this.value = '手机'; } // 失去焦点需要把文本框里面的文字颜色变浅色 this.style.color = '#999'; } </script> </body>
-
注意:两个新事件
- 获得鼠标焦点触发:onfocus
失去鼠标焦点触发:onblur
- 获得鼠标焦点触发:onfocus
5.6.5 方式2:通过操作className属性
-
接下来我们来看第二种操作样式的方式,类名样式操作:
element.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';
//这里可以简写为:
this.className +=' change';//别忘了空格
}
</script>
</body>
- 注意:
-
如果样式修改较多,可以采取操作类名方式更改元素样式。
-
class因为是个保留字,因此使用className来操作元素类名属性
-
className 会直接更改元素的类名,会覆盖原先的类名。
5.6.6 案例:密码框格式提示错误信息
-
需求:用户如果离开密码框,里面输入个数不是6~16,则提示错误信息,否则提示输入正确信息
-
效果:
-
分析:
- 首先判断的事件是表单失去焦点 onblur
- 如果输入正确则提示正确的信息颜色为绿色小图标变化
- 如果输入不是6到16位,则提示错误信息颜色为红色 小图标变化
- 因为里面变化样式较多,我们采取className修改样式
-
代码:
<body> <div class="register"> <input type="password" class="ipt"> <p class="message">请输入6~16位密码</p> </div> <script> // 首先判断的事件是表单失去焦点 onblur // 如果输入正确则提示正确的信息颜色为绿色小图标变化 // 如果输入不是6到16位,则提示错误信息颜色为红色 小图标变化 // 因为里面变化样式较多,我们采取className修改样式 // 1.获取元素 var ipt = document.querySelector('.ipt'); var message = document.querySelector('.message'); //2. 注册事件 失去焦点 ipt.onblur = function() { // 根据表单里面值的长度 ipt.value.length if (this.value.length < 6 || this.value.length > 16) { // console.log('错误'); message.className = 'message wrong';//同样可以更换为+= message.innerHTML = '您输入的位数不对要求6~16位'; } else { message.className = 'message right'; message.innerHTML = '您输入的正确'; } } </script> </body>
5.3 DOM操作属性总结***
- html标签中的属性,与 DOM中元素对象的属性 基本是一致的 ***
- 具体对比表格如下:
html标签中的属性 | DOM中元素对象的属性 | 备注 |
---|---|---|
id | id | |
name | name | |
src | src | |
href | href | |
type | type | |
value | value | 操作表单控件的内容 |
标签内容(无此属性) | innerHTML | 操作非表单控件的内容 |
selected | selected | |
disabled | disabled | |
checked | checked | |
style | style | |
background-color | backgroundColor | 属性不支持字符-,所以改为驼峰命名 |
class | className | class是保留字 |
-
注意
-
元素对象的属性,都可以获取,设置/修改(读写)
-
标签内容:指的是开始标签结束标签中间的内容,innerHTML操作的就是标签内容
<a id="" href="">我是一个链接</a> <!--我是一个链接就是标签内容 -->
-
html标签中的属性值 ,大部分与标签中的属性值一样,基本上都是字符串类型的 ***
但是如果是可用/不可用,两种可选状态的属性值就改为boolean类型的值:true,false。 ***
比如:checked、selected、disabled
例子:
//html中如下书写: <button disabled="disabled" disabled>按钮</button> //js中如下书写: btn.disabled = true
. 样式属性名包含有 - ,变为驼峰命名
//html中如下书写: <button id="btn" style="background-color:red;font-size:10px">按钮</button> //js中如下书写: btn.style.backgroundColor = 'red'; btn.style.width = '100px'; //为啥不与html一致呢? btn.style = "background-color:red;width:100px"; //比较复杂,可读性糟糕,将style变成了一个对象,样式属性变为style的属性
理解: btn.style.width,为啥style还可以点属性?
//之前说过一句话:谁可以点属性/方法,只有对象。 //所以btn对象中的style属性,也是一个对象。 //某个对象的属性的数据类型,也可以是对象类型 (任意类型都可以) *** //这里有个例子,理解一下对象的属性还是对象 //人类构造函数 function Person(name,classes){ this.name = name; this.classes = classes; // 对象的属性值,可以是任意的数据类型,包含复杂类型,对象 this.sayHi = function(){} } //班级构造函数 function Classes(name){ this.name = name; } //创建班级1 var class1 = new classes('班级一;); // class1.name ---- 班级一 //创建zs var zs = new Person('张三',class1); //问,如何获取张三所属班级的名称? //zs.classes --- class1 : 是一个班级对象 //zs.classes.name --- 张三所在班级的名称
-
6. 今日总结
2 - Web APIs
学习目标:
能够说出排他操作的一般实现步骤
能够使用html5中的dataset方式操作自定义属性
能够根据提示完成百度换肤的案例
能够根据提示完成全选案例
能够根据提示完成tab栏切换案例
能够区分元素节点、文本节点、属性节点
能够获取指定元素的父元素
能够获取指定元素的所有子元素
能够说出childNodes和children的区别
能够使用createElement创建页面元素
1. 排他操作 ***
1.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';
//注意:这里不能将this替换为btns[i]
//btns[i].style.backgroundColor = 'pink';
}
}
</script>
1.2 总结
-
this和添加事件的对象可以替换的情况:
- 如果是给一个元素对象添加事件,事件处理程序中可将this与div替换
- 代码如下:
var div = document.getElementById('div01'); div.onclick = function() { this.style.backgroundColor = 'pink'; //这种情况下,this可以替换为添加onclick事件的对象div div.style.backgroundColor = 'pink'; }
-
this和添加事件的对象不可以替换的情况:
-
如果是循环给一组元素添加事件,然后在事件处理程序中,如果想要操作当前对象(事件源),只能 使用this,不能使用添加事件的对象
-
代码如下:
<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'; //注意:这里不能将this替换为btns[i] //btns[i].style.backgroundColor = 'pink'; } } </script>
-
原因:
- 因为在事件处理程序中有一个for循环,这个循环执行完成之后i永远是5,不是当前点击的btn的序号,不能代码当前事件源
- 即使事件处理程序中的for循环没有用i,那么外层循环的i依然是是固定的5
- 当前此循环,不是双重循环。两个循环执行的时机不一样。***
- 外边的循环,是在界面加载的时候执行的。 给所有的btn添加点击事件。***
- 里边的循环,点击某个按钮的时候执行的。 清楚所有按钮的样式。(干掉所有人)***
- 事件处理程序,也是点击某个按钮执行的,他与外层循环代码执行时机是不一样的。***
-
-
最终总结:其实大家完全可以不用记什么时候可以用this,什么情况下可以用btns[i],因为这两种情况,都可以用this,只要是在事件处理程序中想要操作事件源,就可以用this。
2 案例
2.1 百度换肤
- 需求:点击图片,切换body的背景图片
- 效果:
- 分析:
- 这个案例练习的是给一组元素注册事件
- 给4个小图片利用循环注册点击事件
- 当我们点击了这个图片,让我们页面背景改为当前的图片
- 核心算法: 把当前图片的src 路径取过来,给 body 做为背景即可
- 代码:
<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>
2.2 表格隔行变色
-
需求:鼠标经过某个tr后变色,离开恢复原色
-
效果:
-
分析:
- 用到新的鼠标事件:鼠标经过 onmouseover 鼠标离开 onmouseout
- 核心思路:鼠标经过 tr 行,当前的行变背景颜色, 鼠标离开去掉当前的背景颜色
- 注意: 第一行(thead里面的行)不需要变换颜色,因此我们获取的是 tbody 里面的行
-
代码:
<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>
-
注意:mouseover与mouseout的效果,和hover是基本一样的
- Js中的很多效果,通过css都是可以实现的
- 以后开发,我们会用css实现某些特效,也会用Js实现某些特效
- 至于什么情况下用css实现,什么情况下用js实现,以后会慢慢介绍
2.3 全选以及取消全选
-
需求:
- 点击上面全选复选框,下面所有的复选框都选中(全选)
- 再次点击全选复选框,下面所有的复选框都不中选(取消全选)
- 如果下面复选框全部选中,上面全选按钮就自动选中
- 如果下面复选框有一个没有选中,上面全选按钮就不选中
- 所有复选框一开始默认都没选中状态
-
效果:
- 分析:
- 全选和取消全选做法: 让下面所有复选框的checked属性(选中状态) 跟随 全选按钮即可
- 下面复选框需要全部选中, 上面全选才能选中做法: 给下面所有复选框绑定点击事件,每次点击,都要循环查看下面所有的复选框是否有没选中的,如果有一个没选中的, 上面全选就不选中
- 可以设置一个变量,来控制全选是否选中
-
代码:
<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; } } //2. 下边ck控制全选 //2.1 给所有的子复选框注册单击事件 for (var i = 0; i < j_tbs.length; i++) { j_tbs[i].onclick = function() { //flag 控制全选按钮是否选中(//这个变量可以改个名字:isCheckAll,是否全选) //2.2 假设是全都选中了 (我们这里利用了一种假设的思想) var flag = true; //2.3 验证假设(既然是假设,就需要验证) // 每次点击下面的复选框都要循环检查者4个小按钮是否全被选中 for (var i = 0; i < j_tbs.length; i++) { if (!j_tbs[i].checked) { flag = false; break; //只要有一个未选中,则认为未全选,跳出即可,后续不需要在判断 } } //2.4 循环结束之后,就已经知道是否全选 // 设置全选按钮的状态 j_cbAll.checked = flag; } } </script>
-
注意:
- 这里用到了一种假设的思想,很多时候,我们都会使用这种思想
- 刚开始是不知道是真是假,需要执行某段逻辑之后才知道,那就使用假设
- 先假设,在验证
-
补充:判断是否全选还有另一种思路
- 可以通过一个计数器,来记录选中的个数,如果选中个数与j_tb的个数一致,那么就认为全选
3. 自定义属性操作
- 属性分为两种:
- 元素自带属性
- 程序员添加的自定义属性
3.1 获取属性值
- 那么如何操作这两种属性呢?先来看如何获取:
- 获取语法:
- 获取元素自带属性,通过点语法:
element.属性
- 获取自定义属性,通过方法:
element.getAttribute('属性');
- 获取元素自带属性,通过点语法:
- 代码:
<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'));//getAttribute也可以获取自带属性,但是没有点语法方便
console.log(div.getAttribute('index'));
</script>
3.2. 设置属性值
- 接下来,我们研究下如何设置属性
- 设置语法:
- 设置元素自带属性,通过点语法:
element.属性='属性值'
- 设置自定义属性,通过方法:
element.setAttribute('属性','属性值');
- 设置元素自带属性,通过点语法:
- 代码:
// 2. 设置元素属性值
// (1) element.属性= '值'
div.id = 'test';
div.className = 'navs';
// (2) element.setAttribute('属性', '值'); 主要针对于自定义属性
div.setAttribute('index', 2);
div.setAttribute('class', 'footer'); // class 特殊 这里面写的就是class (也可以设置自带属性,但是麻烦)
3.3. 移除自定义属性
- 最后,我们来看一下移除属性
- 移除自定义属性语法:
element.removeAttribute('属性值')
// 3 移除属性 removeAttribute(属性)
div.removeAttribute('index');
div.removeAttribute('id');
-
注意:可以移除所有属性:自带和自定义
-
移除的作用:属性和属性值都没了
<div style="background-color:red" id="id"></div> //赋值为空字符串:属性还在,但是值没了 div.id= "";//<div style="background-color:red" id=""></div> div.setAttribute('id','');//<div style="background-color:red" id=""></div> //移除效果: 这个属性没了 div.removeAttribute('id') //<div style="background-color:red" ></div>
3.4 总结 ***
- 属性分为两种:
- 元素自带属性
- 程序员添加的自定义属性
- 操作属性:
- 自带属性,通过点语法,获取和设置
- 自定义属性,通过xxxAttribute()方法,获取和设置并且可以移除
- 虽然xxxAttribute()方法也可以操作自定义属性,但是不规范并且不方便
3.5. 案例:tab栏 (难点)
-
需求:当鼠标点击上面相应的选项卡(tab),下面内容跟随变化
-
效果:
-
分析:
- Tab栏切换有2个大的模块
- 上的模块选项卡,点击某一个,当前这一个底色会是红色,其余不变(排他思想) 修改类名的方式
- 下面的模块内容,会跟随上面的选项卡变化。所以下面模块变化写到点击事件里面
- 规律:下面的模块显示内容和上面的选项卡一一对应,相匹配
- 核心思路: 给上面的tab_list 里面的所有小li 添加自定义属性,属性值从0开始编号
- 当我们点击tab_list 里面的某个小li,让tab_con 里面对应序号的 内容显示,其余隐藏(排他思想)
-
代码:
<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>
- 总结:
- 在循环添加事件之后,事件处理程序中不能使用i,需要使用自定义属性。 因为循环完成之后,i是固定的值
- 在循环添加点击事件的时候,同时添加自定义属性,自定义属性值就是索引
- 循环过程中i的值是变化的,可以用,但是循环执行完成之后i是固定值,不能用
-
自定义属性的应用场景:***
一组元素,如果想进行排行(添加索引),那就使用自定义属性。(一组元素,默认每个元素是不知道在自家排行老几的。)- 自定义属性就是用于保存数据的,这里保存的数据,就是每个元素自家的排行索引。
3.6. H5自定义属性
-
自定义属性目的:是为了保存并使用数据。有些数据可以保存到页面中而不用保存到数据库中。
-
但是有些自定义属性很容易引起歧义,不容易判断是元素的内置属性还是自定义属性。
-
H5规定自定义属性data-开头做为属性名并且赋值。
<div data-index=“1”></div>
-
获取语法:
element.getAttribute(‘data-index’);
-
H5新增获取语法:
element.dataset.index 或者 element.dataset[‘index’] ie 11才开始支持
-
设置语法:
element.setAttribute(‘data-index’, 2)
-
代码:
<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>
4. 节点操作
4.1. 节点概述
-
网页中的所有内容都是节点(标签、属性、文本、注释等),在DOM 中,节点使用 node 来表示。
-
HTML DOM 树中的所有节点均可通过 JavaScript 进行访问,所有 HTML 元素(节点)均可被修改,也可以创建或删除。
-
节点属性:
-
属性:
nodeType(节点类型)、nodeName(节点名称)和nodeValue
-
节点类型
元素节点 nodeType 为 1 ***
属性节点 nodeType 为 2
文本节点 nodeType 为 3 (文本节点包含文字、空格、换行等)
-
-
获取元素通常使用两种方式:
- 利用 DOM 提供的方法获取元素
- 利用节点层级关系获取元素
-
总结:
- 我们一般通过DOM提供的获取元素的方法,获取一个元素,
- 然后利用节点层级关系获取 与其相关 的元素。
4.2. 节点层级
利用 DOM 树可以把节点划分为不同的层级关系,常见的是父子兄层级关系。
4.3. 父级节点 ***
- 语法:
element.parentNode
- 如果element没有父节点,那么就返回null
- 例子:
<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>
4.4. 子节点
- 获取子节点语法:
element.childNodes
,返回element的所有子节点的集合 (包括元素节点,文本节点等) - 获取子元素节点语法:
element.children
,返回element的所有的子元素节点 ***- children虽然是非标准,但是大部分浏览器都支持
- 例子:
<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>
-
获取第一个子节点:
element.firstChild
返回element的第一个子节点 (这个子节点可以是元素节点,也可以是文本节点)
-
获取最后一个子节点:
element.lastChild
返回element的最后一个子节点(这个子节点可以是元素节点,也可以是文本节点)
-
获取第一个子元素节点:
element.firstElementChild IE9+
返回第一个子元素节点
-
获取最后一个子元素节点:
element.lastElementChild IE9+
返回最后一个子元素节点
-
实际开发中,firstChild 和 lastChild 包含其他节点,操作不方便,而 firstElementChild 和 lastElementChild又有兼容性问题,那么我们如何获取第一个子元素节点或最后一个子元素节点呢?
- 如果想要第一个子元素节点,可以使用
element.chilren[0]
- 如果想要最后一个子元素节点,可以使用
element.chilren[element.chilren.length - 1]
- 如果想要第一个子元素节点,可以使用
-
例子:
<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>
4.5. 案例:新浪下拉菜单
-
需求:鼠标悬浮菜单之后出现下拉菜单
-
效果:
-
分析:
- 导航栏里面的li 都要有鼠标经过效果,所以需要循环注册鼠标事件
- 核心原理: 当鼠标经过li 里面的 第二个孩子 ul 显示, 当鼠标离开,则ul 隐藏
-
代码:
<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>
4.6. 兄弟节点
-
获取下一个兄弟节点:
element.nextSibling
返回element的下一个兄弟节点(这个节点可以是元素节点,也可以是文本节点)
-
获取上一个兄弟节点:
element.previousSibling
返回element的上一个兄弟节点(这个节点可以是元素节点,也可以是文本节点)
-
获取下一个兄弟元素节点:
element.nextElementSibling IE9+
***返回element的下一个兄弟元素节点
-
获取上一个兄弟元素节点:
element.previousElementSibling IE9+
***返回element的上一个兄弟元素节点
- 例子:
<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;
}
4.7. 创建节点 ***
- 创建节点:
document.createElement('tagName')
- document.createElement() 方法创建由 tagName 指定的 HTML 元素。
- 因为这些元素原先不存在,是根据我们的需求动态生成的,所以我们也称为动态创建元素节点。
4.8. 添加节点 ***
-
创建出来的元素节点,需要添加到界面上才能显示
-
添加方式:
-
末尾添加:
element.appendChild(childNode)
将 childNode 添加到element的子节点列表的末尾 (追加) -
指定位置添加:
element.insertBefore(childNode, 指定元素)
将childNode 添加到element的指定元素的前面
-
-
例子:
<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 li2 = document.createElement('li');
ul.insertBefore(li2, ul.children[0]);
// 4. 我们想要页面添加一个新的元素 : 1. 创建元素 2. 添加元素
</script>
4.9. 案例:简单版发布留言
-
需求:输入内容,点击发布,显示到界面上
-
效果:
-
分析:
- 核心思路: 点击按钮之后,就动态创建一个li,添加到ul 里面。
- 创建li 的同时,把文本域里面的值通过li.innerHTML 赋值给 li
- 如果想要新的留言后面显示就用 appendChild 如果想要前面显示就用insertBefore
-
代码:
<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.难点总结
- 循环添加事件,在事件处理程序中不能使用i
- 因为i在循环结束之后,值是定死的。
- 在事件处理程序中如何想获取,当前点击的这个元素的索引,就需要通过自定义属性来处理:例如tab烂切换
- 代码:
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 () {
var i;
console.log(i);// 5 , undefined
// 2. 下面的显示内容模块
var index = this.getAttribute('index');
// 干掉所有人 让其余的item 这些div 隐藏
for (var i = 0; i < items.length; i++) {
items[i].style.display = 'none';
}
// 留下我自己 让对应的item 显示出来
items[index].style.display = 'block';
}
}
- 循环添加事件,在事件处理程序中不能使用xx[i]
- 因为i在循环结束之后,值是定死的。
- 在事件处理程序中如果想获取当前点击的事件源,直接使用this即可,例如
- 代码:
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'; // 当前的事件源
// this 不能换成 btns[i]
}
}
3 - Web APIs
学习目标:
能够使用removeChild()方法删除节点
能够完成动态生成表格案例
能够使用传统方式和监听方式给元素注册事件
能够说出事件流执行的三个阶段
能够在事件处理函数中获取事件对象
能够使用事件对象取消默认行为
能够使用事件对象阻止事件冒泡
能够使用事件对象获取鼠标的位置
能够完成跟随鼠标的天使案例
1.1. 节点操作
1.1.1 删除节点 ***
(01-节点操作-删除节点.avi)
-
语法
node.removeChild(child)
说明:node.removeChild() 方法从 node节点中删除一个子节点,返回删除的节点。
-
例子
<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 {
//注意:每次点击都是删除第一个
//第一次删除3个里边的第一个,删除之后原来的1,2变为0,1,所以在次删除还是删除0
ul.removeChild(ul.children[0]);
}
}
</script>
1.1.2 案例:删除留言
- 案例效果:
- 案例分析:
- 当我们把文本域里面的值赋值给li 的时候,多添加一个删除的链接
- 需要把所有的链接获取过来,当我们点击当前的链接的时候,删除当前链接所在的li
- 阻止链接跳转需要添加 javascript:void(0); 或者 javascript:; (如果不添加,点击删除时,界面会刷新)
- 案例代码:
<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);
}
}
// 第三步有简单的方式1:
li.querySelector('a').onclick = function() {
ul.removeChild(this.parentNode);
}
// 第三步有简单的方式2:
li.children[0].onclick = function () {
ul.removeChild(this.parentNode);
}
// 第三步简单方式3:
var a = document.querySelector('a');
a.onclick = function() {
ul.removeChild(this.parentNode);
};
// 分析:
// 第一种删除逻辑是,当添加了一个li和a之后,查找当前界面所有的a,
// 然后循环给a添加删除事件,但其实这么做有点麻烦
// 因为如果是添加的第二个a,添加点击事件的时候,他又找了所有的a,给a添加点击事件
// 但其实此时的第一个a已经在第一次添加到ul中的时候,已经添加过点击事件了
// 第二种逻辑(简单方式):添加一个,只给当前添加的这个li下边的a添加点击事件,不重复
}
}
</script>
-
注意:href与onclick对吧:
<!--href和onclick,其实都是点击a之后,触发的逻辑--> <!-- //当点击删除超链接之后,跳转到百度 --> <a href='http://www.baidu.com'>删除</a> <!-- //当点击删除超链接之后,弹出123,可通过href实现,也可以通过onclick --> <a href='javascript:alert(123);' onclick="alert(123)">删除</a> <!-- 这个href中写javascript意思就是,不跳转界面,而是要执行js代码--> <!-- // href写#,点击之后,url后会加上#--> <a href="#" onclick="alert(123)">删除</a> <!-- 不希望href做点击响应,href就写javascript:; 相当于执行空代码 --> <a href='javascript:;' onclick="alert(123)">删除</a> <ul> <!--总结:一般情况下,我们要给a绑定事件,就给href赋值为javascript:; -->
1.1.3 复制(克隆)节点
(03-节点操作-复制节点.avi)
-
语法:
node.cloneNode([isDeep]) ;// 是否深度拷贝,true
说明:node.cloneNode() 方法返回调用该方法的节点的一个副本,意思就是放一个node的副本, 也称为克隆节点/拷贝节点
-
案例:
<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>
//理解:
//浅拷贝,只是浅浅的拷贝,点到为止,所以只给标签表面(标签本身)拷贝了一下
//深拷贝,就是深度的拷贝,将标签表面,和标签内部所有的内容都拷贝
1.1.4 案例:动态生成表格 ***
(04-09-动态生成表格.avi)
-
案例效果:
-
案例分析:
- 因为里面的学生数据都是动态的,我们需要js 动态生成。 这里我们模拟数据,自己定义好数据。 数据我们采取对象形式存储。
- 所有的数据都是放到tbody里面的行里面。
- 因为行很多,我们需要循环创建多个行(对应多少人)
- 每个行里面又有很多单元格(对应里面的数据),我们还继续使用循环创建多个单元格,并且把数据存入里面(有行有列,使用双重for循环)
- 最后一列单元格是删除,需要单独创建单元格。
- 最后添加删除操作,单击删除,可以删除当前行。
问题:
- 为啥每行数据,用一个对象进行存储?
因为每一行,都有三对数据:姓名-xxx,科目-xxx,成绩-xxx。典型的key和value,所以就是对象。
2. 数据都是动态的,怎么理解?
意思就是时时刻刻都是变化的,而数据是由后台维护的,所以是有后台将数据传递给我们前端,然后我们进行动态显示。但是这里我们没有后台的支持,所以一会要自己模拟一个后台传递过来的数据。
- 案例代码:
<script>
// 1.先去准备好学生的数据 (datas是一个数组,数组中存储多个对象)
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++) {
// 2.1. 创建 tr行
var tr = document.createElement('tr');
tbody.appendChild(tr); // 创建完元素对象,就添加,避免忘记
// 2.2. 行里面创建单元格td 单元格的数量取决于每个对象里面的属性个数
// 使用for in遍历学生对象‘
{
name: '魏璎珞',
subject: 'JavaScript',
score: 100
}
var Obj = datas[i];
for (var k in Obj) { //'name';
// 创建单元格
var td = document.createElement('td');
// 把对象里面的属性值 datas[i][k] 给 td
td.innerHTML = Obj[k];//比如第一行的第一列:魏璎珞 Obj['name'],Obj.name
tr.appendChild(td);
}
// 2.3. 创建 有删除2个字 的单元格
var td = document.createElement('td');
td.innerHTML = '<a href="javascript:;">删除 </a>';
tr.appendChild(td);
}
// 3. 删除操作 开始
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)
}
// 分析:
// 这个第三步删除操作,放到外边就没有重复添加点击事件的情况
// 因为我们是先整体添加完完成,然后再找所有a,添加点击事件
// 当然也有更简单的方式,跟之前删除留言案例一样,可以在2.3,添加完之后,立马找到a并且添加事件
// td.children[0].onclick = function () {
// tbody.removeChild(this.parentNode.parentNode);
// }
</script>
1.1.5 创建元素的三种方式
(10-document.write创建元素(了解).avi)
- 语法:
- document.write()
- element.innerHTML
- document.createElement()
- 区别:
- document.write 是直接将内容写入页面的内容流,界面加载完成之后,再写入,这样它会导致页面全部重绘
- innerHTML 是将内容写入某个 DOM 节点,不会导致页面全部重绘
- innerHTML 创建多个元素效率更高(不要拼接字符串,采取数组形式拼接),结构稍微复杂
- createElement() 创建多个元素效率稍低一点点,但是结构更清晰
- 总结:不同浏览器下,innerHTML 效率要比 creatElement 高
- 案例:
<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>
1.1.6 innerTHML和createElement效率对比
(11-innerHTML和createElement效率对比.avi)
- innerHTML字符串拼接方式(效率低)
<script>
function fn() {
var d1 = +new Date();
var str = '';
for (var i = 0; i < 1000; i++) {
document.body.innerHTML += '<div style="width: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="width:100px; height:2px; border:1px solid blue;"></div>');
}
document.body.innerHTML = array.join('');
var d2 = +new Date();
console.log(d2 - d1);
}
fn();
</script>
1.2. DOM的核心总结
(12-DOM重点核心.avi)
-
文档对象模型(Document Object Model,简称 DOM),是 W3C 组织推荐的处理可扩展标记语言(HTML或者XML)的标准编程接口。
-
W3C 已经定义了一系列的 DOM 接口,通过这些 DOM 接口可以改变网页的内容、结构和样式。
- 对于JavaScript,为了能够使JavaScript操作HTML,JavaScript就有了一套自己的dom编程接口。
-
对于HTML,dom使得html形成一棵dom树. 包含 文档、元素、节点
关于dom操作,我们主要针对于元素的操作。主要有创建、增、删、改、查、属性操作、事件操作。
1.2.1. 创建
- document.write
- innerHTML
- createElement
1.2.2. 增加
- appendChild
- insertBefore
1.2.3. 删
- removeChild
1.2.4. 改
主要修改dom的元素属性,dom元素的内容、属性, 表单的值等
- 修改元素属性: src、href、title等
- 修改普通元素内容: innerHTML 、innerText
- 修改表单元素: value、type、disabled等
- 修改元素样式: style、className
1.2.5. 查
主要获取查询dom的元素
- DOM提供的API 方法: getElementById、getElementsByTagName 古老用法 不太推荐
- H5提供的新方法: querySelector、querySelectorAll 提倡
- 利用节点操作获取元素: 父(parentNode)、子(children)、兄(previousElementSibling、nextElementSibling) 提倡
1.2.6. 属性操作
主要针对于自定义属性:一组元素中(同一个父亲),每个元素他自己不知道自己排行老几,设置自定义属性,来排行。
- setAttribute:设置dom的属性值
- getAttribute:得到dom的属性值
- removeAttribute移除属性
1.2.7. 事件操作(重点)
给元素注册事件, 采取 事件源.on事件类型 = 事件处理程序
1.3. 事件高级
(13-事件高级导读.avi)
- 能够写出元素注册事件的两种方式
- 能够说出删除事件的两种方式
- 能够说出 DOM 事件流的三个阶段
- 能够利用事件对象完成跟随鼠标案例
- 能够封装阻止冒泡的兼容性函数
- 能够说出事件委托的原理
- 能够说出常用的鼠标和键盘事件
1.3.1. 注册事件(2种方式)***
( 14-注册事件两种方式.avi )
-
给元素添加事件,称为注册事件或者绑定事件。
-
注册事件有两种方式:传统方式和方法监听注册方式
-
传统方式
-
利用 on 开头的事件 onclick
-
<button onclick=“alert('hi~')”></button>
-
btn.onclick = function() {}
-
特点: 注册事件的唯一性(只能有一个)
-
同一个元素同一个事件只能设置一个处理函数,最后注册的处理函数将会覆盖前面注册的处理函数
-
代码:
btns[0].onclick = function() { alert('hi'); } btns[0].onclick = function() { alert('hao a u'); } // 点击btn之后弹出 hao a u
-
-
方法监听
-
w3c 标准 推荐方式 :addEventListener()
-
addEventListener() : 添加事件监听
-
IE9 之前的 IE 不支持此方法,可使用 attachEvent() 代替 (了解即可)
- (attach:附着,附着事件,理解为绑定事件)
-
特点:同一个元素同一个事件可以注册多个监听器(事件处理程序)
-
按注册顺序依次执行
-
代码
// 参数1:事件类型,它是字符串 必定加引号 而且不带on // 参数2:事件处理程序 btns[1].addEventListener('click', function() { alert(22); }) btns[1].addEventListener('click', function() { alert(33); }) // 3. attachEvent ie9以前的版本支持 ,这里的事件需要带on btns[2].attachEvent('onclick', function() { alert(11); })
addEventListener详细介绍见1.3.2。
-
1.3.2 事件监听
1. addEventListener事件监听 ***
- 语法:
eventTarget.addEventListener(type, listener[, useCapture])
-
注意:
- addEventListener()事件监听(IE9以后支持)
- eventTarget.addEventListener()方法将指定的监听器注册到 eventTarget(目标对象)上,当该对象触发指定的事件时,就会执行事件处理函数。
-
该方法接收三个参数:
- type:事件类型字符串,比如 click 、mouseover ,注意这里不要带 on
- listener:事件处理函数,事件发生时,会调用该监听函数
- useCapture:可选参数,是一个布尔值,默认是 false。学完 DOM 事件流后,我们再进一步学习
2. attacheEvent()事件监听(IE678支持)
(15-attachEvent注册事件.avi)
- 语法:
eventTarget.attachEvent(eventNameWithOn, callback)
- 注意:
eventTarget.attachEvent()方法将指定的监听器注册到 eventTarget(目标对象) 上,当该对象触发指定的事件时,指定的回调函数就会被执行。
-
该方法接收两个参数:
- eventNameWithOn:事件类型字符串,比如 onclick 、onmouseover ,这里要带 on
- callback: 事件处理函数,当目标触发事件时回调函数被调用
-
案例:
<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>
3. 事件监听兼容性解决方案
兼容性处理的原则: 首先照顾大多数浏览器,再处理特殊浏览器
封装一个函数,函数中判断浏览器的类型:
function addEventListener(element, eventName, fn) {
// 判断当前浏览器是否支持 addEventListener 方法
if (element.addEventListener) { // 浏览器支持此种方式,可以根据方法名找到方法声明,方法声明会转换为true
element.addEventListener(eventName, fn); // 第三个参数 默认是false
} else if (element.attachEvent) {
element.attachEvent('on' + eventName, fn);
} else {
// 相当于 element.onclick = fn;
element['on' + eventName] = fn;
}
1.3.3. 删除事件(解绑事件)
(16-删除事件.avi)
-
传统注册方式
eventTarget.onclick = null; ***
-
方法监听注册方式
1. eventTarget.removeEventListener(type, listener[, useCapture]); *** 2. eventTarget.detachEvent(eventNameWithOn, callback);
-
案例:
<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); // 点击div1的时候弹出22,然后直接移除点击事件。 // 移除事件需要参数1,哪种事件,参数2,这种事件中的fn处理程序 // 为啥需要指定处理程序fn?因为这个点击事件可以有多个处理程序,你要指定删除哪个 divs[1].removeEventListener('click', fn); // divs[1].addEventListener('click', fn()) // 里面的fn 不需要调用加小括号 // 如果添加了小括号,就相当于将fn()调用完成的返回值作为第二个参数传递了 // 事件处理程序,不是我们调用的,而是js引擎调用的。 // 我们通过第二个参数,告诉js引擎,事件发生时要调用的方法是谁,所以只告诉他方面即可 } // 3. detachEvent divs[2].attachEvent('onclick', fn1); function fn1() { alert(33); divs[2].detachEvent('onclick', fn1); } </script>
注意:
-
**删除事件兼容性解决方案 **
function removeEventListener(element, eventName, fn) { // 判断当前浏览器是否支持 removeEventListener 方法 if (element.removeEventListener) { element.removeEventListener(eventName, fn); // 第三个参数 默认是false } else if (element.detachEvent) { element.detachEvent('on' + eventName, fn); } else { element['on' + eventName] = null; }
1.3.4. DOM事件流
1. 介绍
(17-DOM事件流理论.avi)
-
思考问题:
html中的标签都是相互嵌套的,我们可以将元素想象成一个盒子装一个盒子,document是最外面的大盒子。
当你单击一个div时,同时你也单击了div的父元素,甚至整个页面。那么是先执行父元素的单击事件,还是先执行div的单击事件 ???
要搞清楚这个问题,就需要学习事件流 -
介绍:
- 事件流描述的是从页面中接收事件的顺序。(事件流,事件传播的流程)
- 事件发生时会在元素节点之间按照特定的顺序传播,这个传播过程即 DOM 事件流。
-
举例说明:
比如:我们给页面中的一个div注册了单击事件,当你单击了div时,也就单击了body,单击了html,单击了document
如下图:
-
事件冒泡: IE 最早提出,事件开始时由最具体的元素接收,然后逐级向上传播到到 DOM 最顶层节点的过程。
-
事件捕获: 网景最早提出,由 DOM 最顶层节点开始,然后逐级向下传播到到最具体的元素接收的过程。如何理解捕获,就相当于事件发生的时候,从顶级节点开始,先知道事件发生。捕获理解为知道即可。
当时的2大浏览器霸主谁也不服谁!
IE 提出从目标元素开始,然后一层一层向外接收事件并响应,也就是冒泡型事件流。
Netscape(网景公司)提出从最外层开始,然后一层一层向内接收事件并响应,也就是捕获型事件流。江湖纷争,武林盟主也脑壳疼!!!
最终,w3c 采用折中的方式,平息了战火,制定了统一的标准 —–— 先捕获再冒泡。
现代浏览器都遵循了此标准,所以当事件发生时,会经历3个阶段。 -
结论:DOM 事件流会经历3个阶段:
- 捕获阶段
- 当前目标阶段
- 冒泡阶段
-
再理解事件流***
我们向水里面扔一块石头,首先它会有一个下降的过程,这个过程就可以理解为从最顶层向事件发生的最具体元素(目标点)的捕获过程;之后会产生泡泡,会在最低点( 最具体元素)之后漂浮到水面上,这个过程相当于事件冒泡。
2. 案例
(18-DOM事件流代码验证.avi)
- 事件冒泡案例
<div class="father">
<div class="son">son盒子</div>
</div>
<script>
// 如果addEventListener 第三个参数是 true 那么则处于捕获阶段
// 顺序为:document -> html -> body -> father -> son
// 如果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>
3. 结论 ***
事件发生时会在元素节点之间按照特定的顺序传播,这个传播过程即 DOM 事件流。
注意:
- JS 代码中只能执行捕获或者冒泡其中的一个阶段。
- onclick 和 attachEvent 只能得到冒泡阶段。(理解:只在冒泡阶段触发)
- addEventListener(type, listener[, useCapture])第三个参数如果是 true,表示在事件捕获阶段调用事件处理程序;如果是 false(不写默认就是false),表示在事件冒泡阶段调用事件处理程序。
- 实际开发中我们很少使用事件捕获,我们更关注事件冒泡。
- 有些事件是没有冒泡的,比如 onblur、onfocus、onmouseenter、onmouseleave
- 事件冒泡有时候会带来麻烦,有时候又会帮助很巧妙的做某些事件,我们后面讲解。
1.3.5. 事件对象
1. 什么是事件对象 ***
(19-什么是事件对象.avi)
官方解释:event 对象代表事件的状态,比如键盘按键的状态、鼠标的位置、鼠标按钮的状态。
简单理解:事件发生后,跟事件相关的一系列信息数据的集合都放到这个对象里面,这个对象就是事件对象 event,它有很多属性和方法。
比如:
- 谁绑定了这个事件。
- 鼠标触发事件的话,会得到鼠标的相关信息,如鼠标位置。
- 键盘触发事件的话,会得到键盘的相关信息,如按了哪个键。
2. 事件对象的使用 ***
事件触发发生时就会产生事件对象,并且系统会以实参的形式传给事件处理函数。
所以,在事件处理函数中声明1个形参用来接收事件对象。
例子:
eventTarget.onclick = function(event) {
// 这个 event 就是事件对象,我们还喜欢的写成 e 或者 evt
}
eventTarget.addEventListener('click', function(event) {
// 这个 event 就是事件对象,我们还喜欢的写成 e 或者 evt
})
// event谁传递的?
// 函数的形参是由谁传递的? 调用者传递
// 事件处理程序是谁调用的。
// js引擎调用,div.onclick(e);
// 当事件发生之后,js引擎将事件相关信息封装为一个对象,e。调用事件处理程序时将e传递
// var obj = {
// name:'',
// onclick:function(e){
// }
// }
// obj.onclick(e);
应用场景:什么时候添加形参e:事件处理程序中需要用到事件对象,那么就添加形参e
3. 事件对象的兼容性处理
事件对象本身的获取存在兼容问题:
- 标准浏览器中是浏览器给方法传递的参数,只需要定义形参 e 就可以获取到。
- 在 IE6~8 中,浏览器不会给方法传递参数,如果需要的话,需要到 window.event 中获取查找。
语法:
兼容写法:
e = e || window.event;
这个利用了短路逻辑运算:
如果浏览器支持e,而e转boolean为true,则直接返回e
如果浏览器不支持e,会去识别window.event,然后返回window.event
代码:
<div>123</div>
<script>
var div = document.querySelector('div');
div.onclick = function(e) {
// 事件对象
e = e || window.event;
console.log(e);
}
</script>
4. 事件对象的属性和方法
(20-e.target和this区别.avi)
- 重点: target,preventDefault(),stopPropagation() ***
5. e.target 和 this 的区别 ***
-
this 是事件绑定的元素(绑定这个事件处理函数的元素) 。
-
e.target 是事件触发的元素。
通常情况下target 和 this是一致的
但有一种情况不同,那就是在事件冒泡时(父子元素有相同事件,单击子元素,父元素的事件处理函数也会被触发执行),
这时候this指向的是父元素,因为它是绑定事件的元素对象,
而target指向的是子元素,因为他是触发事件的那个具体元素对象。总结 :
e.target 点击了哪个元素,就返回哪个元素
this 哪个元素绑定了这个点击事件,那么就返回谁
-
例子:没有冒泡的target和this
<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.οnclick=function(){this}
// 我们给ul 绑定了事件 那么this 就指向ul
console.log(this); // ul //(当前函数所属对象,怎么理解,看上边普通方式)
// e.target 触发了事件的对象 我们点击的是li e.target 指向的就是li
console.log(e.target); // li //(目标:目标阶段,触发的对象)
});
</script>
-
总结:
this:绑定事件的对象
e.target:触发事件的对象
1.3.6 阻止默认行为 ***
(21-阻止默认行为.avi)
html中一些标签有默认行为,例如a标签被单击后,默认会进行页面跳转。
<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>
1.3.7 阻止事件冒泡
(22-阻止事件冒泡.avi)
-
事件冒泡:开始时由最具体的元素接收,然后逐级向上传播到到 DOM 最顶层节点。
-
事件冒泡本身的特性,会带来的坏处,也会带来的好处,需要我们灵活掌握。
-
阻止事件冒泡的方式
- 标准写法:利用事件对象里面的 stopPropagation()方法 ( stop 停止 Propagation 传播)
e.stopPropagation() ***
- 非标准写法:IE 6-8 利用事件对象 cancelBubble 属性 (Bubble:水泡,cancelBubble :取消冒泡)
e.cancelBubble = true;
-
例子:
<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();
window.event.cancelBubble = true;
}, false);
var father = document.querySelector('.father');
// 给father注册单击事件
father.addEventListener('click', function() {
alert('father'); // 在son中取消冒泡之后,这里就不会执行
}, false);
// 给document注册单击事件
document.addEventListener('click', function() {
alert('document');// 在son中取消冒泡之后,这里就不会执行
})
</script>
- 阻止事件冒泡的兼容性处理
if(e && e.stopPropagation){
e.stopPropagation();
}else{
window.event.cancelBubble = true;
}
1.3.8 事件委托 ***
(23-事件委托.avi)
1. 什么是事件委托
-
事件委托:把事情委托给别人,代为处理。
-
事件委托也称为事件代理,在 jQuery 里面称为事件委派。
-
理解:说白了就是,不给子元素注册事件,给父元素注册事件,把处理代码在父元素的事件中执行。
-
生活中的委托:
- 咱们班有100个学生, 快递员有100个快递, 如果一个个的送花费时间较长。同时每个学生领取的时候,也需要排队领取,也花费时间较长,何如?
- 解决方案: 快递员把100个快递,委托给班主任,班主任把这些快递放到办公室,同学们下课自行领取即可。
- 优势: 快递员省事,委托给班主任就可以走了。 同学们领取也方便,因为相信班主任。
-
js事件中的委托:
<ul>
<li>知否知否,应该有弹框在手</li>
<li>知否知否,应该有弹框在手</li>
<li>知否知否,应该有弹框在手</li>
<li>知否知否,应该有弹框在手</li>
<li>知否知否,应该有弹框在手</li>
</ul>
点击每个 li 都会弹出对话框,以前需要给每个 li 注册事件,是非常辛苦的,而且访问 DOM 的次数越多,这就会延长整个页面的交互就绪时间,那怎么解决呢?看下一小节。
2. 事件委托的原理
不是每个子节点单独设置事件监听器,而是事件监听器设置在其父节点上,然后利用冒泡原理影响设置每个子节点。
以上案例:给 ul 注册点击事件,然后利用事件对象的 target 来找到当前点击的 li,因为点击 li,事件会冒泡到 ul 上, ul 有注册事件,就会触发事件监听器。
3. 事件委托的作用
-
我们只操作了一次 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>
4. 总结
- 事件委托,子元素委托父元素
- 什么情况下用事件委托:当要给一组元素添加相同事件时,可以直接添加给父亲
- 事件委托原理:事件冒泡,当触发子元素的事件时,通过冒泡,事件传递给父亲,父亲身上绑定有事件处理程序,进而触发
1.4. 常用鼠标事件***
1.4.1 案例:禁止选中文字和禁止右键菜单
(24-禁止选中文字和禁止右键菜单.avi)
1.禁止鼠标右键菜单
contextmenu事件:鼠标右键菜单事件。主要控制应该何时显示上下文菜单(右键菜单),主要用于程序员取消默认的上下文菜单
document.addEventListener('contextmenu', function(e) {
e.preventDefault();
})
2.禁止鼠标选中(selectstart 事件:鼠标选中事件,用于界面文字防止选中)
document.addEventListener('selectstart', function(e) {
e.preventDefault();
})
代码:
<body>
我是一段不愿意分享的文字
<script>
// 1. contextmenu 我们可以禁用右键菜单
document.addEventListener('contextmenu', function(e) {
e.preventDefault();
})
// 2. 禁止选中文字 selectstart
document.addEventListener('selectstart', function(e) {
e.preventDefault();
})
</script>
</body>
1.4.2 鼠标事件对象
event对象代表事件的状态,跟事件相关的一系列信息的集合。
现阶段我们主要是用鼠标事件对象 MouseEvent 和键盘事件对象 KeyboardEvent。
如下是MouseEvent鼠标事件对象的常用属性:
1.4.3 获取鼠标在页面的坐标
(25-获得鼠标在页面中的坐标.avi)
一会我们要处理的案例,需要获取鼠标在界面上的坐标,所以现在我们来看一下如何获取,代码如下:
<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>
1.4.4 案例:跟随鼠标的天使
(26-跟随鼠标的天使.avi)
案例效果: 下图中的天使图片,跟着鼠标一起移动
案例分析:
- 鼠标不断的移动,使用鼠标移动事件: mousemove
- 在页面中移动,给document注册事件
- 图片要移动距离,而且不占位置,我们使用绝对定位即可
- 核心原理: 每次鼠标移动,我们都会获得最新的鼠标坐标, 把这个x和y坐标做为图片的top和left 值就可以移动图片
案例代码:
<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>
4 - Web APIs
学习目标:
能够说出常用的3-5个键盘事件
能够知道如何获取当前键盘按下的是哪个键
能够知道浏览器的顶级对象window
能够使用window.onload事件
能够使用window.onresize事件
能够说出两种定时器的区别
能够使用location对象的href属性完成页面之间的跳转
能够使用location对象获取url中的参数部分
能够使用history提供的方法实现页面刷新
1.1. 常用的键盘事件
1.1.1 键盘事件 ***
-
(01-常用的键盘事件.avi)
-
事件除了使用鼠标触发,还可以使用键盘触发
-
事件:
-
注意:
- 如果使用addEventListener 不需要加 on
- onkeypress 和前面2个的区别是,它不识别功能键,比如左右箭头,shift 等。
- 三个事件的执行顺序是: keydown – keypress — keyup (按下–按住–抬起)
-
案例:
<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>
1.1.2 键盘事件对象 ***
-
我们来看一下常用的键盘事件对象的属性
-
键盘事件对象中的keyCode属性可以得到相应键的ASCII码值 :
- 注意:
- onkeydown 和 onkeyup 不区分字母大小写,通过keyCode属性获取到的a 和 A 得到的都是65
- onkeypress 区分字母大小写 ,通过keyCode属性获取到的 a 是 97 和 A 得到的是65
- 但是在我们实际开发中,我们更多的使用keydown和keyup, 它能识别所有的键(包括功能键)
- Keypress 不识别功能键,但是keyCode属性能区分大小写,返回不同的ASCII值
- 案例:使用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>
- ASCII码表
1.1.3 案例:模拟京东按键输入内容
(03-模拟京东按键输入内容案例.avi)
-
需求:当我们按下 s 键, 光标就定位到搜索框(文本框获得焦点)
-
分析:
- 核心思路: 检测用户是否按下了s 键,如果按下s 键,就把光标定位到搜索框里面
- 使用键盘事件对象里面的keyCode属性, 判断用户按下的是否是s键
- 搜索框获得焦点: 使用 js 里面的 focus() 方法
- 案例代码:
<input type="text">
<script>
// 获取输入框
var search = document.querySelector('input');
// 给document注册keyup事件
document.addEventListener('keyup', function(e) {
// 判断keyCode的值
if (e.keyCode === 83) {
// 触发输入框的获得焦点事件
search.focus();
}
})
</script>
-
注意:
-
如果通过键盘在text中输入内容,整个流程是:keydown,keypress,内容输入到text文本框中,keyup
-
这里如果使用keydown或者keypress,你会发现,虽然输入s能够让搜索框获取焦点,但是s也被输入到文本框中了,因为上边的流程,keydown和keypress是在内容输入之前。
-
所以当你使用keydown或者keypress事件处理这个逻辑的话,会先获取焦点,然后内容输入。
-
所以,只有keyup可以。
-
-
问题1:search.focus()啥意思?
- 事件触发有两种方式:
- 用户触发
- 程序员通过代码触发
- 而search.focus(); 就属于程序员主动触发获取焦点事件
- 事件触发有两种方式:
-
问题2:search.focus()与search.οnfοcus=function(){}的关系?
- search.οnfοcus=function(){}是绑定获取焦点事件
- search.focus()是触发获取焦点事件
1.1.4 案例:模拟京东快递单号查询
-
(4-模拟京东快递单号查询(上).avi - 05-模拟京东快递单号查询(下).avi)
-
需求:当我们在文本框中输入内容时,文本框上面自动显示大字号的内容。
-
效果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cMzRYWU7-1575684662592)(images/1551318882189.png)]
- 显示大号字效果如下:
- 分析:
- 快递单号输入内容时, 上面的大号字体盒子(con)显示(这里面的字号更大)
- 同时把快递单号里面的值(value)获取过来赋值给 con盒子(innerText)做为内容
- 如果快递单号里面内容为空,则隐藏大号字体盒子(con)盒子
- 注意: keydown 和 keypress 在文本框里面的特点: 他们两个事件触发的时候,文字还没有落入文本框中。
- keyup事件触发的时候, 文字已经落入文本框里面了
- 所以,如果想监听文本框中输入的内容,只能使用keyup
- 当我们失去焦点,就隐藏这个con盒子
- 当我们获得焦点,并且文本框内容不为空,就显示这个con盒子
- 案例代码:
<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() { //**如果想监听文本框中输入的内容,只能使用keyup**
// 判断输入框内容是否为空
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>
1.2. BOM
(06-BOM导读.avi)
目标:
- 能够说出什么是 BOM
- 能够知道浏览器的顶级对象 window
- 能够写出页面加载事件以及注意事项
- 能够写出两种定时器函数并说出区别
- 能够说出 JS 执行机制
- 能够使用 location 对象完成页面之间的跳转
- 能够知晓 navigator 对象涉及的属性
- 能够使用 history 提供的方法实现页面刷新
1.2.1. 什么是BOM
(07-BOM概述.avi)
BOM(Browser Object Model)即浏览器对象模型,它提供了独立于内容而与浏览器窗口进行交互的对象,其核心对象是 window。
BOM 由一系列相关的对象构成,并且每个对象都提供了很多方法与属性。
BOM 缺乏标准,JavaScript 语法的标准化组织是 ECMA,DOM 的标准化组织是 W3C,BOM 最初是Netscape 浏览器标准的一部分。
1.2.2. BOM的构成
BOM 比 DOM 更大,它包含 DOM
1.2.3. 顶级对象window
- window 对象是浏览器的顶级对象,它具有双重角色。
- 它是 JS 访问浏览器窗口的一个接口。
- 它是一个全局对象。定义在全局作用域中的变量、函数都会变成 window 对象的属性和方法。
- 在调用的时候可以省略 window,前面学习的对话框都属于 window 对象方法,如 alert()、prompt() 等。
- 注意:window下的一个特殊属性 window.name
1.2.4. window对象的常见事件 ***
1. 两种页面(窗口)加载事件
(09-页面加载事件.avi)
第1种
语法:
window.onload = function(){}
或者
window.addEventListener("load",function(){});
window.onload 是窗口 (页面)加载事件,当文档内容完全加载完成会触发该事件(包括图像、脚本文件、CSS 文件等), 就调用的处理函数。
问题:
为啥有跟click事件一样,有两种写法,因为load也是一个事件,是界面加载完成事件。
注意:
- 有了 window.onload 就可以把 JS 代码写到页面元素的上方,因为 onload 是等页面内容全部加载完毕,再去执行处理函数。
- window.onload 传统注册事件方式 只能写一次,如果有多个,会以最后一个 window.onload 为准。
- 如果使用 addEventListener 则没有限制
第2种
语法:
document.addEventListener('DOMContentLoaded',function(){})
注意:
-
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>
2. 调整窗口大小事件
(10-调整窗口大小事件.avi)
语法:
window.onresize = function(){}
window.addEventListener("resize",function(){});
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>
1.2.5. 定时器(两种)
(11-定时器之setTimeout.avi)
window 对象给我们提供了 2 个非常好用的方法-定时器。
-
setTimeout()
-
setInterval()
1. setTimeout() 炸弹定时器
开启定时器
语法:
window.setTimeout(回调函数, [延迟的毫秒数]);
setTimeout() 方法用于设置一个定时器,该定时器在定时器到期后执行调用函数。
注意:
- window 可以省略。
- 这个调用函数可以直接写函数,或者写函数名或者采取字符串‘函数名()'三种形式。第三种不推荐
- 延迟的毫秒数省略默认是 0,如果写,必须是毫秒。
- 因为定时器可能有很多,所以我们经常给定时器赋值一个标识符。
回调函数理解:
(12-回调函数以及5秒之后自动关闭的广告.avi)
setTimeout(callback,delayTime) 这个调用函数我们也称为回调函数 callback
call: 调用
back:回头
- 普通函数是按照代码顺序直接调用。
- 简单理解: 回调,就是回头调用的意思。上一件事干完,再回头调用这个函数。
- 定时器:setTimeout(function(){},2000),倒计时数完,再回头调用 回调函数
- function setTimeout(callback,delaytime){ // callback = function(){},delaytime = 2000
- 时间间隔代码
- 一旦时间数完,callback();
- }
- 点击事件:div.addEventListener(‘click’,function(){}),用户点击完成,再回头调用 回调函数
- 定时器:setTimeout(function(){},2000),倒计时数完,再回头调用 回调函数
- 例如:定时器中的调用函数,事件处理函数,也是回调函数。
- 以前我们讲的 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);
//setTimeout('callback()', 3000); // 我们不提倡这个写法
</script>
案例:5秒后关闭广告
效果: 如下图广告,5s之后自动关闭
分析:
- 核心思路:5秒之后,就把这个广告隐藏起来
- 用定时器setTimeout
案例代码:
<body>
<img src="images/ad.jpg" alt="" class="ad">
<script>
// 获取要操作的元素
var ad = document.querySelector('.ad');
// 开启定时器
setTimeout(function() {
ad.style.display = 'none';
}, 5000);
</script>
</body>
停止定时器
(13-清除定时器clearTimeout.avi)
接下来我们来讨论一下如何停止定时器
语法:
window.clearTimeout(timeoutID)
clearTimeout()方法取消了先前通过调用 setTimeout() 建立的定时器。
注意:
- window 可以省略。
- 里面的参数就是定时器的标识符 (如下代码中的timer)。
例子代码:
<button>点击停止定时器</button>
<script>
var btn = document.querySelector('button');
// 开启定时器
var timer = setTimeout(function() {
console.log('爆炸了');
}, 5000);
// 给按钮注册单击事件
btn.addEventListener('click', function() {
// 停止定时器
clearTimeout(timer);
})
</script>
2. setInterval() 闹钟定时器 ***
(14-定时器之setInterval.avi)
开启定时器
语法:
window.setInterval(回调函数, [间隔的毫秒数]);
setInterval() 方法重复调用一个函数,每隔这个时间,就去调用一次回调函数。
注意:
- window 可以省略。
- 这个调用函数可以直接写函数,或者写函数名或者采取字符串 ‘函数名()’ 三种形式。
- 间隔的毫秒数省略默认是 0,如果写,必须是毫秒,表示每隔多少毫秒就自动调用这个函数。
- 因为定时器可能有很多,所以我们经常给定时器赋值一个标识符。
- 第一次执行也是间隔毫秒数之后执行,之后每隔毫秒数就执行一次。
例子代码:
// 1. setInterval
// 语法规范: window.setInterval(调用函数, 延时时间);
setInterval(function() {
console.log('继续输出');
}, 1000);
// 2. setTimeout 延时时间到了,就去调用这个回调函数,只调用一次 就结束了这个定时器
// 3. setInterval 每隔这个延时时间,就去调用这个回调函数,会调用很多次,重复调用这个函数
案例:倒计时
(15-倒计时效果.avi)
效果:
分析:
- 这个倒计时是不断变化的,因此需要定时器来自动变化(setInterval)
- 三个黑色盒子里面分别存放时分秒
- 三个黑色盒子利用innerHTML 放入计算的小时分钟秒数
- 第一次执行也是间隔毫秒数,因此刚刷新页面会有空白
- 最好采取封装函数的方式, 这样可以先调用一次这个函数,防止刚开始刷新页面有空白问题
案例代码:
<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>
停止定时器
(16-清除定时器clearInterval.avi)
语法:
window.clearInterval(intervalID);
clearInterval()方法取消了先前通过调用 setInterval()建立的定时器。
注意:
- window 可以省略。
- 里面的参数就是定时器的标识符
代码:
<body>
<button class="begin">开启定时器</button>
<button class="stop">停止定时器</button>
<script>
var begin = document.querySelector('.begin');
var stop = document.querySelector('.stop');
// 全局变量 null是一个空对象(定时器是一个对象,但是setInterval并没有返回定时器对象,而是返回的定时器id)
var timer = null;
begin.addEventListener('click', function() {
timer = setInterval(function() {
console.log('ni hao ma');
}, 1000);
console.log(timer); // 1 ,定时器的id
})
stop.addEventListener('click', function() {
clearInterval(timer);
})
// 补充内容:
// 变量的默认初始值,刚开始不知道是什么,那怎么给初始值?变量要存储的数据是什么类型。
var str = '';
// sdfasdf 在经过一堆代码执行之后,才知道具体值是什么
str = 'zs';
// var num = 0,1,-1
// var flag = true,false;
// var obj = null;
</script>
</body>
3. 案例:发送短信倒计时
(17-发送短信案例.avi)
需求:点击按钮后,该按钮60秒之内不能再次点击,防止重复发送短信。
效果:
点击之后的效果:
分析:
- 按钮点击之后,会禁用 disabled 为true
- 同时按钮里面的内容会变化, 注意 button 里面的内容通过 innerHTML修改
- 里面秒数是有变化的,因此需要用到定时器
- 定义一个变量,在定时器里面,不断递减
- 如果变量为0 说明到了时间,我们需要停止定时器,并且复原按钮初始状态。
案例代码:
手机号码: <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>
1.2.6. this指向问题
(18-this指向问题.avi)
this的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定this到底指向谁,一般情况下this的最终指向的是那个调用它的对象。
现阶段,我们先了解一下几个this指向
- 全局作用域或者普通函数中,this指向全局对象window(注意定时器里面的this也指向window)
- 方法调用中,谁调用方法,this指向谁
- 构造函数中this指向构造函数的实例(通过构造函数new出来的对象)
例子代码:
<button>点击</button>
<script>
// this 指向问题 一般情况下this的最终指向的是那个调用它的对象
// 1. 全局作用域或者普通函数中this指向全局对象window( 注意定时器里面的this指向window)
console.log(this);
function fn() {
console.log(this);
}
window.fn();
window.setTimeout(function() { // 倒计时一到,其实是有window调用匿名函数,所以也可以理解为谁调用此函数,this指向谁
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>
1.2.7. location对象
(22-location对象常见属性.avi)
讲这块内容之前,先讲解1.3JS执行机制
1. 什么是 location 对象
window 对象给我们提供了一个 location 属性用于获取或设置窗体的 URL,并且可以用于解析 URL 。 因为这个属性返回的是一个对象,所以我们将这个属性也称为 location 对象。
2. URL
-
统一资源定位符 (Uniform Resource Locator, URL) 是互联网上标准资源的地址。
-
互联网上的每个文件都有一个唯一的 URL,它包含的信息指出文件的位置以及浏览器应该怎么处理它。
-
URL 的一般语法格式为:
protocol://host[:port]/path/[?query]#fragment http://www.itcast.cn/index.html?name=andy&age=18#link
组成如图:
3. location 对象的属性
4. 案例:5分钟自动跳转页面
(23-5秒钟之后跳转页面.avi)
效果:
分析:
- 利用定时器做倒计时效果
- 时间到了,就跳转页面。 使用 location.href
案例代码:
<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>
5. 案例:获取URL参数
(24-获取URL参数.avi)
需求:获取url参数内容如下:?之后的内容,即为参数
分析:
- 第一个登录页面,里面有提交表单, action 提交到 index.html页面
- 第二个页面,可以使用第一个页面的参数,这样实现了一个数据不同页面之间的传递效果
- 第二个页面之所以可以使用第一个页面的数据,是利用了URL 里面的 location.search参数
- 在第二个页面中,需要把这个参数提取。
- 第一步去掉? 利用 substr
- 第二步 利用=号分割 键 和 值 split(‘=‘)
- 第一个数组就是键 第二个数组就是值
案例代码:login.html
<body>
<!--
补充内容:
点击submit提交按钮之后,表单会进行提交
1. action是什么,跳转的url
2. 提交方式:get,post,默认get
3. 如果是get,就会在跳转的url后跟上参数,
index.html?uname=red: 会将表单中所有带name的表单标签的数据进行收集
表单标签的name的值,作为参数的key
表单标签的value的值,作为参数的value
url携带参数的格式: index.html?key1=value1&key2=value2....
-->
<form action="index.html">
用户名: <input type="text" name="uname" value='red'>
<input type="submit" value="登录">
</form>
</body>
案例代码:index.html
<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>
6. location对象的常见方法
(25-location常见方法.avi)
常见方法如下:
例子代码:
<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>
1.2.8. navigator对象
(26-navigator对象.avi)
navigator 对象包含有关浏览器的信息,它有很多属性,我们最常用的是 userAgent,该属性可以返回由客户机发送服务器的 user-agent 头部的值。
下面前端代码可以判断用户那个终端打开页面,实现跳转
if((navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i))) {
window.location.href = ""; //手机
} else {
window.location.href = ""; //电脑
}
理解:
这里的userAgent,翻译过来是用户代理,但是真正的意思是浏览器信息。
怎么理解呢?
用户访问www.jd.com来问京东服务器要数据,那用户是通过浏览器这个桥梁来访问的,而不是用户直接就可以京东服务器。
所以对于服务器来说,浏览器就是用户的代理。
1.2.9 history对象
(27-history对象.avi)
window对象给我们提供了一个 history对象,与浏览器历史记录进行交互。该对象包含用户(在浏览器窗口中)访问过的URL。
history对象一般在实际开发中比较少用,但是会在一些 OA 办公系统中见到。
1.3. JS执行机制 (了解)
1.3.1 JS 是单线程
(19-js 同步和异步.avi)
- JavaScript 语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。
- 这是因为 Javascript 这门脚本语言诞生的使命所致——JavaScript 是为处理页面中用户的交互,以及操作 DOM 而诞生的。
- 比如我们对某个 DOM 元素进行添加和删除操作,不能同时进行。 应该先进行添加,之后再删除。
单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。
这样所导致的问题是: 如果 JS 执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞的感觉。
问题:
以下代码执行的结果是什么?
console.log(1); // 先打印1
setTimeout(function () {
console.log(3); // 1s钟之后打印3
}, 1000);
console.log(2);
// 如果是单线程,这个代码只有在打印3之后,才打印2。 时间比较久,需要等待上个定时器执行完成
// 如果是多线程,就会打印1,直接打印2,再去打印3 (实际打印:1,2,3)
那么单线程,多线程是什么呢?继续往下走:
1.3.2 同步任务和异步任务
单线程导致的问题就是后面的任务等待前面任务完成,如果前面任务很耗时(比如读取网络数据),后面任务不得不一直等待!!
为了解决这个问题,利用多核 CPU 的计算能力,HTML5 提出 Web Worker 标准,允许 JavaScript 脚本创建多个线程,但是子线程完全受主线程控制。于是,JS 中出现了同步任务和异步任务。
同步
前一个任务结束后再执行后一个任务,程序的执行顺序与任务的排列顺序是一致的、同步的。比如做饭的同步做法:我们要烧水煮饭,等水开了(10分钟之后),再去切菜,炒菜。
异步
你在做一件事情时,因为这件事情会花费很长时间,在做这件事的同时,你还可以去处理其他事情。比如做饭的异步做法,我们在烧水的同时,利用这10分钟,去切菜,炒菜。
JS中所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。 同步任务指的是: 在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务; 异步任务指的是: 不进入主线程、而进入”任务队列”的任务,当主线程中的任务运行完了,才会从”任务队列”取出异步任务放入主线程执行。
理解:
同步,不是两件事情同时去做,而是先做一件事情,然后再做另一件事情。他是单线程,一个人依次做多件事情
异步,是两件事情同时去做,他是多线程,多个人同时做多个事情
同步任务,异步任务
(20-同步任务和异步任务执行过程.avi)
再来看一个问题:
console.log(1);
setTimeout(function() {
console.log(3);
}, 0);
console.log(2);
// 结果还是1,2,3
// 为啥呢?继续往下走:
我们来看一下同步任务和异步任务:
了解完这一块之后,我们知道了刚刚的问题代码中,setTimeout的回调函数,是异步任务,放到任务队列了,但是为啥后执行呢?继续往下走:
1.3.3 JS执行机制
1. 执行顺序
- 先执行执行栈中的同步任务。
- 异步任务(回调函数)放入任务队列中。(但是不执行回调函数)
- 一旦执行栈中的所有同步任务执行完毕,系统就会按次序读取任务队列中的异步任务,于是被读取的异步任务结束等待状态,进入执行栈,开始执行。
如图:
2. 事件循环
(21-js执行机制.avi)
问题:
console.log(1);
document.onclick = function () {
console.log('click');
}
console.log(2);
setTimeout(function () {
console.log(3)
}, 3000)
分析过程:
-
此代码中有两个异步任务,我们先给同步代码和异步代码分开:
-
当主线代码执行完了之后,1,2打印完了。
-
3s之后,定时任务时间到了,就将fn放到异步任务的任务队列中了:
-
然后执行异步任务的打印3。最终出现1,2,3。
-
如果setTimeout的回调函数的任务执行完成,异步任务队列就为空了。
-
如果此时点击了document,onclick的回调函数就放到异步任务了
-
然后执行,最终出现1,2,3,click
-
执行完了,异步任务又会清空
-
最后有个注意事项,主线程代码执行完成之后,会反复去异步任务中看是否有任务需要执行
由于主线程不断的重复获得任务、执行任务、再获取任务、再执行,所以这种机制被称为事件循环( event loop)。
事件循环综合图:
1.3.4 代码思考题
console.log(1);
document.onclick = function() {
console.log('click');
}
setTimeout(function() {
console.log(3)
}, 3000)
console.log(2);
day05 - Web APIs
学习目标:
能够说出常见 offset 系列属性的作用
能够说出常见 client 系列属性的作用
能够说出常见 scroll 系列属性的作用
能够封装简单动画函数
**1.1. **元素偏移量 offset 系列 ***
1.1.1 offset 概述
-
offset 翻译过来就是偏移量, 我们使用 offset系列相关属性可以动态的得到该元素的位置(偏移)、大小等。
-
获得元素距离带有定位父元素的位置
-
获得元素自身的大小(宽度高度)
-
注意:返回的数值都不带单位
-
-
offset 系列常用属性:
-
分析图
1.1.2 offset 与 style 区别
offset
-
offset 可以得到任意样式表中的样式值
-
offset 系列获得的数值是没有单位的
-
offsetWidth 包含padding+border+width
-
offsetWidth 等属性是只读属性,只能获取不能赋值
-
所以,我们想要获取元素大小位置,用offset更合适
style
- style 只能得到行内样式表中的样式值
- style.width 获得的是带有单位的字符串
- style.width 获得不包含padding和border 的值
- style.width 是可读写属性,可以获取也可以赋值
- 所以,我们想要给元素更改值,则需要用style改变
1.1.3 案例:获取鼠标在盒子内的坐标
效果:
分析:
- 我们在盒子内点击,想要得到鼠标距离盒子左右的距离。
- 首先得到鼠标在页面中的坐标(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.1.4 案例:模态框拖拽
弹出框,我们也称为模态框。
效果:
案例需求:
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);
})
})
问题:
1. 为啥mousemove要添加给document,而不是title?
答:如果添加给title,可以试一下快速拖动,就会发现如果鼠标划出title,则模态框就不会一起移动了。
模态框分析:
问题:
在mousedown 中打印login.offsetLeft 发现683(1366屏幕分辨率),login的位置是left:50%,然后通过 transform: translate(-50%, -50%); 又进行了位移,但是offsetLeft和offsetTop这两个属性不计算translate移动的值。
所以打印的offsetLeft只有left:50%的距离。
如果再打印x,y,发现是负值,这是因为offsetLeft值比真实位置大了login宽度的一半:256
1.1.6 案例:仿京东放大镜
我们在京东详情界面,做一个商品图片放大镜的效果。
效果:
案例需求:
- 整个案例可以分为三个功能模块
- 鼠标经过小图片盒子, 黄色的遮挡层 和 大图片盒子显示,离开隐藏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;
// (2) 减去盒子的宽高的一半,让鼠标在盒子中间(盒子往上往左各走自身一半)
var maskX = x - mask.offsetWidth / 2;
var maskY = y - mask.offsetHeight / 2;
// (3) 限制黄色盒子移动的位置
// 如果x坐标小于了0 就让他停在0 的位置
// 如果x坐标大于了 最大移动距离,就让他停在最大局里的位置
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';
//(4)让大图片跟着黄色盒子一起移动
var bigIMg = document.querySelector('.bigImg'); // 大图片
var bigMax = bigIMg.offsetWidth - big.offsetWidth; // 大图片最大移动距离
// 比例
var rate = bigMax / maskMax;
// 大图片的移动距离 = 黄色盒子移动距离 * 比例
var bigX = maskX * rate;
var bigY = maskY * rate;
bigIMg.style.left = bigX + 'px'; //大图移动方向与黄色盒子相反,所以添加-负号
bigIMg.style.top = bigY + 'px';
})
})
京东放大镜分析:
1.2. 元素可视区 client 系列
1.2.1 client概述
client 翻译过来就是客户端,我们使用 client 系列的相关属性来获取元素可视区的相关信息。通过 client
系列的相关属性可以动态的得到该元素的边框大小、元素大小等。
client相关属性如下:
client:理解为快递的内容,不包含快递箱子(边框)
clientTop:快递的内容,距离整个元素边界的上间距。
属性分析图:
1.2.2. 淘宝 flexible.js 源码分析
-
立即执行函数 (function(){})() 或者 (function(){}())
-
主要作用: 创建一个独立的作用域,避免了命名冲突问题。
- 这句话是有问题的,普通函数就是独立的作用域
- 所以,立即执行函数真正的作用就是立即执行(缺点:只能调用一次)
-
应用场景:工具制作者,帮助程序员调用,程序员只管引入,不需要再调用。
-
下面三种情况都会刷新页面,都会触发 load 事件。
1.a标签的超链接
2.F5或者刷新按钮(强制刷新)
3.前进后退按钮
-
但是 火狐中,有个特点,有个“往返缓存”,这个缓存中不仅保存着页面数据,还保存了DOM和JavaScript的状态;实际上是将整个页面都保存在了内存里。
-
所以此时后退按钮不能刷新页面。
-
此时可以使用 pageshow事件来触发。,这个事件在页面显示时触发,无论页面是否来自缓存。
-
在重新加载页面中,pageshow会在load事件触发后触发;
-
根据事件对象中的persisted来判断是否是缓存中的页面触发的pageshow事件
-
persisted:判断页面是否来自缓存,如果是返回true,如果不是返回false
-
注意这个事件给window添加。
-
persisted:保持,理解为使用保持/保存下来的缓存
1.3.元素滚动 scroll 系列
1.3.1. scroll 概述
scroll 翻译过来就是滚动的,我们使用 scroll 系列的相关属性可以动态的得到该元素的大小、滚动距离等。
scroll相关属性如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZZ6jHwU1-1575684662738)(images\图片5.png)]
属性分析图:
1.3.2. 页面被卷去的头部
- 如果浏览器的高(或宽)度不足以显示整个页面时,会自动出现滚动条。
- 当滚动条向下滚动时,页面上面被隐藏掉的高度,我们就称为页面被卷去的头部。
- 滚动条在滚动时会触发 onscroll事件。
1.3.3.案例:仿淘宝固定右侧侧边栏
效果:
案例需求:
- 原先侧边栏是绝对定位
- 当页面滚动到一定位置,侧边栏改为固定定位
- 页面继续滚动,会让 返回顶部显示出来 (粉红块,某些条件下回显示返回顶部文字)
案例分析:
- 需要用到页面滚动事件 scroll 因为是页面滚动,所以事件源是document
- 滚动到某个位置,就是判断页面被卷去的上部值。
- 页面被卷去的头部:可以通过window.pageYOffset 获得 如果是被卷去的左侧window.pageXOffset
- 注意,元素被卷去的头部是element.scrollTop , 如果是页面被卷去的头部 则是 window.pageYOffset
- 其实这个值 可以通过盒子的 offsetTop可以得到,如果大于等于这个值,就可以让盒子固定定位了
案例代码:
//1. 获取元素
var sliderbar = document.querySelector('.slider-bar'); // 粉色div,右侧侧边栏
var banner = document.querySelector('.banner'); //青色div
// banner距离界面顶部的距离
var bannerTop = banner.offsetTop;
// 当我们侧边栏固定定位之后应该变化的数值 (侧边栏距离界面顶部的长度 - banner距离界面顶部的长度)
var sliderbarTop = sliderbar.offsetTop - bannerTop;
// 获取main 主体元素
var main = document.querySelector('.main'); // 绿色主体部分
var goBack = document.querySelector('.goBack'); // 返回顶部的span
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) { // 如果滚动距离大于等于banner距离顶部的距离(其实就是banner滚动到了页面顶部)
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';
}
})
案例补充:
元素在界面上的位置(offsetTop,offsetLeft),不会随着界面滚动而发生改变。他会因为通过style.left/top的改变而改变。
1.3.5.页面被卷去的头部兼容性解决方案
需要注意的是,获取页面被卷去的头部高度,有兼容性问题,因此通常有如下几种写法:
- 声明了 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
1.4. 三大系列总结
他们主要用法:
1.offset系列 经常用于获得元素位置 offsetLeft offsetTop
2.client经常用于获取元素大小 clientWidth clientHeight
3.scroll 经常用于获取滚动距离 scrollTop scrollLeft
4.注意页面滚动的距离通过 window.pageYOffset 获得
1.5. mouseenter 和mouseover的区别
- 当鼠标移动到元素上时就会触发mouseenter 事件
- 类似 mouseover,它们两者之间的差别是
- mouseover 鼠标经过自身盒子会触发,经过子盒子还会触发。mouseenter 只会经过自身盒子触发
- 之所以这样,就是因为mouseenter不会冒泡
- 跟mouseenter搭配鼠标离开 mouseleave 同样不会冒泡
mouseenter与mouseover的区别:
mouseenter在父子盒子之间来回切换,不会重复触发mouseenter事件。只有第一次进入父盒子区域(父盒子区域,和子盒子区域),才会触发。
mouseover在父子盒子之间来回切换,会重复触发mouseover事件。
1.6. 动画函数封装
1.6.1. 动画实现原理
核心原理:通过定时器 setInterval() 不断移动盒子位置。
实现步骤:
- 获得盒子当前位置
- 让盒子在当前位置加上1个移动距离
- 利用定时器不断重复这个操作
- 加一个结束定时器的条件
- 注意此元素需要添加定位,才能使用element.style.left
1.6.2. 动画函数给不同元素记录不同定时器
如果多个元素都使用这个动画函数,每次都要var 声明定时器。我们可以给不同的元素使用不同的定时器(自己专门用自己的定时器)。
核心原理:利用 JS 是一门动态语言,可以很方便的给当前对象添加属性。
function animate(obj, target) {
// 当我们不断的点击按钮,这个元素的速度会越来越快,因为开启了太多的定时器
// 解决方案就是 让我们元素只有一个定时器执行
// 先清除以前的定时器,只保留当前的一个定时器执行
clearInterval(obj.timer);
// var timer = setInterval(....)
obj.timer = setInterval(function() {
if (obj.offsetLeft == target) {//注意这里应该是判断是否相等,而不是大于等于
// 停止动画 本质是停止定时器
clearInterval(obj.timer);
}
obj.style.left = obj.offsetLeft + 1 + 'px';
}, 30);
}
animate(div,500); // 局部变量timer , 5s 给div添加timer属性,并且赋值为1
animate(div,800); // 局部变量timer , 5s 给div重新赋值timer属性,为2
// 在函数外侧,也可以停止定时器:clearInterval(div.timer);
6 - Web APIs**
学习目标:
能够封装简单动画函数
能够理解缓动动画的封装
能够使用动画函数
能够写出网页轮播图案例
能够写出移动端触屏事件
1.1. 动画函数封装
1.1.1 缓动效果原理
缓动动画就是让元素运动速度有所变化,最常见的是让速度慢慢停下来
思路:
- 让盒子每次移动的距离慢慢变小,速度就会慢慢落下来。
- 核心算法: (目标值 - 现在的位置) / 10 做为每次移动的距离步长
- 停止的条件是: 让当前盒子位置等于目标位置就停止定时器
- 注意步长值需要取整
代码:
<script>
// 缓动动画函数封装obj目标对象 target 目标位置
// 思路:
// 1. 让盒子每次移动的距离慢慢变小, 速度就会慢慢落下来。
// 2. 核心算法:(目标值 - 现在的位置) / 10 做为每次移动的距离 步长
// 3. 停止的条件是: 让当前盒子位置等于目标位置就停止定时器
function animate(obj, target) {
// 先清除以前的定时器,只保留当前的一个定时器执行
clearInterval(obj.timer);
obj.timer = setInterval(function () {
// 步长值写到定时器的里面
var step = (target - obj.offsetLeft) / 10;
if (obj.offsetLeft == target) {
// 停止动画 本质是停止定时器
clearInterval(obj.timer);
}
// 把每次加1 这个步长值改为一个慢慢变小的值 步长公式:(目标值 - 现在的位置) / 10
obj.style.left = obj.offsetLeft + step + 'px';
}, 15);
}
var span = document.querySelector('span');
var btn = document.querySelector('button');
btn.addEventListener('click', function () {
// 调用函数
animate(span, 500);
})
// 匀速动画 就是 盒子是当前的位置 + 固定的值 10
// 缓动动画就是 盒子当前的位置 + 变化的值(目标值 - 现在的位置) / 10)
</script>
1.1.2 动画函数多个目标值之间移动
可以让动画函数从 800 移动到 500。
当我们点击按钮时候,判断步长是正值还是负值
1.如果是正值,则步长往大了取整
2.如果是负值,则步长 向小了取整
代码:
<script>
// 缓动动画函数封装obj目标对象 target 目标位置
// 思路:
// 1. 让盒子每次移动的距离慢慢变小, 速度就会慢慢落下来。
// 2. 核心算法:(目标值 - 现在的位置) / 10 做为每次移动的距离 步长
// 3. 停止的条件是: 让当前盒子位置等于目标位置就停止定时器
function animate(obj, target) {
// 先清除以前的定时器,只保留当前的一个定时器执行
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);
}
// 把每次加1 这个步长值改为一个慢慢变小的值 步长公式:(目标值 - 现在的位置) / 10
obj.style.left = obj.offsetLeft + step + 'px';
}, 15);
}
var span = document.querySelector('span');
var btn500 = document.querySelector('.btn500');
var btn800 = document.querySelector('.btn800');
btn500.addEventListener('click', function () {
// 调用函数
animate(span, 500);
})
btn800.addEventListener('click', function () {
// 调用函数
animate(span, 800);
})
// 匀速动画 就是 盒子是当前的位置 + 固定的值 10
// 缓动动画就是 盒子当前的位置 + 变化的值(目标值 - 现在的位置) / 10)
</script>
分析:0-500为啥到496.4px不走了,到不了500?800-500为啥到505px不走了,到不了500?
运行以下代码即可:增加了三个log,屏蔽对step的取整代码
<body>
<button class="btn500">点击夏雨荷到500</button>
<button class="btn800">点击夏雨荷到800</button>
<span>夏雨荷</span>
<script>
// 缓动动画函数封装obj目标对象 target 目标位置
// 思路:
// 1. 让盒子每次移动的距离慢慢变小, 速度就会慢慢落下来。
// 2. 核心算法:(目标值 - 现在的位置) / 10 做为每次移动的距离 步长
// 3. 停止的条件是: 让当前盒子位置等于目标位置就停止定时器
function animate(obj, target) {
// 先清除以前的定时器,只保留当前的一个定时器执行
clearInterval(obj.timer);
obj.timer = setInterval(function() {
console.log('动画之前:' + obj.offsetLeft);// 增加的log******
// 步长值写到定时器的里面
// 把我们步长值改为整数 不要出现小数的问题
// var step = Math.ceil((target - obj.offsetLeft) / 10);
var step = (target - obj.offsetLeft) / 10;
// step = step > 0 ? Math.ceil(step) : Math.floor(step); // 屏蔽解决的代码******
console.log("步长" + step);// 增加的log******
if (obj.offsetLeft == target) {
// 停止动画 本质是停止定时器
clearInterval(obj.timer);
}
// 把每次加1 这个步长值改为一个慢慢变小的值 步长公式:(目标值 - 现在的位置) / 10
obj.style.left = obj.offsetLeft + step + 'px';
console.log('动画之后:' + obj.offsetLeft);// 增加的log******
}, 15);
}
var span = document.querySelector('span');
var btn500 = document.querySelector('.btn500');
var btn800 = document.querySelector('.btn800');
btn500.addEventListener('click', function() {
// 调用函数
animate(span, 500);
})
btn800.addEventListener('click', function() {
// 调用函数
animate(span, 800);
})
// 匀速动画 就是 盒子是当前的位置 + 固定的值 10
// 缓动动画就是 盒子当前的位置 + 变化的值(目标值 - 现在的位置) / 10)
</script>
</body>
运行之后打印结果分析如下:
1.1.3 动函数添加回调函数
回调函数原理:函数可以作为一个参数。将这个函数作为参数传到另一个函数里面,当那个函数执行完之后,再执行传进去的这个函数,这个过程就叫做回调。
回调函数写的位置:定时器结束的位置
<script>
// 缓动动画函数封装obj目标对象 target 目标位置
// 思路:
// 1. 让盒子每次移动的距离慢慢变小, 速度就会慢慢落下来。
// 2. 核心算法:(目标值 - 现在的位置) / 10 做为每次移动的距离 步长
// 3. 停止的条件是: 让当前盒子位置等于目标位置就停止定时器
function animate(obj, target, callback) {
// console.log(callback); callback = function() {} 调用的时候 callback()
// 先清除以前的定时器,只保留当前的一个定时器执行
clearInterval(obj.timer);
obj.timer = setInterval(function() {
// 步长值写到定时器的里面
var step = (target - obj.offsetLeft) / 10;
// 把我们步长值改为整数 不要出现小数的问题
step = step > 0 ? Math.ceil(step) : Math.floor(step);
if (obj.offsetLeft == target) {
// 停止动画 本质是停止定时器
clearInterval(obj.timer);
//回调函数写到定时器结束里面
//如果不判断,调用者不传递callback,就会报错
//错误信息:Uncaught TypeError: callback is not a function
if (callback) {
// 调用函数
callback();
}
// obj.style.backgroundColor= 'red';
// obj.style.width= '100px';
}
obj.style.left = obj.offsetLeft + step + 'px';
}, 15);
}
var span = document.querySelector('span');
var btn500 = document.querySelector('.btn500');
var btn800 = document.querySelector('.btn800');
btn500.addEventListener('click', function() {
animate(span, 500);
})
btn800.addEventListener('click', function() {
animate(span, 800, function() {
span.style.backgroundColor = 'red';
});
});
//回调函数理解:假设如下
// 动画函数的制作者:A程序员
// 动画函数的调用者:B程序员,程序员C
// 需求: 程序员B,希望在span做完动画之后,改变颜色为红色
// 需求: 程序员C,希望在div做完动画之后,改变宽度为100px
// 如果程序员A,在动画结束之后,分别添加以上两行代码,那么是不对的,这些需求是未知因素,应该通过参数传递
// 那么参数如何传递具体代码呢?通过函数
</script>
元素对象添加自定义属性,两种方式总结:
span.setAttribute('index', 0); // setAttribute设置的自定义属性,在html结构中可以看到
span.index = 0;// 点语法添加的自定义属性,在html结构中无法看到
//应用场景:
//操作的是元素标签对象时,要添加自定义属性,使用setAttribute
//操作的是普通对象(Object,Person),要添加自定义属性,使用点语法
1.2. 常见网页特效案例
1.2.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);//另一种写法ul.offsetLeft-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);
})
1.2.2. 节流阀
-
防止轮播图按钮连续点击造成播放过快。
-
节流阀目的:当上一个函数动画内容执行完毕,再去执行下一个函数动画,让事件无法连续触发。
-
核心实现思路:利用回调函数,添加一个变量来控制,锁住函数和解锁函数。
-
步骤:
- 开始设置一个变量var flag= true;
- If(flag){flag = false; do something} 关闭水龙头
- 利用回调函数动画执行完毕, flag = true 打开水龙头
1.2.3. 案例:返回顶部
淘宝案例,实现返回顶部功能。
效果:
思路:
- 带有动画的返回顶部
- 此时可以继续使用我们封装的动画函数
- 只需要把所有的left 相关的值改为 跟 页面垂直滚动距离相关就可以了
- 页面滚动了多少,可以通过 window.pageYOffset 得到
- 最后是页面滚动,使用 window.scroll(x,y)
代码:
// 3. 当我们点击了返回顶部模块,就让窗口滚动的页面的最上方
goBack.addEventListener('click', function() {
// 里面的x和y 不跟单位的 直接写数字即可
// window.scroll(0, 0);
// 因为是窗口滚动 所以对象是window
animate(window, 0);
});
1.2.4. 案例:筋头云案例
效果:
当鼠标移动到下一个菜单的时候,背景的效果会 慢慢移动过去 。
思路:
- 利用动画函数做动画效果
- 原先筋斗云的起始位置是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;
});
}
})
1.3. 触屏事件 ***
1.3.1. 触屏事件概述
移动端浏览器兼容性较好,我们不需要考虑以前 JS 的兼容性问题,可以放心的使用原生 JS 书写效果,但是移动端也有自己独特的地方。比如触屏事件 touch(也称触摸事件),Android和 IOS 都有。
touch 对象代表一个触摸点。触摸点可能是一根手指,也可能是一根触摸笔。触屏事件可响应用户手指(或触控笔)对屏幕或者触控板操作。
常见的触屏事件如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m10wqabo-1575684662769)(images\图片1.png)]
1.3.2. 触摸事件对象(TouchEvent)
TouchEvent 是一类描述手指在触摸平面(触摸屏、触摸板等)的状态变化的事件。这类事件用于描述一个或多个触点,使开发者可以检测触点的移动,触点的增加和减少,等等
touchstart、touchmove、touchend 三个事件都会各自有事件对象。
触摸事件对象,重点我们看三个常见对象列表:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jgbhjg9C-1575684662772)(images\图片2.png)]
因为平时我们都是给元素注册触摸事件,所以重点记住 targetTocuhes
1.3.3.案例:移动端拖动元素
效果:
思路:
-
touchstart、touchmove、touchend可以实现拖动元素
-
但是拖动元素需要当前手指的坐标值 我们可以使用 targetTouches[0] 里面的pageX 和 pageY
-
移动端拖动的原理: 手指移动中,计算出手指移动的距离。然后用盒子原来的位置 + 手指移动的距离
-
手指移动的距离: 手指滑动中的位置 减去 手指刚开始触摸的位置
拖动元素三步曲:
(1) 触摸元素 touchstart: 获取手指初始坐标,同时获得盒子原来的位置
(2) 移动手指 touchmove: 计算手指的滑动距离,并且移动盒子
(3) 离开手指 touchend:
注意: 手指移动也会触发滚动屏幕所以这里要阻止默认的屏幕滚动 e.preventDefault();
代码:
<script>
// (1) 触摸元素 touchstart: 获取手指初始坐标,同时获得盒子原来的位置
// (2) 移动手指 touchmove: 计算手指的滑动距离,并且移动盒子
// (3) 离开手指 touchend:
var div = document.querySelector('div');
var startX = 0; //获取手指初始坐标
var startY = 0;
var x = 0; //获得盒子原来的位置
var y = 0;
div.addEventListener('touchstart', function(e) {
// 获取手指初始坐标
startX = e.targetTouches[0].pageX;
startY = e.targetTouches[0].pageY;
x = this.offsetLeft;
y = this.offsetTop;
});
div.addEventListener('touchmove', function(e) {
// 计算手指的移动距离: 手指移动之后的坐标减去手指初始的坐标
var moveX = e.targetTouches[0].pageX - startX;
var moveY = e.targetTouches[0].pageY - startY;
// 移动我们的盒子 盒子原来的位置 + 手指移动的距离
this.style.left = x + moveX + 'px';
this.style.top = y + moveY + 'px';
e.preventDefault(); // 阻止屏幕滚动的默认行为
//如果不阻止,当粉色盒子滚出界面之后会出现滚动条
});
</script>
7 - Web APIs
学习目标:
能够写出移动端触屏事件
能够写出常见的移动端特效
能够使用移动端开发插件开发移动端特效
能够使用移动端开发框架开发移动端特效
能够写出 sessionStorage 数据的存储以及获取
能够写出 localStorage 数据的存储以及获取
能够说出它们两者的区别
1.1. 触屏事件 ***
1.1.1. 触屏事件概述
移动端浏览器兼容性较好,我们不需要考虑以前 JS 的兼容性问题,可以放心的使用原生 JS 书写效果,但是移动端也有自己独特的地方。比如触屏事件 touch(也称触摸事件),Android和 IOS 都有。
touch 对象代表一个触摸点。触摸点可能是一根手指,也可能是一根触摸笔。触屏事件可响应用户手指(或触控笔)对屏幕或者触控板操作。
常见的触屏事件如下:
1.1.2. 触摸事件对象(TouchEvent)
TouchEvent 是一类描述手指在触摸平面(触摸屏、触摸板等)的状态变化的事件。这类事件用于描述一个或多个触点,使开发者可以检测触点的移动,触点的增加和减少,等等
touchstart、touchmove、touchend 三个事件都会各自有事件对象。
触摸事件对象,重点我们看三个常见对象列表:
因为平时我们都是给元素注册触摸事件,所以重点记住 targetTocuhes
1.1.3.案例:移动端拖动元素
效果:
思路:
-
touchstart、touchmove、touchend可以实现拖动元素
-
但是拖动元素需要当前手指的坐标值 我们可以使用 targetTouches[0] 里面的pageX 和 pageY
-
移动端拖动的原理: 手指移动中,计算出手指移动的距离。然后用盒子原来的位置 + 手指移动的距离
-
手指移动的距离: 手指滑动中的位置 减去 手指刚开始触摸的位置
拖动元素三步曲:
(1) 触摸元素 touchstart: 获取手指初始坐标,同时获得盒子原来的位置
(2) 移动手指 touchmove: 计算手指的滑动距离,并且移动盒子
(3) 离开手指 touchend:
注意: 手指移动也会触发滚动屏幕所以这里要阻止默认的屏幕滚动 e.preventDefault();
代码:
<script>
// (1) 触摸元素 touchstart: 获取手指初始坐标,同时获得盒子原来的位置
// (2) 移动手指 touchmove: 计算手指的滑动距离,并且移动盒子
// (3) 离开手指 touchend:
var div = document.querySelector('div');
var startX = 0; //获取手指初始坐标
var startY = 0;
var x = 0; //获得盒子原来的位置
var y = 0;
div.addEventListener('touchstart', function(e) {
// 获取手指初始坐标
startX = e.targetTouches[0].pageX;
startY = e.targetTouches[0].pageY;
x = this.offsetLeft;
y = this.offsetTop;
});
div.addEventListener('touchmove', function(e) {
// 计算手指的移动距离: 手指移动之后的坐标减去手指初始的坐标
var moveX = e.targetTouches[0].pageX - startX;
var moveY = e.targetTouches[0].pageY - startY;
// 移动我们的盒子 盒子原来的位置 + 手指移动的距离
this.style.left = x + moveX + 'px';
this.style.top = y + moveY + 'px';
e.preventDefault(); // 阻止屏幕滚动的默认行为
//如果不阻止,当粉色盒子滚出界面之后会出现滚动条
});
</script>
1.2. 移动端常见特效
1.2.1 案例: 移动轮播图
需求:
移动端轮播图功能和基本PC端一致
- 可以自动播放图片
- 手指可以拖动播放轮播图
案例分析:
-
自动播放功能
-
开启定时器
-
移动端移动,可以使用translate 移动
-
想要图片优雅的移动,请添加过渡效果[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
-
自动播放功能-无缝滚动
-
注意,我们判断条件是要等到图片滚动完毕再去判断,就是过渡完成后判断
-
此时需要添加检测过渡完成事件 transitionend
-
判断条件:如果索引号等于 3 说明走到最后一张图片,此时 索引号要复原为 0
-
此时图片,去掉过渡效果,然后移动
-
如果索引号小于0, 说明是倒着走, 索引号等于2
-
此时图片,去掉过渡效果,然后移动
1.2.2 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');
注意:以上方法里面,所有类名都不带点
1.2.3 轮播图优化
-
小圆点跟随变化效果
-
把ol里面li带有current类名的选出来去掉类名 remove
-
让当前索引号的小li 加上 current add
-
但是,是等着过渡结束之后变化,所以这个写到 transitionend 事件里面
-
手指滑动轮播图
-
本质就是ul跟随手指移动,简单说就是移动端拖动元素
-
触摸元素touchstart: 获取手指初始坐标
-
移动手指touchmove: 计算手指的滑动距离,并且移动盒子
-
离开手指touchend: 根据滑动的距离分不同的情况
-
如果移动距离小于某个像素 就回弹原来位置
-
如果移动距离大于某个像素就上一张下一张滑动。
-
滑动也分为左滑动和右滑动判断的标准是 移动距离正负 如果是负值就是左滑 反之右滑
-
如果是左滑就播放下一张 (index++)
-
如果是右滑就播放上一张 (index–)
1.2.4 案例:返回顶部
需求:
当页面滚动某个地方,就显示,否则隐藏
点击可以返回顶部
案例分析:
- 滚动某个地方显示
- 事件:scroll页面滚动事件
- 如果被卷去的头部(window.pageYOffset )大于某个数值
- 点击,window.scroll(0,0) 返回顶部
1.3. click 延时解决方案
移动端 click 事件会有 300ms 的延时,原因是移动端屏幕双击会缩放(double tap to zoom) 页面。
解决方案:
1. 禁用缩放: 浏览器禁用默认的双击缩放行为并且去掉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 延迟。
1.4. 移动端常用开发插件
1.4.1. 什么是插件
移动端要求的是快速开发,所以我们经常会借助于一些插件来帮我完成操作,那么什么是插件呢?
JS 插件是 js 文件(js工具),它遵循一定规范编写,方便程序展示效果,拥有特定功能且方便调用。如轮播图和瀑布流插件。
特点:它一般是为了解决某个问题而专门存在,其功能单一,并且比较小。
我们以前写的animate.js 也算一个最简单的插件
fastclick 插件解决 300ms 延迟。
GitHub官网地址: https://github.com/ftlabs/fastclick
1.5.2. 插件的使用
-
引入 js 插件文件。
-
按照规定语法使用。
-
fastclick 插件解决 300ms 延迟。
-
GitHub官网地址: https://github.com/ftlabs/fastclick‘
-
代码如下:
if ('addEventListener' in document) { document.addEventListener('DOMContentLoaded', function() { FastClick.attach(document.body); }, false); }
1.5.3. Swiper 插件的使用
中文官网地址: https://www.swiper.com.cn/
- 引入插件相关文件。
- 按照规定语法使用 (css,html,js)
1.5.4. 其他移动端常见插件
lsuperslide: http://www.superslide2.com/
l iscroll: https://github.com/cubiq/iscroll
1.5.5. 插件的使用总结
1.确认插件实现的功能
2.去官网查看使用说明
3.下载插件
4.打开demo实例文件,查看需要引入的相关文件,并且引入
5.复制demo实例文件中的结构html,样式css以及js代码
1.5.6. 移动端视频插件 zy.media.js
H5 给我们提供了 video 标签,但是浏览器的支持情况不同。
不同的视频格式文件,我们可以通过source解决。
但是外观样式,还有暂停,播放,全屏等功能我们只能自己写代码解决。
这个时候我们可以使用插件方式来制作。
我们可以通过 JS 修改元素的大小、颜色、位置等样式。
1.6. 移动端常用开发框架
1.6.1. 移动端视频插件 zy.media.js
框架,顾名思义就是一套架构,它会基于自身的特点向用户提供一套较为完整的解决方案。框架的控制权在框架本身,使用者要按照框架所规定的某种规范进行开发。
插件一般是为了解决某个问题而专门存在,其功能单一,并且比较小。
前端常用的框架有 Bootstrap、Vue、Angular、React 等。既能开发PC端,也能开发移动端
前端常用的移动端插件有 swiper、superslide、iscroll等。
框架: 大而全,一整套解决方案
插件: 小而专一,某个功能的解决方案
1.6.2. Bootstrap
Bootstrap 是一个简洁、直观、强悍的前端开发框架,它让 web 开发更迅速、简单。
它能开发PC端,也能开发移动端
Bootstrap JS插件使用步骤:
1.引入相关js 文件
2.复制HTML 结构
3.修改对应样式
4.修改相应JS 参数
1.7. 本地存储 (本机浏览器存储) ***
随着互联网的快速发展,基于网页的应用越来越普遍,同时也变的越来越复杂,为了满足各种各样的需求,会经常性在本地存储大量的数据,HTML5规范提出了相关解决方案。
1.7.1.本地存储特性
1、数据存储在用户浏览器中
2、设置、读取方便、甚至页面刷新不丢失数据
3、容量较大,sessionStorage约5M、localStorage约20M
4、只能存储字符串,可以将对象JSON.stringify() 编码后存储
1.7.2.window.sessionStorage (session:会话,用户打开网站,关闭网站,这就是一次会话)
1、生命周期为关闭浏览器窗口
2、在同一个窗口(页面)下数据可以共享
3、以键值对的形式存储使用
存储数据:
sessionStorage.setItem(key, value)
获取数据:
sessionStorage.getItem(key)
删除数据:
sessionStorage.removeItem(key)
清空数据:(所有都清除掉)
sessionStorage.clear()
1.7.3.window.localStorage
1、声明周期永久生效,除非手动删除 否则关闭页面也会存在
2、可以多窗口(页面)共享(同一浏览器可以共享)
- 以键值对的形式存储使用
存储数据:
localStorage.setItem(key, value)
获取数据:
localStorage.getItem(key)
删除数据:
localStorage.removeItem(key)
清空数据:(所有都清除掉)
localStorage.clear()
1.7.4.案例:记住用户名
如果勾选记住用户名, 下次用户打开浏览器,就在文本框里面自动显示上次登录的用户名
案例分析
-
把数据存起来,用到本地存储
-
关闭页面,也可以显示用户名,所以用到localStorage
-
打开页面,先判断是否有这个用户名,如果有,就在表单里面显示用户名,并且勾选复选框
-
当复选框发生改变的时候change事件
-
如果勾选,就存储,否则就移除
-
代码如下: