Web APIs:具有操作网页属性和方法的对象
最主要的三大对象:window对象、Document对象、Event对象,这些对象中有很多操作网页的方法和属性!
黑马程序员Web APIs

复习:

JS_summarize

splice() 方法用于添加或删除数组中的元素。

注意:这种方法会改变原始数组。

  1. 删除数组:

splice(起始位置, 删除的个数)

比如:

let arr = ['red', 'green', 'blue']
arr.splice(1,1) // 删除green元素
console.log(arr) // ['red, 'blue']
  1. 添加元素

splice(起始位置,删除个数,添加数组元素)

let arr = ['red', 'green', 'blue']
//arr.splice(1, 0, 'pink') // 在索引号是1的位置添加 pink
//console.log(arr) // ['red', 'pink', 'green', 'blue']
arr.splice(1, 0, 'pink', 'hotpink') // 在索引号是1的位置添加 pink hotpink
console.log(arr) // ['red', 'pink', 'hotpink', 'green', 'blue']

进入Web APIs学习,需要更新的观念

  1. 声明变量,能用const,尽量用const!很多都是不变的,不变的语义化和功能都更好!
  2. 当发现这个变量需要改变时,才更改为let
  3. 简单数据根据情况,引用数据可以直接使用,注意改变时通过地址操作,完全没问题,如果通过新建值进行操作,就会报错!

image-20230531141205178

Web APIs作用

APIs:提前封装好的属性和方法,也被称为接口(API)——应用程序编程接口(Application Programming Interface),简单来说就是一些预定义的属性和函数,最大的好处是,调用这些接口,不需要关系内部的实现细节!

比如之前学过的Array.length(),获取数组内元素的个数,Array.pop(),删除数组最后一个值,还有数学API,math.random()生成随机数,JS的关键就是灵活使用它提供的各种APIs,有些是内置,有些是第三方提供,总之这些APIs都已经实现了特定的功能,我们要做的只是去确认结果是不是想要的,能满足需求,那么拿来用便是!第三方库通常要在头部引用库文件,就是一些.js文件!

之前只是学习了JS语法,从来没有实现过操作网页元素,而Web APIs就提供了很多功能、方法,来操控网页元素,操控浏览器!和HTML和CSS一样,很多写好的功能,只是调用就行!

其中为什么可以把网页内容当作对象处理呢,就涉及到浏览器了,浏览器先把网页内容解析成一个对象,然后再调用JS文件,就可以通过修改对象属性的方式,最后再渲染到页面,改变页面展示的内容

Web APIs有哪些

最重要的就三个对象,Window对象、Document对象、Event对象,由浏览器提供!每一个窗口对应一个Window对象(窗口对象),其中内容区,也就是HTML文档展示的区域对应一个document对象(文档对象),每发生一个事件就自动产生一个event对象(事件对象)!

所有与浏览器相关的API几乎都放在了window对象内,document也属于window,所有网页元素操作相关的API都放在了document对象内,给网页元素绑定事件后,event就记录着每个事件的详细信息

DOM(文档对象模型)一套理论模型

DOM将网页上的元素与Javascript程序联系起来,浏览器将一个HTML文档解析成一个类似树的结构,树上有一些节点元素、节点属性、节点文本等,如果把每一个节点都生成一个对象,这样JS就能操作和访问这些元素了!

浏览器生成网页流程

DOM树——一套理论、原型(一个逻辑结构表示)

HTML文档树状结构直观的表现出来,称之为文档树,或者DOM树!

作用:把元素间关系描述出来,便于操作,即文档树能直观的反映标签与标签之间的关系!像族谱!

DOM对象——根据理论生成的对象

通过浏览器,一方面,把网页文件生成了DOM对象,更方便去操作网页中的元素,另一方法,给JS提供了内置方法,选中和修改这些对象的方法!DOM对象就是专门为JS准备的,包含方法和属性!所有DOM对象都放到了document对象中!document对象是最大的DOM对象!

核心一句话,DOM是JS中一部分,通过DOM对象中的属性和方法,用来操作网页内容!

DOM对象拥有很多一套操作网页元素的方法,是按照DOM树的形式,把每个元素都变成了对象!

DOM对象是按照这套理论,把HTML文件解析成对象,封装了很多功能和属性!

Web APIs - 第1天——对元素的属性操作

Web APIs 第一天

了解 DOM 的结构并掌握其基本的操作,体验 DOM 的在开发中的作用

  • 知道 ECMAScript 与 JavaScript 的关系
  • 了解 DOM 的相关概念及DOM 的本质是一个对象
  • 掌握查找节点的基本方法
  • 掌握节点属性和文本的操作
  • 能够使用间歇函数创建定时任务

介绍

知道 ECMAScript 与 JavaScript 的关系,Web APIs 是浏览器扩展的功能。

也就是说,没有浏览器,JS啥也不是;JS需要浏览器解析和执行,浏览器还把HTML、CSS网页文件生成DOM对象,浏览器就像底层的自然界生物系统,把太阳光转化为其它能量,才能被操作!

初级阶段

严格意义上讲,我们在 JavaScript 阶段学习的知识绝大部分属于 ECMAScript 的知识体系,ECMAScript 简称 ES 它提供了一套语言标准规范,如变量、数据类型、表达式、语句、函数等语法规则都是由 ECMAScript 规定的。

应用场景

浏览器将 ECMAScript 大部分的规范加以实现,并且在此基础上又扩展一些实用的功能,这些被扩展出来的内容我们称为 Web APIs。

guide

ECMAScript 运行在浏览器中然后再结合 Web APIs 才是真正的 JavaScript,Web APIs 的核心是 DOM 和 BOM。

扩展阅读

ECMAScript 规范在不断的更新中,存在多个不同的版本,早期的版本号采用数字顺序编号如 ECMAScript3、ECMAScript5,后来由于更新速度较快便采用年份做为版本号,如 ECMAScript2017、ECMAScript2018 这种格式,ECMAScript6 是 2015 年发布的,常叫做 EMCAScript2015。

关于 JavaScript 历史的扩展阅读

核心任务

知道 DOM 相关的概念,建立对 DOM 的初步认识,学习 DOM 的基本操作,体会 DOM 的作用

DOM对象是浏览器提供的一套专门用来操作网页内容的功能!

  1. 有很多已经被设计好了的函数功能,用来操作网页内容,
  2. 浏览器把HTML和CSS文件解析成对象形式,也就是DOM对象
  3. 浏览器内置JS解析器,编译的过程,实现对刚生成的DOM对象进行操作,给元素对象附加功能!生成新的DOM对象
  4. 加载,并渲染DOM对象的内容,生成可以观看的网页,并时时检测DOM对象中属性的值是否变化

原理

DOM(Document Object Model)是将整个 HTML 文档的每一个标签元素视为一个对象,这个对象下包含了许多的属性和方法,通过操作这些属性或者调用这些方法实现对 HTML 的动态更新,为实现网页特效以及用户交互提供技术支撑。

简言之 DOM 是用来动态修改 HTML 的,其目的是开发网页特效及用户交互。

观察一个小例子:

demo

上述的例子中当用户分分别点击【开始】或【结束】按钮后,通过右侧调试窗口可以观察到 html 标签的内容在不断的发生改变,这便是通过 DOM 实现的。

关于DOM的概念

DOM 树

将HTML文档以树状结构直观的表现出来,称之为文档树,或者DOM树!

作用

文档树能直观的反映标签与标签之间的关系!像族谱!把元素间关系描述出来,更加便于操作!

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>标题</title>
</head>
<body>
文本
<a href="">链接名</a>
<div id="" class="">文本</div>
</body>
</html>

如下图所示,将 HTML 文档以树状结构直观的表现出来,我们称之为文档树或 DOM 树,文档树直观的体现了标签与标签之间的关系。

web-api

DOM对象

浏览器根据HTML标签,生成的JS对象(DOM对象),这样就可以把内容当作对象进行处理!

DOM 节点

核心思想:把网页内容当作对象来处理!

节点是文档树的组成部分,每一个节点都是一个 DOM 对象,主要分为元素节点、属性节点、文本节点等。

  1. 【元素节点】其实就是 HTML 标签,如上图中 headdivbody 等都属于元素节点。
  2. 【属性节点】是指 HTML 标签中的属性,如上图中 a 标签的 href 属性、div 标签的 class 属性。
  3. 【文本节点】是指 HTML 标签的文字内容,如 title 标签中的文字。
  4. 【根节点】特指 html 标签。
  5. 其它…

document对象

document 是学习 DOM 的核心,也是DOM对象中最大的对象,其中子代里面,每个元素也是对象,按照结构,是包含在内的小对象!网页所有内容,都在document对象里面!

先通过document方法,去获取小的元素对象,通过修改元素对象的属性,来改变页面!

<script>
// document 是内置的对象
// console.log(typeof document);

// 1. 通过 document 获取根节点
console.log(document.documentElement); // 对应 html 标签

// 2. 通过 document 节取 body 节点
console.log(document.body); // 对应 body 标签

// 3. 通过 document.write 方法向网页输出内容
document.write('Hello World!');
</script>

上述列举了 document 对象的部分属性和方法,我们先对 document 有一个整体的认识。

获取DOM对象——查找元素对象

document对象,内置了很多方法,用于对元素对象的增删查改!

查找元素对象核心方式是,根据CSS选择器来获取元素;其它的获取方式,简单了解即可!

CSS选择器

querySelector

满足条件的第一个元素对象!常见操作是:建立常量,保存其地址,通过地址来访问和修改这个对象

const 标识符 = document.querySelector('CSS选择器')
  • 参数:一个或多个有效的CSS选择器字符串!总之就是CSS怎么写,这个就怎么写,再加上引号!

  • 返回值:CSS选择器匹配的第一个元素,一个HTMLElement对象!没有匹配到则返回空!

  • 目的:获取到的对象可以直接进行修改,就跟操作对象属性和方法一样!

例如:

const li = document.querySelector('ul li:first-child')

querySelectorAll

满足条件的元素集合 返回伪数组,一个充满地址的一个数组,每个地址又是一个对象,即数值对象!基本和querySelector一致,但获取到的对象不能直接修改,需要通过for循环遍历数组,进行修改!

  • 返回值是伪数组:

    1. 有长度和索引

    2. 没有增、删、改等数组方法!

  • 想要得到里面的对象,需要遍历(for)的方式获得

了解其他选择器

// 根据id获取一个元素
document.getElementById('')

// 根据标签,获取所有元素
document.getElementsByTagName('')

// 根据类名,获取所有元素
document.getElementByClassName('')

案例:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DOM - 查找节点</title>
</head>
<body>
<h3>查找元素类型节点</h3>
<p>从整个 DOM 树中查找 DOM 节点是学习 DOM 的第一个步骤。</p>
<ul>
<li>元素</li>
<li>元素</li>
<li>元素</li>
<li>元素</li>
</ul>
<script>
const p = document.querySelector('p') // 获取第一个p元素
const lis = document.querySelectorAll('li') // 获取第一个p元素
</script>
</body>
</html>

总结:

  • document.getElementById 专门获取元素类型节点,根据标签的 id 属性查找
  • 任意 DOM 对象都包含 nodeType 属性,用来检检测节点类型

操作元素对象

操作元素内容

通过修改 DOM 的文本内容,动态改变网页的内容。

  1. innerText 将文本内容添加/更新到任意标签位置,文本中包含的标签不会被解析。
<script>
// innerText 将文本内容添加/更新到任意标签位置
const intro = document.querySelector('.intro')
// intro.innerText = '嗨~ 我叫李雷!'
// intro.innerText = '<h4>嗨~ 我叫李雷!</h4>'
</script>
  1. innerHTML 将文本内容添加/更新到任意标签位置,文本中包含的标签会被解析。
<script>
// innerHTML 将文本内容添加/更新到任意标签位置
const intro = document.querySelector('.intro')
intro.innerHTML = '嗨~ 我叫韩梅梅!'
intro.innerHTML = '<h4>嗨~ 我叫韩梅梅!</h4>'
</script>

总结:如果文本内容中包含 html 标签时推荐使用 innerHTML,否则建议使用 innerText 属性。

操作元素属性——元素属性,其中分常用、样式

有3种方式可以实现对属性的修改:

常用属性修改

  1. 直接能过属性名修改,最简洁的语法

作用:可以给图片图片标签换图片!

对象.属性 = 值

案例:

<script>
// 1. 获取 img 对应的 DOM 元素
const pic = document.querySelector('.pic')
// 2. 修改属性
pic.src = './images/lion.webp'
pic.width = 400;
pic.alt = '图片不见了...'
</script>

控制样式属性——核心

通过JS设置/修改标签元素的样式属性!

分三种方法、通过修改样式本身style、通过写好的样式(添加类名)、通过classList

应用【修改样式】

通过修改行内样式 style 属性,实现对样式的动态修改。

对象.style.样式属性 = 值

通过元素节点获得的 style 属性本身的数据类型也是对象,如 box.style.colorbox.style.width 分别用来获取元素节点 CSS 样式的 colorwidth 的值。

某些连词,通过小驼峰名来修改样式!

body是唯一,可以不获取,直接使用!

document.body.style.backgroundImage = `./image.${random}.png`
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>练习 - 修改样式</title>
</head>
<body>
<div class="box">随便一些文本内容</div>
<script>
// 获取 DOM 节点
const box = document.querySelector('.intro')
box.style.color = 'red'
box.style.width = '300px'
// css 属性的 - 连接符与 JavaScript 的 减运算符
// 冲突,所以要改成驼峰法
box.style.backgroundColor = 'pink'
</script>
</body>
</html>

任何标签都有 style 属性,通过 style 属性可以动态更改网页标签的样式,如要遇到 css 属性中包含字符 - 时,要将 - 去掉并将其后面的字母改成大写,如 background-color 要写成 box.style.backgroundColor

  1. 操作类名className属性——操作CSS

如果修改的样式比较多,直接通过style属性修改比较繁琐,我们可以通过借助于css类名的形式。

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>练习 - 修改样式</title>
<style>
.pink {
background: pink;
color: hotpink;
}
</style>
</head>
<body>
<div class="box">随便一些文本内容</div>
<script>
// 获取 DOM 节点
const box = document.querySelector('.intro')
box.className = 'pink'
</script>
</body>
</html>

注意:

由于class是关键字, 所以使用className去代替

className是使用新值换旧值, 如果需要添加一个类,需要保留之前的类名

通过 classList 操作类控制CSS

元素.className = ''
  1. 操作类名classList对象——终极方案!

为了解决className 容易覆盖以前的类名,我们可以通过classList方式追加和删除类名

// 在后面追加类名,传入字符串,不用加点!
元素对象.classList.add('')

// 删除类
元素对象.classList.remove('')

// 切换类,更改原类名,存在就删除,不存在就追加(类似取反)
元素对象.classList.toggle('')

image-20230531191146539

<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
div {
width: 200px;
height: 200px;
background-color: pink;
}

.active {
width: 300px;
height: 300px;
background-color: hotpink;
margin-left: 100px;
}
</style>
</head>

<body>

<div class="one"></div>
<script>
// 1.获取元素
// let box = document.querySelector('css选择器')
let box = document.querySelector('div')
// add是个方法 添加 追加
// box.classList.add('active')
// remove() 移除 类
// box.classList.remove('one')
// 切换类
box.classList.toggle('one')
</script>
</body>

</html>

操作表单元素的常用属性

表单很多情况,也需要修改属性,比如点击眼睛,可以看到密码,本质是把表单类型转换为文本框

正常的有属性有取值的跟其他的标签属性没有任何区别

获取:

DOM对象.属性名

设置:

DOM对象.属性名= 新值

input.value = '小米手机'
input.type = 'password'
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>

</head>

<body>
<input type="text" value="请输入">
<button disabled>按钮</button>
<input type="checkbox" name="" id="" class="agree">
<script>
// 1. 获取元素
let input = document.querySelector('input')
// 2. 取值或者设置值 得到input里面的值可以用 value
// console.log(input.value)
input.value = '小米手机'
input.type = 'password'

// 2. 启用按钮
let btn = document.querySelector('button')
// disabled 不可用 = false 这样可以让按钮启用
btn.disabled = false
// 3. 勾选复选框
let checkbox = document.querySelector('.agree')
checkbox.checked = false
</script>
</body>

</html>

操作表单元素的样式属性

表单属性中添加就有效果,移除就没有效果,在JS的表单元素对象中,一律用布尔值表示,也就是说,属性值为ture,代表有效果,反之没有效果

// 存在隐式转换
元素对象.checked/disabled = true/false

自定义属性

标准属性: 标签天生自带的属性 比如class id title等, 可以直接使用点语法操作比如: disabled、checked、selected

什么是自定义属性:

在html5中推出来了专门的data-自定义属性

添加:

在标签上一律以data-开头,规范写法
获取:

在DOM对象上一律以dataset对象方式获取,获取的是个集合对象,再添加属性名,就能获取值

元素对象.dataset.id/spm
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>

</head>

<body>
<div data-id="1"> 自定义属性 </div>
<script>
// 1. 获取元素
let div = document.querySelector('div')
// 2. 获取自定义属性值
console.log(div.dataset.id)

</script>
</body>

</html>

间歇函数

知道间歇函数的作用,利用间歇函数创建定时任务。

定时器函数,可以实现,间隔指定的时长,重复执行代码,基本步骤分成开启定时和关闭定时!

  1. 开启定时器
// 作用:每间隔一段时间,调用这个函数,单位是ms
setInterval(函数,间隔时间)

setInterval 是 JavaScript 中内置的函数,它的作用是间隔固定的时间自动重复执行另一个函数,也叫定时器函数。

使用方法:

setInterval(匿名函数,间隔时间)

setInterval(函数名,间隔时间)

每个定时器都有编号,返回一个ID值,数字型,通过这个来关闭指定的定时器,每个定时器独一无二

// 一会儿开和关,所以用let重新赋值操作
let n = setInterval(函数名,间隔时间)

// 关闭定时器
clearInterval(n)
<script>
// 1. 定义一个普通函数
function repeat() {
console.log('不知疲倦的执行下去....')
}

// 2. 使用 setInterval 调用 repeat 函数
// 间隔 1000 毫秒,重复调用 repeat
setInterval(repeat, 1000)
</script>

倒计时读用户协议

const btn = document.querySelector('button')
btn.disabled = true
btn.innerHTML = `我已经阅读用户协议(5)`
let i = 5
let n =setInterval(function (){
i--
btn.innerHTML = `我已经阅读用户协议(${i})`
if(i === 0){
clearInterval(n)
btn.innerHTML = `同意`
btn.disabled = false
}
},1000)

轮播图

// 获取元素对象
const img = document.querySelector('.slider-wrapper img')
const p = document.querySelector('.slider-footer p')
const divFooter = document.querySelector('.slider-footer')
const lis = document.querySelectorAll(`ul li`)
const liNth = document.querySelector(`ul li:nth-child(${rdNum})`)

// 默认封面
lis[0].classList.add('active')
img.src = sliderData[0].url
p.innerHTML = sliderData[0].title
divFooter.style.backgroundColor = sliderData[0].color

let i = 1
// 更改设置间隔定时器,修改元素对象属性
setInterval(function (){
// 修改元素对象的-默认属性
img.src = sliderData[i].url
// 修改元素对象-内容
p.innerHTML = sliderData[i].title
// 样式
divFooter.style.backgroundColor = sliderData[i].color
// 清除样式
for(let i = 0; i < lis.length; i++){
lis[i].classList.remove('active')
}

lis[i].classList.add('active')
i++
if(i === sliderData.length){
i = 0
}
},2000)

小圆点清除样式和添加样式:

// 清除样式
document.querySelector('.slider-indicator .active').classList.remove('active')

// 添加样式
// document.querySelector(`.slider-indicator li:nth-child(${i+1})`).classList.add('active')

Web APIs - 第2天——事件监听

Web APIs 第二天

学会通过为DOM注册事件来实现可交互的网页特效。

  • 能够判断函数运行的环境并确字 this 所指代的对象
  • 理解事件的作用,知道应用事件的 3 个步骤

学习会为 DOM 注册事件,实现简单可交互的网页特交。

交互功能原理

交互功能的核心原理是事件驱动模型,通过页面中的元素设置事件,然后浏览器检测事件是否触发,相当于是设置一个陷阱或者夹子,浏览器作为被动监听,一直在捕捉行为,当用户或者浏览器与网站中设置的陷阱有交互时,网页就会响应的生成内容,变化页面等,使页面具有一种交互性,动态性!

JS的核心任务就是弄清除用户或系统,怎么与网站实现交互的!

驱动模型三要素

事件源、事件、事件驱动程序

比如,我用手去按开关,灯亮了。

  • 事件源是:开关
  • 事件是:按开关。
  • 事件驱动程序是:灯的亮与灭。

事件源,就是引发后续事件的物体,比如网页中的元素,事件是规定好的一系列行为,比如鼠标单击,事件驱动程序就是我们学习的重点,也就是网页的动态改变,包括改变网页中元素的内容、属性、样式等!

事件

事件是编程语言中的术语,它是用来描述程序的行为或状态的,一旦行为或状态发生改变,便立即调用一个函数。

什么是事件

事件是在编程时,系统内发生的动作、发生的事情!包括浏览器加载、用户输入!

例如:用户使用【鼠标点击】网页中的一个按钮、用户使用【鼠标拖拽】网页中的一张图片

事件监听

让浏览器检测是否有事件产生,一旦检测到事件,就立即执行设置的响应函数——设置事件与响应函数产生联系,就称为绑定事件 !或者称为设置事件监听!或者称注册事件!

结合 DOM 使用事件时,需要为 DOM 对象添加事件监听,等待事件发生(触发)时,便立即调用一个函数。

添加事件监听语法

使用元素对象的addEventListener 方法

addEventListener 是 DOM 对象专门用来添加事件监听的方法,它的两个参数分别为【事件类型】和【事件回调】。

元素对象.addEventListener('事件类型',响应函数)

事件监听三要素

事件源:监听哪个DOM元素的哪个事件

事件类型:什么方式触发,(触发:浏览器在事件源上检测到事件类型发生了)

事件调用的函数:做什么事

注意

事件类型要加引号,函数是触发一次执行一次

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>事件监听</title>
</head>
<body>
<h3>事件监听</h3>
<p id="text">为 DOM 元素添加事件监听,等待事件发生,便立即执行一个函数。</p>
<button id="btn">点击改变文字颜色</button>
<script>
// 1. 获取 button 对应的 DOM 对象
const btn = document.querySelector('#btn')

// 2. 添加事件监听
btn.addEventListener('click', function () {
console.log('等待事件被触发...')
// 改变 p 标签的文字颜色
let text = document.getElementById('text')
text.style.color = 'red'
})

// 3. 只要用户点击了按钮,事件便触发了!!!
</script>
</body>
</html>

完成事件监听分成3个步骤:

  1. 获取 DOM 元素
  2. 通过 addEventListener 方法为 DOM 节点添加事件监听
  3. 等待事件触发,如用户点击了某个按钮时便会触发 click 事件类型
  4. 事件触发后,相对应的回调函数会被执行

大白话描述:所谓的事件无非就是找个机会(事件触发)调用一个函数(回调函数)。

案例:抽取随机问题

分析问题:
1.目标是一个页面中大盒子包含三个小盒子,一个盒子显示问题,两个盒子控制显示的内容
2.显示问题的盒子,默认是文字‘开始选择’
3.开始盒子,添加点击事件,开启定时器
4.暂停盒子,添加点击事件,关闭定时器

注意:事件里的代码,执行一遍后,垃圾回收机制,当函数执行结束后,函数里面的变量会被释放!

全局变量和局部变量!全局变量,在函数内部可以再次声明,不影响!

事件类型

click 译成中文是【点击】的意思,它的含义是监听(等着)用户鼠标的单击操作,除了【单击】还有【双击】dblclick

<script>
// 双击事件类型
btn.addEventListener('dblclick', function () {
console.log('等待事件被触发...');
// 改变 p 标签的文字颜色
const text = document.querySelector('.text')
text.style.color = 'red'
})

// 只要用户双击击了按钮,事件便触发了!!!
</script>

结论:【事件类型】决定了事件被触发的方式,如 click 代表鼠标单击,dblclick 代表鼠标双击。

事件处理程序

addEventListener 的第2个参数是函数,这个函数会在事件被触发时立即被调用,在这个函数中可以编写任意逻辑的代码,如改变 DOM 文本颜色、文本内容等。

<script>
// 双击事件类型
btn.addEventListener('dblclick', function () {
console.log('等待事件被触发...')

const text = document.querySelector('.text')
// 改变 p 标签的文字颜色
text.style.color = 'red'
// 改变 p 标签的文本内容
text.style.fontSize = '20px'
})
</script>

结论:【事件处理程序】决定了事件触发后应该执行的逻辑。

事件监听版本

DOM L0

事件源.on事件 = function () {}

DOM L2

事件源.addEventListener(事件, 事件处理函数)

区别:

on方式会被覆盖(再次绑定同一事件,后覆盖前),addEventListener可以绑定多次,依次响应!

on方式只有冒泡,没有捕获!

事件类型

将众多的事件类型分类可分为:鼠标事件、键盘事件、表单事件、焦点事件等,我们逐一展开学习。

image-20230601140552310

鼠标事件

鼠标事件是指跟鼠标操作相关的事件,如单击、双击、移动等。

  1. `mouseenter 监听鼠标是否移入 DOM 元素
<body>
<h3>鼠标事件</h3>
<p>监听与鼠标相关的操作</p>
<hr>
<div class="box"></div>
<script>
// 需要事件监听的 DOM 元素
const box = document.querySelector('.box');

// 监听鼠标是移入当前 DOM 元素
box.addEventListener('mouseenter', function () {
// 修改文本内容
this.innerText = '鼠标移入了...';
// 修改光标的风格
this.style.cursor = 'move';
})
</script>
</body>
  1. `mouseleave 监听鼠标是否移出 DOM 元素

案例:小米搜索框

<body>
<h3>鼠标事件</h3>
<p>监听与鼠标相关的操作</p>
<hr>
<div class="box"></div>
<script>
// 需要事件监听的 DOM 元素
const box = document.querySelector('.box');

// 监听鼠标是移出当前 DOM 元素
box.addEventListener('mouseleave', function () {
// 修改文本内容
this.innerText = '鼠标移出了...';
})
</script>
</body>

键盘事件

keydown 键盘按下触发
keyup 键盘抬起触发

元素对象.addEventListener('keydown/keyup',function (){})

注意:按一次按键,触发两个事件!

焦点事件

focus 获得焦点

blur 失去焦点

元素对象.addEventListener('focus/blur',function (){})

主要是对表单绑定的事件!

案例:完善轮播图

分析
1.目标是默认情况是自动轮播,当鼠标移入按钮,暂停轮播,并点击时,切换到上一张或下一张
2.数据是数组对象!
3.在鼠标移入的事件中,设置函数,实现开始定时,移入关闭定时
4.在鼠标点击的事件中,上一页获取当前页,使其--,下一页同理!
5.各个盒子里,调用相应的数组对象数据——制作成一个函数!

方法二:

1.设置全局变量i
2.设置渲染函数
3.上一页,i--,当i<0,i=data.length-1,再调用渲染函数
4.下一页,i++,当i>=data.length,i=0,再调用渲染函数
5.自动播放,定时器中,调用下一页的点击事件!next.click(),别忘了加小括号!
6.在大盒子里面,添加鼠标移入事件,关闭定时器
7.在大盒子里,添加鼠标移除事件,关闭定时器,开启新的一个定时器,并把编号赋值

表单事件

文本框输入事件

input

元素对象.addEventListener('input',function (){})

注意:按下按键,输入值,触发一个事件

案例:评论字数统计

分析
1.文本域、上传按钮、下方显示可输入的总数字,当前输入数字
2.显示的,当每次输入事件发生,重新检测input.value.length
美化
1.扩展输入框,CSS样式加入修改和动画
<style>
input {
width: 200px;
transition: all .3s;
}
input:focus {
width: 300px;
}
</style>

<script>
tx.addEventListener('focus',function () {
total.style.opacity = 1
})
tx.addEventListener('blur',function () {
total.style.opacity = 0
})
// 添加输入事件
tx.addEventListener('input',function () {
total.innerHTML = `${tx.value.length}/200字`
})
</script>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>表单事件</title>
</head>
<body>
<form action="">
<!-- 回顾: input的type类型值有checkbox, text, radio, file, image, submit, button, number, password, email, reset, range, prgress, color, time, date等等-->
<label for="myInput">账号:</label>
<input type="text" id="myInput">
<input type="radio" name="sex">
<input type="radio" name="sex">
<input type="reset">
<input type="submit"> <button>提交</button>
<input type="color" id="color">
</form>

<script>
var textInput = document.getElementById("myInput")
var radios = document.getElementsByName("sex")
// 1, input输入事件 关注输入过程
textInput.oninput = function(){
console.log("input", this.value)
}
// 2, change更新事件 关注输入结果
textInput.onchange = function(){
console.log("change", this.value)
}
// 3, focus 获取焦点事件
textInput.onfocus = function(){
console.log("focus", this.value)
}
// 4, blur 失去焦点事件
textInput.onblur= function(){
console.log("blur", this.value)
}
// 5, select 选择事件
textInput.onselect= function(e){
console.log("select", e.select) // ?
}
var form = document.querySelector("form")
// 6, reset 重置事件 事件目标是form
form.onreset = function(e){
// alert("即将重置")
// var msg = prompt("请说明你要重置的原因?")
var bool = confirm("确定要重置表单吗?")
if(!bool){
e.preventDefault()
}
}

// 7, submit 表单提交事件 事件目标是form
form.onsubmit = function(e){
// 阻止表单自动提交
e.preventDefault()
}

// 单选框, 可以绑定click, input, change事件, 建议使用change
radios[0].onchange = function(){
// radio的value值不管有没有选中, 都是on
console.log(this.value, radios[1].value)
// 单选或多选框通过checked属性获取选中状态
console.log(this.checked, radios[1].checked)
}

// 总结: 常用的表单事件有
// input, change, focus, blur, reset, submit, select


color.onchange= function(){
console.log(this.value)
}

</script>
</body>
</html>

总结: 常用的表单事件有
input, change, focus, blur, reset, submit, select

元素事件绑定后调用

当元素绑定了事件后,可以通过对象.事件(),来调用该事件,div.click()

事件对象

任意事件类型被触发时,与事件相关的信息,会被以对象的形式记录下来,称为事件对象。

使用场景:比如用户输入的是哪一个键、鼠标位置,哪一个元素触发了事件!

<body>
<h3>事件对象</h3>
<p>任意事件类型被触发时与事件相关的信息会被以对象的形式记录下来,我们称这个对象为事件对象。</p>
<hr>
<div class="box"></div>
<script>
// 获取 .box 元素
const box = document.querySelector('.box')

// 添加事件监听
box.addEventListener('click', function (e) {
console.log('任意事件类型被触发后,相关信息会以对象形式被记录下来...');

// 事件回调函数的第1个参数即所谓的事件对象
console.log(e)
})
</script>
</body>

获取事件对象

事件绑定的回调函数中,【第1个参数】就是事件对象,通常习惯性的将这个对数命名为 eventevev

元素对象.addEventListener('input',function (e){})

常用属性

接下来简单看一下事件对象中包含了哪些有用的信息:

image-20230601165208809

  1. e.type 当前事件的类型
  2. ev.clientX/Y 光标相对浏览器窗口的位置
  3. ev.offsetX/Y 光标相于当前 DOM 元素的位置

注:在事件回调函数内部通过 window.event 同样可以获取事件对象。

案例:按下回车,显示评论

// 键盘抬起事件,获取事件对象,判断是否按下Enter键
tx.addEventListener('keyup',function (e) {
if(e.key === 'Enter'){
if(tx.value.trim() !== ''){
// 显示评论
item.style.display = 'block'
// 更改评论内容
text.innerHTML = tx.value
}

// 清空文本域
tx.value = ''
total.innerHTML = `${tx.value.length}/200字`
}
})

扩展——字符串的方法

去除字符串的左右空格

str.trim()

环境对象

能够分析判断函数运行在不同环境中 this 所指代的对象。

环境对象:指的是函数内部特殊的变量 this ,它代表着当前函数运行时所处的环境。

作用: 一般的函数都存在this!能反映出这个函数,存在于哪个对象中!——找妈

this变量存储的是一个地址,这个地址指向一个对象,这个对象由当前函数的环境决定!

this指向

死记硬背:

  1. 非严格模式下(默认),指向全局对象window
  2. 构造函数时,指向新创建的对象
  3. 通过上下文对象,调用该函数时,函数中的this指向上下文对象,“谁调用它,this指向谁”
  4. 箭头函数中,this由外层作用域来决定

模糊概念:函数的调用方式不同,this指代的对象也不同,“谁调用函数,this就指向谁”

应用场景

简化代码,在事件中通过当前指向对象(调用者),来设置当前对象的属性等效果

btn.addEventListener('click',function () {
this.style.color = 'red'
})
<script>
// 声明函数
function sayHi() {
// this 是一个变量
console.log(this);
}

// 声明一个对象
let user = {
name: '张三',
sayHi: sayHi // 此处把 sayHi 函数,赋值给 sayHi 属性
}

let person = {
name: '李四',
sayHi: sayHi
}

// 直接调用
sayHi() // window
window.sayHi() // window

// 做为对象方法调用
user.sayHi()// user
person.sayHi()// person
</script>

结论:

  1. this 本质上是一个变量,数据类型为对象
  2. 函数的调用方式不同 this 变量的值也不同
  3. 【谁调用 this 就是谁】是判断 this 值的粗略规则
  4. 函数直接调用时实际上 window.sayHi() 所以 this 的值为 window

回调函数

如果将函数 A 做为参数传递给函数 B 时,我们称函数 A 为回调函数。函数当作实参!

这个函数,在声明时不执行(写成实参时不执行),当B函数满足一定条件时,才去调用执行A函数

<script>
// 声明 foo 函数
function foo(arg) {
console.log(arg);
}

// 普通的值做为参数
foo(10);
foo('hello world!');
foo(['html', 'css', 'javascript']);

function bar() {
console.log('函数也能当参数...');
}
// 函数也可以做为参数!!!!
foo(bar);
</script>

函数 bar 做参数传给了 foo 函数,bar 就是所谓的回调函数了!!!

我们回顾一下间歇函数 setInterval

<script>
function fn() {
console.log('我是回调函数...');
}
// 调用定时器
setInterval(fn, 1000);
</script>

fn 函数做为参数传给了 setInterval ,这便是回调函数的实际应用了,结合刚刚学习的函数表达式上述代码还有另一种更常见写法。

<script>
// 调用定时器,匿名函数做为参数
setInterval(function () {
console.log('我是回调函数...');
}, 1000);
</script>

结论:

  1. 回调函数本质还是函数,只不过把它当成参数使用
  2. 使用匿名函数做为回调函数比较常见

DOM小结

学习web Apis的注意事项

  1. 还是要对JS语法中的对象、数组、函数等有基础的印象,比如分清楚堆和栈,容器与值,创建变量,就是请求内存分配空间,去存储右边的值,值又分为直接量和引用量,直接量又分为数值、字符串、布尔、未定义和空,后三种值很少,是存储在栈中的值,可以直接调用,修改!引用性数据可以看成是在栈中存放地址,在堆中存放数据,栈中的地址不能修改!可以使用null来清空堆中的数据!
  2. 注意类型的隐式转换!
  3. 主要的是,利用内置的方法,比如数学内置对象,能熟练的增删查改对象、数组!基本的类型转换与表达式计算,核心功能肯定是已经提供好的方法和属性APIs!思路需要自己思考和计算简单数值,以及不同数值之间的逻辑关系!然后结合这些APIs去实现功能!
  4. 编程的逻辑思维,默认是顺序的解决问题,从一开始构建解决的办法时,最好是多条思路,每次选择最优解,形成习惯,积累思路!然后再去添加改变逻辑顺序的选择和循环,其实也可以把这两种结果按照顺序结果理解,即执行与不执行(选择性执行),重复的把一段代码复制几份!
  5. 网页所有内容都存放在document对象中,也就是说document对象主要功能是,来操作显示网页内容、样式等效果,改变网页内容的,主要引用场景是把后端传递的数据,生成到页面中,把事件发生时,改变页面中的内容!
  6. 无非就是修改最终页面展示内容和属性,在不同的环节都可以实现,在本身的HTML文件和CSS上就可以下功夫,是怎么布局,JS修改会更容易,修改后的效果会更好,本身就是对HTML+CSS的熟练度和审美的训练,也就是开始的时候是什么样子,遇到交互,会变成什么样子,中间过渡时间等,还有就是偏向于功能型的,比如,网页中哪些内容需要变化,自动切换,自动计数,自动更新收集到的一切信息等JS功能
  7. 不仅理解代码块,更重要的是理解和熟练使用函数的功能,结束,返回,以及this等!
  8. 本质上就是容器与值,表达式,与控制结构的语句,就能实现很多计算,如果要完成系统性的功能,内置的已经写好了
  9. 选择器是内置的、修改属性和内容的方法是内置的、解决思路是前辈积累的、各种事件都是内置的,只需要添加就行!

选择元素——document对象

在学习DOM的过程中,其实就是对HTML文件解析的DOM对象进行修改,HTML与document对象一一对应,所以选择页面中的元素是依靠document对象!

操作元素——节点对象

然后核心是操作元素,也就是改变其内容和属性!页面中的元素又与元素对象(节点对象)一一对应!

HTML + CSS 样式占大头(丰富的页面布局和动效)

怎么变化,这又需要对HTML有一定的基础,或者讲,要想变化的好看,HTML本身就要设计得好看,然后修改后的效果也要好看,这需要扎实的HTML和CSS样式的基础!开始和交互结果!

自动化效果——JS占大头

倒计时,事件,都是自动响应,也就是自动去调取函数执行,这些函数一般涉及到DOM对象,让网页内容或者样式发生改变!从而达到自动化交互的网页!这才是web2.0时代!HTML和CSS只能展示页面充其量就是电子版的报纸!加上自动化的事件,才能让网页交互!

Web APIs - 第3天——事件进阶

Web APIs 第三天

补充:

案例:全选按钮

补充知识:CSS选择器

除了元素选择器和关系选择器(什么关系的什么),其它选择器可以加限定符,表示什么的什么!

选择表单元素时,可以使用伪类选择器下的状态伪类,:enabled:disabled:checked可以选中,具有这个属性的表单,可以在前面加上限定符,比如元素选择器!表示集合中的一部分!

/* 可以选中具有类名为ck的元素的具有checked属性的元素!
.ck:checked {

}

解决方法:

// 获取全选checkbox和其它checkbox,点击后,跟随值变化
const checkAll = document.querySelector('#checkAll')
const ck = document.querySelectorAll('.ck')
// 由于其它选择有三个对象,所有用for循环操作
checkAll.addEventListener('click',function () {
for(let i = 0; i < 3; i++){
ck[i].checked = this.checked
}
})

// 给每个其它选择绑定事件,当被勾上的数量等于其它选择的总数量,全选的状态也勾上
for(let i = 0; i < 3; i++){
ck[i].addEventListener('click',function () {
checkAll.checked = document.querySelectorAll('.ck:checked').length === ck.length
})
}

事件前言:

今天学习事件的内部阶段,事件的细分领域——事件执行过程中,流动的执行经过!

与元素(对象)、元素间的关系(父级元素)、事件对象都有关!

进一步学习 事件进阶,实现更多交互的网页特效,结合事件流的特征优化事件执行的效率

  • 掌握阻止事件冒泡的方法
  • 理解事件委托的实现原理

一、事件流

事件流是对事件执行过程的描述,即事件流是事件完整执行过程中的流动路径,分两个阶段!逐层向下捕获,再逐层向上冒泡

如图所示,任意事件被触发时总会经历两个阶段:【捕获阶段】和【冒泡阶段】。

event

从document对象找到html对象,逐层找到对应的元素对象!

简言之,捕获阶段是【从父到子】的传导过程,冒泡阶段是【从子向父】的传导过程。

事件流两个阶段——捕获和冒泡

虽然你动的是儿子,但是要经过祖宗!从祖宗到儿子,再从儿子到祖宗,可以选择执行同名事件!

事件捕获阶段:

从DOM的根元素开始执行,对应的事件(从父到子),同名事件会逐层发生,调用对应函数!当第三个参数为true时,启用,如果每层都加上,从父到子每层都执行!按需决定是否启动,在JS中很少使用!默认为false,on事件不支持事件的捕获阶段!

DOM对象.addEventListener('事件',处理函数,是否使用捕获机制)

事件冒泡阶段:默认就是使用冒泡阶段

当一个元素触发事件后,会依次向上调用所有父级元素的同名事件,事件冒泡是默认存在的,第三个参数不写,默认false,就是使用冒泡!执行从子到父层的同名事件

DOM对象.addEventListener('事件',处理函数)
<body>
<h3>事件流</h3>
<p>事件流是事件在执行时的底层机制,主要体现在父子盒子之间事件的执行上。</p>
<div class="outer">
<div class="inner">
<div class="child"></div>
</div>
</div>
<script>
// 获取嵌套的3个节点
const outer = document.querySelector('.outer');
const inner = document.querySelector('.inner');
const child = document.querySelector('.child');

// html 元素添加事件
document.documentElement.addEventListener('click', function () {
console.log('html...')
})

// body 元素添加事件
document.body.addEventListener('click', function () {
console.log('body...')
})

// 外层的盒子添加事件
outer.addEventListener('click', function () {
console.log('outer...')
})

// 中间的盒子添加事件
outer.addEventListener('click', function () {
console.log('inner...')
})

// 内层的盒子添加事件
outer.addEventListener('click', function () {
console.log('child...')
})
</script>
</body>

执行上述代码后发现,当单击事件触发时,其祖先元素的单击事件也【相继触发】,这是为什么呢?

结合事件流的特征,我们知道当某个元素的事件被触发时,事件总是会先经过其祖先才能到达当前元素,然后再由当前元素向祖先传递,事件在流动的过程中遇到相同的事件便会被触发。

事件流执行顺序

再来关注一个细节就是事件相继触发的【执行顺序】,事件的执行顺序是可控制的,即可以在捕获阶段被执行,也可以在冒泡阶段被执行。

如果事件是在冒泡阶段执行的,我们称为冒泡模式,它会先执行子盒子事件再去执行父盒子事件,默认是冒泡模式。如果事件是在捕获阶段执行的,我们称为捕获模式,它会先执行父盒子事件再去执行子盒子事件。

<body>
<h3>事件流</h3>
<p>事件流是事件在执行时的底层机制,主要体现在父子盒子之间事件的执行上。</p>
<div class="outer">
<div class="inner"></div>
</div>
<script>
// 获取嵌套的3个节点
const outer = document.querySelector('.outer')
const inner = document.querySelector('.inner')

// 外层的盒子
outer.addEventListener('click', function () {
console.log('outer...')
}, true) // true 表示在捕获阶段执行事件

// 中间的盒子
outer.addEventListener('click', function () {
console.log('inner...')
}, true)
</script>
</body>

结论:

  1. addEventListener 第3个参数决定了事件是在捕获阶段触发还是在冒泡阶段触发
  2. addEventListener 第3个参数为 true 表示捕获阶段触发,false 表示冒泡阶段触发,默认值为 false
  3. 事件流只会在父子元素具有相同事件类型时才会产生影响
  4. 绝大部分场景都采用默认的冒泡模式(其中一个原因是早期 IE 不支持捕获)

阻止冒泡——利用事件对象

因为默认是冒泡模式的存在,所以容易导致事件影响父级元素,需要使用阻止冒泡!

阻止冒泡是指阻断事件的流动,保证事件只在当前元素被执行,而不再去影响到其对应的祖先元素。

是利用event事件对象的方法

需要拿到事件对象,也就是事件发生时,生成的记录事件详情的对象!

事件对象.stopPropagation()

作用:阻止事件流动传播,不光在冒泡阶段有效,在捕获阶段也有效!

<body>
<h3>阻止冒泡</h3>
<p>阻止冒泡是指阻断事件的流动,保证事件只在当前元素被执行,而不再去影响到其对应的祖先元素。</p>
<div class="outer">
<div class="inner">
<div class="child"></div>
</div>
</div>
<script>
// 获取嵌套的3个节点
const outer = document.querySelector('.outer')
const inner = document.querySelector('.inner')
const child = document.querySelector('.child')

// 外层的盒子
outer.addEventListener('click', function () {
console.log('outer...')
})

// 中间的盒子
inner.addEventListener('click', function (ev) {
console.log('inner...')

// 阻止事件冒泡
ev.stopPropagation()
})

// 内层的盒子
child.addEventListener('click', function (ev) {
console.log('child...')

// 借助事件对象,阻止事件向上冒泡
ev.stopPropagation()
})
</script>
</body>

结论:事件对象中的 ev.stopPropagation 方法,专门用来阻止事件冒泡。

特别的:鼠标经过事件

  • mouseovermouseout 会有冒泡效果

  • mouseentermouseleave 没有冒泡效果 (推荐)—不会干扰父代的事件,让父代反复执行!

解绑事件

  1. on事件(L0)

赋值的函数,给清空就是!

元素对象.on事件 = null
  1. 事件监听addEventListener(L2)

使用解绑函数,需要在绑定函数时,设置函数的名字,也就是不能使用匿名函数,有名字回调函数

作用:让事件执行一次就结束,在某些时候,让事件消失,比如弹窗广告!

元素对象.removeEventListener('事件',函数名)

总结两种绑定区别:

传统on注册(L0)

  1. 同一对象,后绑定的同名事件覆盖前面
  2. 使用null覆盖解绑
  3. 只有冒泡阶段

事件监听注册(L2)

  1. 语法中,多了参数用于控制使用拿一个阶段
  2. 后不覆盖前
  3. 第三参数去确认阶段
  4. 必须使用removeEventListener解绑
  5. 匿名函数无法被解绑!

二、事件委托——利用冒泡

利用冒泡阶段,来做事件委托,是开发中的一种技巧!当子代都需要命名同函数事件时,不绑定在子代身上,绑定在父代身上!——儿子的学习,爸爸操心!

事件委托是利用事件流的特征解决一些现实开发需求的知识技巧,主要的作用是提升程序效率。

作用

减少注册次数,提升程序性能

原理

核心:给父元素注册事件,当触发子元素的事件,会冒泡到父元素身上,从而触发父元素的事件!然后在父元素设置一些改变子元素的语句功能就行!

大量的事件监听是比较耗费性能的,如下代码所示

<script>
// 假设页面中有 10000 个 button 元素
const buttons = document.querySelectorAll('table button');

for(let i = 0; i <= buttons.length; i++) {
// 为 10000 个 button 元素添加了事件
buttons.addEventListener('click', function () {
// 省略具体执行逻辑...
})
}
</script>

利用事件流的特征,可以对上述的代码进行优化,事件的的冒泡模式总是会将事件流向其父元素的,如果父元素监听了相同的事件类型,那么父元素的事件就会被触发并执行,正是利用这一特征对上述代码进行优化,如下代码所示:

<script>
// 假设页面中有 10000 个 button 元素
let buttons = document.querySelectorAll('table button');

// 假设上述的 10000 个 buttom 元素共同的祖先元素是 table
let parents = document.querySelector('table');
parents.addEventListener('click', function () {
console.log('点击任意子元素都会触发事件...');
})
</script>

我们的最终目的是保证只有点击 button 子元素才去执行事件的回调函数,如何判断用户点击是哪一个子元素呢?

event

事件委托步骤

1.给父元素绑定同名事件

2.父元素设置响应函数

找到真正触发事件的子元素

借助事件对象Event对象,e.target获取触发事件的子元素对象!父元素内部所有子元素都有效!如果只想部分子元素有效,可以使用通过对象的tagName属性,e.target.tagName指定子元素类型

e.target:代表子代的元素,或者叫触发事件的元素对象,是一个对象!可以改变这个对象的属性,从而改变页面中显示效果!

e.target.tagName:是元素对象的一个属性,其值是一个大写字母的字符串,表示其类型,用于筛选事件中的元素对象,让某些对象才能执行一些操作!

让子元素执行操作

在响应函数function内设置如下代码

  1. 所有元素
('事件'function () {
e.target.属性/内容 = xxx
})
  1. 指定类型元素,先筛选元素对象类型,再让元素对象执行操作!相当于给每个子元素绑定事件!
('事件',function () {
if (e.target.tagName === '大写类型') {
e.target.属性/内容 = xxx
}
})

注意:还可以设置其它元素改变其属性或者内容,通过document对象获取!

事件对象Event中的属性 targetsrcElement属性,表示真正触发事件的元素,它是一个元素类型的节点。

<div class="tab">
<div class="tab-nav">
<h3>每日特价</h3>
<ul>
<li><a class="active" href="javascript:;">精选</a></li>
<li><a href="javascript:;">美食</a></li>
<li><a href="javascript:;">百货</a></li>
<li><a href="javascript:;">个护</a></li>
<li><a href="javascript:;">预告</a></li>
</ul>
</div>
<div class="tab-content">
<div class="item active"><img src="./images/tab00.png" alt="" /></div>
<div class="item"><img src="./images/tab01.png" alt="" /></div>
<div class="item"><img src="./images/tab02.png" alt="" /></div>
<div class="item"><img src="./images/tab03.png" alt="" /></div>
<div class="item"><img src="./images/tab04.png" alt="" /></div>
</div>
</div>
<script>
const ul = document.querySelector('ul')
ul.addEventListener('click',function (e) {
// 通过事件对象指定选中的对象,且类型为a标签,才能执行操作
if(e.target.tagName === 'A'){
// 有active类名的对象,移除active类名
document.querySelector('ul .active').classList.remove('active')
// 当前选中元素,添加active类名
e.target.classList.add('active')
}
})
</script>

阻止元素默认行为——事件对象

再某些情况下,需要阻止默认行为的发生,比如阻止链接跳转,表单提交!

语法:

e.preventDefault()

给表单或者链接,绑定事件,在回调函数中,获取事件对象,然后e.preventDefault()即可!

作用:可以设置某些事件,在满足要求下,才能执行

案例:阻止链接跳转,阻止直接提交表单

<a herf="www.baidu.com">百度一下</a>

<script>
const a = document.querySelector('a')
a.addEventListener('click',function (e) {
e.preventDefault()
})
</script>

其他事件——涉及到BOM

1、页面加载事件

全部资源加载事件

监听页面所有资源加载完毕:给window对象,添加load(window代表窗口,比document还大)

等待所有资源,包括外部资源(如图片、外联CSS和JavaScript等)加载完毕,才触发的事件

作用:有些时候需要等页面资源全部处理完了做一些事情,或者某个资源加载完成,可以把JS放头部

这样的话,JS代码出现的位置,就可以随意了,放在头部也可以正常执行,不会获取不到元素而报错

事件名:load

监听页面所有资源加载完毕:给window对象,添加load

window.addEventListener('load', function() {
// 等待页面所有资源加载完毕,就执行这个回调函数
})

也可以给元素添加加载事件,代表这个元素引入完毕!

img.addEventListener('load', function() {
// 等这张图加载完毕,就执行这个回调函数
})

文档资源加载事件

当初始的HTML文档被完全加载和解析完成之后,DOMContentLoaded事件被触发

就不需要等待外部资源、如样式、图像等加载完毕,这样在网速不好使更快速!

事件名:DOMContentLoaded

监听HTML结构加载完毕:给document对象,添加DOMContentLoaded

document.addEventListener('DOMContentLoaded', function() {
// 等待页面HTML结构加载完毕,就执行这个回调函数
})

2、元素滚动事件——重要

滚动条在滚动的时候持续触发的事件,需要有滚动条!

作用:浏览器检测到页面滚动到某个区域后,做一些处理,如回到顶部

事件名:scroll

监听整个页面滚动:给window对象或者document,添加scroll,更习惯给window加,效果一样

window.addEventListener('scroll', function() {
// xxxxx
})

也可以给元素添加滚动事件,当元素有滚动条,让这个元素内容滚动时,做一些处理!

div.addEventListener('scroll', function() {
// xxxxx
})

但是仅检测滚动,缺少使用场景,更刚常见的是,页面滚动到某个地方,才发生一些事情!

元素对象属性——查看元素内容卷去大小

通过元素的scrollLeftscrollTop属性,获取内部的内容卷走,隐藏的大小,这两个值是数值类型,可读写!也就是说还可以让内容指定卷去多少!这个属性,常常配合scroll事件,以便设置临界点!

image-20230602182115464

代表被卷去的大小,即元素内容往左、上滚出元素的距离!返回的是数值类型!

image-20230603120025888

div.addEventListener('scroll', function() {
// 当盒子的内容发生滚动时,打印被卷去的高度!
console.log(元素对象.scrollTop)
})

使用场景一:常配合滚动滚动事件,一起来操作内容的显隐!
常用的元素对象是整个页面,也就是html对象!获取html 对象,使用document.documentElement

当对象内部的内容滚动,内容有卷去头部时,外层对象的scrollTop属性,才会发生变化!所以当body滚动时,比body大的元素,html对象的scrollTop属性就会发生变化!写法特殊document.documentElement.scrollTop

window.addEventListener('scroll', function() {
// 放在回调函数内部,可以使用常量,如果放在外部,就需要使用变量
const n = document.documentElement.scrollTop
if (n >= 100) {
div.style.display = 'block'
}
})

使用场景二:给元素对象的scrollTop属性赋值,指定卷去的高度

案例:点击回到顶部

const backTop = document.querySelector('#backTop')
backTop.addEventListener('click',function () {
document.documentElement.scrollTop = 0
// 也可以通过方法,来实现滚动,scrollTo(X,Y),第一个参数是左右,第二个参数是上下!
// Window.scrollTo(0,0)
})

补充:隐藏元素的技巧

  1. display = none/block,加上延迟动画,效果就是原位出现
  2. opacity = 0/1,加上延迟动画,效果就是原位淡入淡出
  3. margin-top = -边框以内尺寸/0,加上延迟动画,效果就是下滑,上滑

案例:当页面滑动300像素,显示电梯导航

const elevator = document.querySelector('.xtx-elevator')
window.addEventListener('scroll',function () {
const n = document.documentElement.scrollTop
if (n >= 300){
elevator.style.opacity = 1
} else {
elevator.style.opacity = 0
}
})

特别的元素方法——元素内容滚动的方法

scrollTo()方法,可以把内容滚动到指定坐标

语法:元素对象.scrollTo(x,y)

scrollTop属性实现差不多的效果,不同的是scrollTop属性是赋值,scrollTo(x,y)方法是传入参数!

3、页面尺寸事件——缩放事件

会在窗口尺寸改变的时,触发事件:给window对象,添加resize

事件名resize

作用:检测页面是否缩放!配合屏幕大小属性,类似媒体查询,更强大!

window.addEventListener('resize', function() {
// xxxxx
})

检测元素大小——元素属性

获取元素的可见部分宽高(不包含margin、border、滚动条),仅包含元素自身设置的内容宽高、内边距padding!

属性:clientWidthclientHeight

作用:通过JS可以得到元素内容的大小!常用来检测屏幕大小!

window.clientWidth

回到盒子模型,盒子的大小,盒子之间的位置距离!

元素尺寸与位置——元素对象属性

通过JS的方式,知道元素,在页面中的位置!便于精确的滚动到元素时,才触发事件!

获取盒子大小宽高——属性

获取元素的自身宽高,包括元素内容的宽高、内边距padding、border,与上一个类似,仅多了一个边框的尺寸,获取出来的是数值,方便计算

属性:offsetWidthoffsetHeight

注意: 获取的是可视宽高, 如果盒子是隐藏的,获取的结果是0

获取盒子相对位置——属性(只读)

  • 获取元素离自己定位父级元素的左、上距离!

属性:offsetLeftoffsetTop

注意:只是可读,也就是不能移动元素!且受父级影响,相对于非static定位的第一个祖先元素的距离,和绝对定位的性质类似!如果父代没有都没有,则以页面左上角为准!

window.addEventListener('scroll', function () {
const n = document.documentElecter.scrollTop
elevator.style.opacity = n >= entry.offsetTop ? 1 : 0
})
  • 获取元素的大小及其相对于视口的位置——返回个对象(包含宽高、位置)

方法:getBoundingClientRect()

image-20230603161453438

console.log(div.getBoundingClientRect())

小结

yue18_20230603_162232

总之:

  1. DOM对象不仅可以把一些数据,生成到页面当作,还可以借助事件,让用于与网页产生交互!
  2. 当用户产生一些行为,页面自动的产生响应行为,产生一种智慧的交互效果!这就是事件的作用
  3. window对象也有一些事件,与网页展示的内容无关,与网页本身有关,当网页发生变化,从而去改变网页展示的内容,需要加强练习!

案例:电梯导航

<script>
// 使用自执行函数,封装在函数内,防止变量污染!
// 1.滑动显示,点击回到顶部
(function () {
// 滑动到某个盒子xtx_entry,显示电梯导航
const entry = document.querySelector('.xtx_entry')
const elevator = document.querySelector('.xtx-elevator')
// 给窗口绑定滑动事件,也可以给document添加,效果一样,冒泡过程
window.addEventListener('scroll',function () {
const n = document.documentElement.scrollTop
if (n >= entry.offsetTop){
elevator.style.opacity = 1
} else {
elevator.style.opacity = 0
}
})
// 回到顶部
const backTop = document.querySelector('#backTop')
backTop.addEventListener('click', function () {
// 顶
document.documentElement.scrollTop = 0
// 去样式
const old = document.querySelector('.xtx-elevator-list .active')
if(old) old.classList.remove('active')
})
})();

// 2.电梯导航,点击小盒子,滑动到大盒子
(function () {
// 委托事件,利用冒泡过程
// 获取父盒子
const elevator = document.querySelector('.xtx-elevator-list')
// 获取到所有楼层的盒子,是数组对象!
// const pane = document.querySelectorAll('.xtx_panel')
// 给父级盒子绑定点击事件,子代点击也会触发
elevator.addEventListener('click', function (e) {
// 获取带active类名的元素
const old = document.querySelector('.xtx-elevator-list .active')
// 仅作用于<a>标签
if(e.target.tagName === 'A' && e.target.dataset.name){
// 如果能获取到,就移除已存在的active类名
if(old) old.classList.remove('active')
e.target.classList.add('active')
// 点击的哪一层楼,页面滚动到哪一楼,楼和电梯要对应,要求高,有缺陷
// document.documentElement.scrollTop = pane[e.target.dataset.id].offsetTop
// 另一种更好的做法,可以不按顺序出牌
const top = document.querySelector(`.xtx_goods_${e.target.dataset.name}`).offsetTop
document.documentElement.scrollTop = top
}
})
})();

// 3.页面滑动到大盒子,自动选上当前电梯导航小盒子
(function () {
// 获取到所有楼层的盒子,是数组对象!
const elevators = document.querySelectorAll('.xtx-elevator-list [data-name]')
// 四个楼层的高度
const newTop = document.querySelector('.xtx_goods_new').offsetTop
const popularTop = document.querySelector('.xtx_goods_popular').offsetTop
const brandTop = document.querySelector('.xtx_goods_brand').offsetTop
const gtopicTop = document.querySelector('.xtx_goods_topic').offsetTop

window.addEventListener('scroll', function () {
// 先取消
const old = document.querySelector('.xtx-elevator-list .active')
if(old) old.classList.remove('active')
// 再添加,倒着数就能简写代码
if(document.documentElement.scrollTop >= gtopicTop){
elevators[3].classList.add('active')
} else if(document.documentElement.scrollTop >= brandTop){
elevators[2].classList.add('active')
} else if(document.documentElement.scrollTop >= popularTop){
elevators[1].classList.add('active')
} else if(document.documentElement.scrollTop >= newTop){
elevators[0].classList.add('active')
}
})
})();
</script>
  1. 多个子代同名事件,不用想,直接来个委托,再借助e.target,指定对应子代!
  2. 滚动页面直接使用document.documentElement.scrollTop = xxx,记得在CSS里添加丝滑!
html {
/* 让滚动条滑动 */
scroll-behavior: smooth;
}
  1. 页面发生滚动时,绑定事件window.addEventListener('scroll',function () {})
  2. 自己添加样式前,先删除兄弟的样式
  3. 获取单个对象,直接按对象的增删查改处理,也可以获取对象数组,根据索引拿对象,再操作
  4. 自执行函数,变量在函数内,不冲突,且执行调用,相当于一坨保护变量的代码块!
  5. 获取对象时,直接用const常量,拿的是对象的地址,完全用不担心!
  6. 获取元素相对body的高度,.offsetTop,注意需要前面的父级元素都没有定位!

编程思维最重要:

把最终的想要的先写出来,再去梳理过程,有哪些逻辑判断!

无非就是根据页面变化,绑定对应事件,然后改变DOM对象的属性!

Web APIs - 第4天——对元素的操作

Web APIs 第四天

进一步学习 DOM 相关知识,实现可交互的网页特效

  • 能够插入、删除和替换元素节点
  • 能够依据元素节点关系查找节点

日期对象

用来表示时间的对象,这个对象内保存着年月日、时、分、秒等时间数据!时间轴上任意一点的时间

作用:可以得到当前系统的时间

const date = new Date(); 	// 创建时间对象,返回当前系统时间
console.log(typeof date) // date 变量即所谓的时间对象
console.log(date) // Sun Jun 04 2023 10:20:40 GMT+0800

实例化——JS内置对象Date

ECMAScript 内置获取系统时间的对象 Date,使用 Date 时与之前学习的内置对象 console 和 Math 不同,它需要借助 new 关键字才能使用。

获取当前时间

实例化时,不传值,就是当前时间

// 1. 实例化
const date = new Date(); // 系统默认时间

获取指定时间(创建时传参,字符串类型)

在实例化时,传入指定的时间,就能创建一个指定的时间对象!可以用在倒计时等场景!

const date = new Date('2020-05-01 08:30:00') // 指定时间

日期对象的方法(重要)

直接获取的时间不够直观,且全部显示,借助对象的方法,得到单独的年月日等时间,阿拉伯数字!

方法 作用 说明
getFullYear() 获取年份 四位年份
getMonth() 获取月份 取值为 0 ~ 11
getDate() 获取月份中的每一天 不同月份取值也不相同
getDay() 获取星期 取值为 0 ~ 6(星期天是0,以之开始)
getHours() 获取小时 取值为 0 ~ 23
getMinutes() 获取分钟 取值为 0 ~ 59
getSeconds() 获取秒 取值为 0 ~ 59

注意:方法是在对象里面,所以先获取时间对象,通过实例化就能得到一个时间对象!

返回的是数字类型,可以直接进行加法运算!

// 1. 实例化
const date = new Date();
// 2. 调用时间对象方法
// 通过方法分别获取年、月、日,时、分、秒
const year = date.getFullYear(); // 四位年份
const month = date.getMonth() + 1; // 0 ~ 11
const day = date.getDate();

案例:返回当前时间,格式是年-月-日 时:分:秒

const time = new Date()
const year = time.getFullYear()
const month = (time.getMonth() + 1) < 10 ? '0' + (time.getMonth() + 1) : time.getMonth() + 1
const day = time.getDate() < 10 ? '0' + time.getDate() : time.getDate()
const hours = time.getHours() < 10 ? '0' + time.getHours() : time.getHours()
const minutes = time.getMinutes() < 10 ? '0' + time.getMinutes() : time.getMinutes()
const seconds = time.getSeconds() < 10 ? '0' + time.getSeconds() : time.getSeconds()
console.log(`今天是:${year}-${month}-${day} ${hours}:${minutes}:${seconds}`)

或者简洁的使用内置方法:

time.toLocaleString()	// 2044/4/1 08:08:08

时间戳

时间戳:是指1970年01月01日00时00分00秒起至现在的毫秒数,它是一种特殊的计量时间的方式。

注:ECMAScript 中时间戳是以毫秒计的。(每一刻都是唯一的,相当于时间轴上的一点,无限向前)

作用:用于倒计时等,时间差,由于时间不方便参与计算,让两个时间点相减,得到时间差!时间段

算法:

  1. 将来时间戳 - 现在时间戳 = 剩余时间段(毫秒级别)
  2. 转化为年月日、时分秒

获取时间戳三种方式

获取时间戳的方法,分别为 getTime() Date.now()+new Date()

  1. 使用getTime()
// 需要先实例化,再调用方法
const date = new Date()
date.getTime()
  1. 简写 +new Date()——推荐
// 把时间对象返回的值,隐式转化为数字类型,就是时间戳
+new Date()
  1. 使用 Date.now()

无需实例化,但只能得到当前时间戳,不能指定时间,前两者可以指定返回的时间!

Date.now()

案例:

// 1. 实例化
const date = new Date()
// 2. 获取时间戳
console.log(date.getTime())

// 还有一种获取时间戳的方法
console.log(+new Date())
// 还有一种获取时间戳的方法
console.log(Date.now())
const time = +new Date('2024-1-1 00:00:00') - +new Date()

特别的:表示星期几——借助数组

const arr = ['星期天',['星期一'],['星期二'],['星期三'],['星期四'],['星期五'],['星期六']]
arr[new Date().getDay()]

深入的了解知识,对知识的运用,体现在这些小技巧上的!

案例:倒计时

// 函数封装 getCountTime
function getCountTime() {
// 1. 得到当前的时间戳
const now = +new Date()
// 2. 得到将来的时间戳
const last = +new Date('2022-4-1 18:30:00')
// console.log(now, last)
// 3. 得到剩余的时间戳 count 记得转换为 秒数
const count = (last - now) / 1000
// console.log(count)
// 4. 转换为时分秒
// h = parseInt(总秒数 / 60 / 60 % 24) // 计算小时
// m = parseInt(总秒数 / 60 % 60); // 计算分数
// s = parseInt(总秒数 % 60);
// let d = parseInt(count / 60 / 60 / 24) // 计算当前秒数
let h = parseInt(count / 60 / 60 % 24)
h = h < 10 ? '0' + h : h
let m = parseInt(count / 60 % 60)
m = m < 10 ? '0' + m : m
let s = parseInt(count % 60)
s = s < 10 ? '0' + s : s
console.log(h, m, s)

// 5. 把时分秒写到对应的盒子里面
document.querySelector('#hour').innerHTML = h
document.querySelector('#minutes').innerHTML = m
document.querySelector('#scond').innerHTML = s
}
// 先调用一次
getCountTime()

// 开启定时器
setInterval(getCountTime, 1000)

或者如下:

// 求剩余时间
function time(over) {
let time = +new Date(over) - +new Date()
let s = Math.floor(time/1000)
let m = Math.floor(s/60)
let h = Math.floor(m/60)
let d = Math.floor(h/24)
h = h%24
m = m%60
s = s%60
return [d,h,m,s]
}

// 获取元素对象,依次填充时间
const h = document.querySelector('.h')
const m = document.querySelector('.m')
const s = document.querySelector('.s')
const arr = time('2023-06-04 20:30:00')
h.innerHTML = arr[1] < 10 ? '0' + arr[1] : arr[1]
m.innerHTML = arr[2] < 10 ? '0' + arr[2] : arr[2]
s.innerHTML = arr[3] < 10 ? '0' + arr[3] : arr[3]

setInterval(function () {
const arr = time('2023-06-04 20:30:00')
h.innerHTML = arr[1] < 10 ? '0' + arr[1] : arr[1]
m.innerHTML = arr[2] < 10 ? '0' + arr[2] : arr[2]
s.innerHTML = arr[3] < 10 ? '0' + arr[3] : arr[3]
},1000)

// 剩余多少天
const over = document.querySelector('.over')
over.innerHTML = `剩余${arr[0]}天`

DOM节点操作

掌握元素节点创建、复制、插入、删除等操作的方法,能够依据元素节点的结构关系查找节点

回顾之前 DOM 的操作都是针对元素节点的属性或文本的,除此之外也有专门针对元素节点本身的操作,如插入、复制、删除、替换元素等。

DOM树里的每一个内容都称为节点!

  • 元素节点:所有标签,称为元素节点!html称为根节点!

  • 属性节点:所有属性

  • 文本节点:所有文本

节点操作前需要转换思维

  1. 抛弃之前独立的节点,现在把节点看成一张网,网中,每两个点都存在关系,一个点通过这层关系,可以查找到网中其它任意一点!
  2. 根据关系来找,父关系、子关系、兄弟关系!
  3. 优势:在响应函数中,可以通过this+关系来操作元素,一个节点可以联系对应的子级或者父级!
  4. 事件中,绑定的事件的元素,和响应函数内待操作的元素之间,通过关系更简洁获取元素对象!
  5. 这样就可以减少querySelecter()来查找对象!
  6. querySelecter()是无差别攻击,一句话查任一节点,而关系型是从一点向周围发散,寻找下一个元素,不仅只有查,还有对元素操作的功能,增删改查!特别是增加元素!
  7. 总之之前是获取对象,然后操作元素的属性和内容,现在重点是操作元素本身,增加元素!

查找节点——属性

1.父节点查找

使用元素对象的parentNode属性,返回最近一级的父元素节点!亲爸爸!,找不到就返回null

// 查找元素的父元素
元素对象.parentNode

// 查找元素的爷爷级元素
元素对象.parentNode.parentNode

案例:关闭广告

<div class="box">
我是广告
<div class="box1">X</div>
</div>
<div class="box">
我是广告
<div class="box1">X</div>
</div>
<div class="box">
我是广告
<div class="box1">X</div>
</div>
<script>
// 1. 获取三个关闭按钮
const closeBtn = document.querySelectorAll('.box1')
for (let i = 0; i < closeBtn.length; i++) {
closeBtn[i].addEventListener('click', function () {
// 按照之前的逻辑,需要获取全部大盒子,然后点击子代,再关闭对应大盒子!
// 现在是通过关系轻松找到对应父级,所以点击小盒子,只关闭当前的父元素
this.parentNode.style.display = 'none'
})
}
</script>

2.子节点查找

  1. childNodes属性:获取所有子节点,包括文本节点等
  2. children属性:仅获取子元素节点,返回的是一个伪数组!与QSAll类似,都是亲儿子!
// 获取所有子元素节点——伪数组
元素对象.children

3.兄弟节点查找

  1. previousElementSibling属性:获取上一个兄弟元素节点
元素对象.previousElementSibling //上一个	2->1
  1. nextElementSibling属性:获取下一个兄弟元素节点
元素对象.nextElementSibling //下一个  2->3

可以使用在上一页、下一页!或者排序!

增加节点(重点)——方法

在页面中增加元素,添加数据,比如发表评论,表格增加一行等!

解决方式是,先创建一个元素,然后再添加到页面中,两个关键因素:首先创建新的 DOM 节点,其次在哪个位置插入这个节点。 创建元素不改变网页,把元素添加到到页面后,元素才能渲染显示!

1.创建节点

创建元素节点方法:**document.createElement('标签名')方法**

创建好的节点,使用变量保存在内存中,等添加好数据后,再追加到页面中显示其内容!

// 创建一个新的元素节点
const div = document.createElement('div')

2.追加节点

想要展示,得插入页面的某个父元素中!找爹收留!所以还得需先找一个父级元素,再操作!

追加到最后

插入到父元素得最后一个子元素:**appendChild()方法**

// 插入到
元素对象.appendChild(要插入的元素)

案例:一个完整的步骤

<ul></ul>
<script>
// 先找一个父级元素,用于容纳创建的元素!
const ul = document.querySelecter('.ul')
// 创建一个新的元素节点
const li = document.createElement('li')
// 给新元素添加内容
li.innerHTML = '我是li'
// 追加到最后面
ul.appendChild(li)
</script>

插入到任意一个子元素之前

插入到父元素中,某个子元素的前面:**insertBefore()方法**

父元素.insertBefore(要插入的元素,在哪个子元素之前)

常见的场景,要么追加到最后,要么放在最前面;获取父级,放在父第一个子元素前

// 插入到父元素中第一个子元素前
ul.insertBefore(li, ul.children[0])

案例:重新制作《学生在线》

1.先找一个父级元素,用于容纳创建的元素!
2.创建一个新的元素节点
3.给新元素添加内容
4.父级元素内新元素追加到最后面

如果多个数据,就利用for循环添加!

特殊的增加节点:克隆节点——方法

步骤

  1. 复制一个原有的节点
  2. 把复制的节点放到指定的元素内部

克隆节点

cloneNode():会克隆出一个跟原标签一样的元素,括号内传入布尔值,默认参数为false

  • 参数,为true时,则表示节点后代一起克隆,常用于无限循环的轮播!

  • 为false时,仅克隆当前标签元素,默认为false!

// 克隆一个已有的元素节点
元素对象.cloneNode(布尔值)

深克隆:包含文本内容;浅克隆不包含!

注意:因为追加节点也需要获取父元素,所以直接获取父元素,然后克隆父的子(兄弟),再父内追加

删除节点

在JS原生DOM操作中,删除元素必须通过父元素删除removeChild()

父元素.removeChild(删除的元素)

注意:如果不存在父子关系,则删除不成功,删除不同于隐藏,隐藏是还存在,删除查不到

案例:

// 删除父元素下的一个子元素——必须父删子
ul.removeChild(ul.children[0])

删除节点不同于隐藏,在生成的HTML结构中,这个元素没有了!

小结

  1. 元素本身的操作,重点掌握增加元素
  2. 增加元素离不开,查找元素,所以特别是父子关系,常配合使用!
  3. 增删元素,都需要先获取父级元素再操作,获取了父级,再通过元素间关系操作,减少qs,简洁
  4. 再结合之前学习的DOM操作,修改元素的内容,就可以把用户输入的信息,增加到页面中了!

操作元素的综合案例:

创建节点

  • createElement() 动态创建任意 DOM 节点

  • cloneNode() 复制现有的 DOM 节点,传入参数 true 会复制所有子节点

  • appendChild() 在末尾(结束标签前)插入节点

<body>
<h3>插入节点</h3>
<p>在现有 dom 结构基础上插入新的元素节点</p>
<hr>
<!-- 普通盒子 -->
<div class="box"></div>
<!-- 点击按钮向 box 盒子插入节点 -->
<button class="btn">插入节点</button>
<script>
// 点击按钮,在网页中插入节点
const btn = document.querySelector('.btn')
btn.addEventListener('click', function () {
// 1. 获得一个 DOM 元素节点
const p = document.createElement('p')
p.innerText = '创建的新的p标签'
p.className = 'info'

// 复制原有的 DOM 节点
const p2 = document.querySelector('p').cloneNode(true)
p2.style.color = 'red'

// 2. 插入盒子 box 盒子
document.querySelector('.box').appendChild(p)
document.querySelector('.box').appendChild(p2)
})
</script>
</body>

再来看另一种情形的代码演示:

  • createElement() 动态创建任意 DOM 节点

  • cloneNode() 复制现有的 DOM 节点,传入参数 true 会复制所有子节点

  • insertBefore() 在父节点中任意子节点之前插入新节点

<body>
<h3>插入节点</h3>
<p>在现有 dom 结构基础上插入新的元素节点</p>
<hr>
<button class="btn1">在任意节点前插入</button>
<ul>
<li>HTML</li>
<li>CSS</li>
<li>JavaScript</li>
</ul>
<script>
// 点击按钮,在已有 DOM 中插入新节点
const btn1 = document.querySelector('.btn1')
btn1.addEventListener('click', function () {

// 第 2 个 li 元素
const relative = document.querySelector('li:nth-child(2)')

// 1. 动态创建新的节点
const li1 = document.createElement('li')
li1.style.color = 'red'
li1.innerText = 'Web APIs'

// 复制现有的节点
const li2 = document.querySelector('li:first-child').cloneNode(true)
li2.style.color = 'blue'

// 2. 在 relative 节点前插入
document.querySelector('ul').insertBefore(li1, relative)
document.querySelector('ul').insertBefore(li2, relative)
})
</script>
</body>

删除节点

删除现有的 DOM 节点,也需要关注两个因素:首先由父节点删除子节点,其次是要删除哪个子节点。

结论:removeChild() 删除节点时一定是由父子关系。

查找节点

DOM 树中的任意节点都不是孤立存在的,它们要么是父子关系,要么是兄弟关系,不仅如此,我们可以依据节点之间的关系查找节点。

父子关系

结论:

  • childNodes 获取全部的子节点,回车换行会被认为是空白文本节点
  • children 只获取元素类型节点

结论:parentNode 获取父节点,以相对位置查找节点,实际应用中非常灵活。

兄弟关系

结论:

  • previousSibling 获取前一个节点,以相对位置查找节点,实际应用中非常灵活。
  • nextSibling 获取后一个节点,以相对位置查找节点,实际应用中非常灵活。

M端事件—移动端

移动端的输入方式不同,比如触屏事件touch

touch对象:代表一个触摸点,触屏事件可响应用户手指对屏幕的操作!

触屏touch事件 说明
touchstart 手指触摸到一个元素时触发,开始摸
touchmove 手指从元素上滑动时触发,一直摸
touchend 手指从元素上移开时触发,结束摸

在移动端,JS原生代码很麻烦,一般使用插件处理移动端事件!

第三方插件——swiper

https://www.swiper.com.cn

如何了解第三方插件

  1. 查看官方说明
  2. 查看案例演示
  3. 获取想要的功能
  4. 定制步骤说明——API文档!

如图:

image-20230604205911917

使用步骤

  1. 引入插件JS文件、CSS文件
  2. 复制HTML结构、CSS样式
  3. 调用JS代码,设置属性等
  4. 其它:调整到想要的页面效果(API文档)

在源文件基础上进行更改,仅在需要轮播图的盒子里,复制HTML在盒子里,然后再把数据填入到给你的HTML盒子内!

案例:学生信息表

  1. 表单的提交事件,与按钮的点击事件!(效果可以一致,但准确来讲,是表单提交事件)

  2. 表单事件发生,也就是内部的控件数据提交,那么每个控件立马清空数据,内存中没有存储,就无法持久取到这些数据!

  3. 在事件的回调函数,像是不存在一样,只在事件发生的过程存在,事件完成,那么指令统统消失!不触发,和触发结束,都像是没有这段代码!自动重置了表单!

  4. 并且,让渲染在页面中的数据,是因为输入而存在的吗?语义化也不通,数据应该是一个存储数据的盒子中拿到数据,渲染出来的,而不是依据输入而存在!当不输入,这些数据就不能被渲染了?只有点击那一刻才被渲染吗?

  5. 语义化的正确,加上程序功能上的正确!才是真正的正确

解决步骤

  1. 把表单中填写的数据收集到成一个对象,然后加到数组中
  2. 把生成的数组,添加上HTML元素结构,再渲染到页面中
  3. 重置表单
  4. 通过委托事件,点击删除按钮,删除数组中该条数据
  5. 重置序号,并添加上HTML元素结构,重新渲染
// 获取表单和表格数据
const info = document.querySelector('.info')
const tbody = document.querySelector('tbody')
// 设置数组,存储提交的数据
let arr = []
info.addEventListener('submit',function (e) {
// 阻止默认提交,才能获取表单的数据,不知道原因????
e.preventDefault()
// 保存到对象中,数组的序号就是数组的长度!
const obj = {
order : arr.length + 1,
uname : info.children[0].value,
age : info.children[1].value,
gender : info.children[2].value,
income : info.children[3].value,
live : info.children[4].value
}
// 追加到数组中
arr.push(obj)
// 清空表格中已存在的数据,并重新添加数组中的数据
tbody.innerHTML = ''
for(let i = 0; i < arr.length; i++){
const tr = document.createElement('tr')
tr.innerHTML = `
<td>${arr[i].order}</td>
<td>${arr[i].uname}</td>
<td>${arr[i].age}</td>
<td>${arr[i].gender}</td>
<td>${arr[i].income}</td>
<td>${arr[i].live}</td>
<td>
<a href="javascript:">删除</a>
</td>
`
tbody.appendChild(tr)
}
// 重置表单!
this.reset()
})

// 删除表单数据,本质上是删除数组的数据,并重新渲染,先事件委托到祖先,再找到中间的父级
tbody.addEventListener('click', function (e) {
if(e.target.tagName === 'A'){
const OrdId = e.target.parentNode.parentNode.children[0].innerHTML - 1

// 删除数组中该条数据,再渲染
arr.splice(OrdId,1)
// 清空表格中已存在的数据,并重新添加数组中的数据
tbody.innerHTML = ''
for(let i = 0; i < arr.length; i++){
arr[i].order = i + 1
const tr = document.createElement('tr')
tr.innerHTML = `
<td>${arr[i].order}</td>
<td>${arr[i].uname}</td>
<td>${arr[i].age}</td>
<td>${arr[i].gender}</td>
<td>${arr[i].income}</td>
<td>${arr[i].live}</td>
<td>
<a href="javascript:">删除</a>
</td>
`
tbody.appendChild(tr)
}
}
})
// 获取表单和表格数据
const info = document.querySelector('.info')
const tbody = document.querySelector('tbody')
// 设置数组,存储提交的数据
let arr = []
info.addEventListener('submit',function (e) {
// 阻止默认提交,才能获取表单的数据,不知道原因????
e.preventDefault()
// 保存到对象中,数组的序号就是数组的长度!
const obj = {
order : arr.length + 1,
uname : info.children[0].value,
age : info.children[1].value,
gender : info.children[2].value,
income : info.children[3].value,
live : info.children[4].value
}
// 追加到数组中
arr.push(obj)
addData()
// 重置表单!
this.reset()
})

// 删除表单数据,本质上是删除数组的数据,并重新渲染,先事件委托到祖先,再找到中间的父级
tbody.addEventListener('click', function (e) {
if(e.target.tagName === 'A'){
const OrdId = e.target.parentNode.parentNode.children[0].innerHTML - 1

// 删除数组中该条数据,再渲染
arr.splice(OrdId,1)
// 更新序号
for(let i = 0; i < arr.length; i++){
arr[i].order = i + 1
}
addData()
}
})
// 把重复部分设置成函数
function addData() {
// 清空表格中已存在的数据,并重新添加数组中的数据
tbody.innerHTML = ''
for(let i = 0; i < arr.length; i++){
const tr = document.createElement('tr')
tr.innerHTML = `
<td>${arr[i].order}</td>
<td>${arr[i].uname}</td>
<td>${arr[i].age}</td>
<td>${arr[i].gender}</td>
<td>${arr[i].income}</td>
<td>${arr[i].live}</td>
<td>
<a href="javascript:">删除</a>
</td>
`
tbody.appendChild(tr)
}
}

PInk老师思路:

// 1. 获取表单中填写的数据
const uname = document.querySelector('.uname')
const age = document.querySelector('.age')
const gender = document.querySelector('.gender')
const salary = document.querySelector('.salary')
const city = document.querySelector('.city')
const items = document.querySelectorAll('input[name]')
// 2. 阻止默认的提交表单,拿到表单中的数据
const arr = []
const info = document.querySelector('.info')
const tbody = document.querySelector('tbody')
// 注意是表单的提交事件,而不是按钮的点击事件!
info.addEventListener('submit',function (e) {
// 阻止默认提交
e.preventDefault()
// 判断填写是否填写
for(let i = 0; i < items.length; i++){
if(items[i].value === ''){
return alert('请补写完全部信息')
}
}
const obj = {
orderID : arr.length + 1,
uname : uname.value,
age : age.value,
gender : gender.value,
salary : salary.value,
city : city.value
}
arr.push(obj)
// 请出表单内容,重置
this.reset()
// 3. 把得到的数组,生成HTML渲染出来
printData()
})

// 4. 设置成函数
function printData() {
// 清空已生成的HTML文件
tbody.innerHTML = ''
// for循环遍历所有数据并渲染
for (let i = 0; i < arr.length; i++) {
const tr = document.createElement('tr')
tr.innerHTML = `
<td>${arr[i].orderID}</td>
<td>${arr[i].uname}</td>
<td>${arr[i].age}</td>
<td>${arr[i].gender}</td>
<td>${arr[i].salary}</td>
<td>${arr[i].city}</td>
<td>
<a href="javascript:" data-id=${i}>删除</a>
</td>
`
tbody.appendChild(tr)
}
}

// 5. 删除表单数据,本质上是删除数组的数据,并重新渲染
tbody.addEventListener('click', function (e) {
if(e.target.tagName === 'A'){
arr.splice(e.target.dataset.id, 1)
// 重新渲染
printData()
}
})

总结:

  1. 操作元素,也就是对页面中元素的增删查,之前学的第一节主要是查和改,特别是改属性和内容
  2. 事件里可以通过this从绑定者,去操作元素,是增删元素,还是改变元素的属性
  3. 事件里还可以通过e.target从触发事件者触发,去操作元素,是增删元素,还是改变元素的属性(特别重要,当前触发事件者是委托事件的核心)
  4. 还可以通过关系,找到中间的元素,比如委托事件中,由祖先绑定事件,孙子级别的元素触发事件,但是想要找到父级元素的信息,就可以通过当前的触发元素,也就是e.target,来向上找关系parentNode属性,找到亲生父亲!
  5. 所有结构、不同语句的代码,都可以看成是顺序结构去执行,所以在任何时候都可以选择终端停止!当然必须包含在可以操控的语句块中!
  6. 只是有些语句有自己默认的顺序结构,在相应位置可以插入自己的语句!
  7. 函数中断用return,循环中断用break,if更简单,在判断条件中,while,switch也是用break

Web APIs - 第5天笔记——BOM及本地存储

Web APIs 第五天

目标: 能够利用JS操作浏览器,具备利用本地存储实现学生就业表的能力

  • BOM操作
  • 综合案例

js组成

JavaScript的组成

  • ECMAScript:

    • 规定了js基础语法核心知识。
    • 比如:变量、分支语句、循环语句、对象等等
  • Web APIs :

    • DOM 文档对象模型, 定义了一套操作HTML文档的API,有官方标准
    • BOM 浏览器对象模型,定义了一套操作浏览器窗口的API,非官方标准

    几乎所有的Web APIs,都存放在window对象内!

1676047389456

BOM(浏览器对象模型)

并不是官网的命名,而是对浏览器提供的API的统称,BOM的API都放在了Window全局对象中!Window代表当前浏览器窗口,其中DOM也属于BOM,因为是document对象也属于Window对象!

window对象

BOM (Browser Object Model ) 是浏览器对象模型

  • window对象是一个全局对象,也可以说是JavaScript中的顶级对象
  • 像document、alert()、console.log()这些都是window的属性和方法,基本BOM的属性和方法都是window的
  • 所有通过var定义在全局作用域中的变量、函数都会变成window对象的属性和方法
  • window对象下的属性和方法调用的时候可以省略window
  • 常见方法:alert()、promp()、open()/close()打开和关闭窗口,
  • 常见属性:innnerWidth/innerHeight页面

1676047436362

location:是window内的对象,用于浏览器URL的相关操作,href属性可以获取或跳转

history:是window内的对象,保存历史操作相关信息,back()/forward()/go()后退,前进,返回

navigator:是window内的对象,保存用户浏览器的相关信息,设备型号,操作系统和地理位置

screen:是window内的对象,保存用户屏幕的相关信息,width/height属性获取屏幕宽高、朝向

定时器-延迟函数

JavaScript 内置的一个用来让代码延迟执行的函数,叫 setTimeout

语法:

setTimeout(回调函数, 延迟时间)

setTimeout 仅仅只执行一次,所以可以理解为就是把一段代码延迟执行, 平时省略window

间歇函数 setInterval : 每隔一段时间就执行一次, , 平时省略window

清除延时函数:

clearTimeout(timerId)

注意点

  1. 延时函数需要等待,所以后面的代码先执行
  2. 返回值是一个正整数,表示定时器的编号
<body>
<script>
// 定时器之延迟函数

// 1. 开启延迟函数
let timerId = setTimeout(function () {
console.log('我只执行一次')
}, 3000)

// 1.1 延迟函数返回的还是一个正整数数字,表示延迟函数的编号
console.log(timerId)

// 1.2 延迟函数需要等待时间,所以下面的代码优先执行

// 2. 关闭延迟函数
clearTimeout(timerId)

</script>
</body>

JS执行机制

JavaScript语言的一大特性就是单线程,同一时间,只能做一件事!
好处是更正确的执行DOM操作,先创建好元素才能操作,不能同时或者提前操作,按顺序进行交互!

单线程意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务,这样所导致的问题是,如果某一JS代码执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞的感觉!

为了避免阻塞,更高效的执行程序,以及一些需要花费时间执行的延时函数,由HTML提出的Web Worker标准,通过浏览器引擎实现多线程,解决JS代码执行过程存在的缺陷!同步异步

同步:

按顺序执行,前一个任务结束后,再执行后一个任务

异步:

如果一个时间需要花费很长时间,可以同时先去做其它事情 ,这流水线上各个流程的执行顺序不同!

同步任务

主线程执行,形成一个执行栈

image-20230607121804134

异步任务

异步是通过回调函数实现的,在执行栈中,把耗时的代码都放到任务队列中去处理(消息队列)

image-20230607122206419

异步任务有以下三种:

1、普通事件:与事件相关

2、资源加载:与外部加载的资源相关

3、定时器:间歇函数

执行机制——事件循环

  1. 先执行执行栈中的同步任务
  2. 筛选到异步任务放到任务队列
  3. 一旦执行栈中的所有同步任务执行完毕,系统再按次序,读取任务队列中的异步任务,如果该任务可以被执行了,再进入到执行栈,开始执行!执行结束再回到任务队列看该执行哪一个了!

image-20230607123232915

真实的执行机制

JS代码放在执行栈执行,当有异步任务时,提交到异步进程,浏览器可以异步同时执行多个任务,某些异步进程任务完毕,再推入到任务列表中,主线程执行完毕,查询任务队列取出一任务,推入到主线程处理,重复该操作!这种循环机制,称为事件循环(event loop)

有序

image-20230607123511245

location对象

window对象给我们提供了一个location属性用于获取或设置窗口的URL,并且可以用于解析URL。因为这个属性返回的是一个对象,所以我们将这个属性也称为location对象。

location对象可以将URL解析为独立的片段,让开发人员可以通过不同的的属性访问这些片段。

URL统一资源定位器的格式

protocol : / / host [ :port]/path/ [ ?query]#fragment

img

常用属性和方法

属性/方法 说明
href 属性,获取完整的 URL 地址,赋值时用于地址的跳转
search 属性,获取地址中携带的参数,符号 ?后面部分
hash 属性,获取地址中的啥希值,符号 # 后面部分
reload() 方法,用来刷新当前页面,传入参数 true 时表示强制刷新

href

href属性:获取完整的URL地址,当赋值时,跳转到指定地址

作用:跳转链接!

search

URL中符号 ?后面部分,通常用于表单提交,请求的信息

hash

URL中符号 # 后面部分,通常用于单页面,更新部分页面信息

reload()——方法

刷新当前页面,相当于浏览器左上角的刷新——F5,也可以传入参数true,强制刷新——ctrl + F5

<body>
<form>
<input type="text" name="search"> <button>搜索</button>
</form>
<a href="#/music">音乐</a>
<a href="#/download">下载</a>

<button class="reload">刷新页面</button>
<script>
// location 对象
// 1. href属性 (重点) 得到完整地址,赋值则是跳转到新地址
console.log(location.href)
// location.href = 'http://www.itcast.cn'

// 2. search属性 得到 ? 后面的地址
console.log(location.search) // ?search=笔记本

// 3. hash属性 得到 # 后面的地址
console.log(location.hash)

// 4. reload 方法 刷新页面
const btn = document.querySelector('.reload')
btn.addEventListener('click', function () {
// location.reload() // 页面刷新
location.reload(true) // 强制页面刷新 ctrl+f5
})
</script>
</body>

navigator是对象,该对象下记录了浏览器自身的相关信息,使用的是什么操作系统,什么浏览器!

常用属性和方法:

  • 通过 userAgent 检测浏览器的版本及平台

作用:根据设备,跳转到指定地址

// 检测 userAgent(浏览器信息)
(function () {
const userAgent = navigator.userAgent
// 验证是否为Android或iPhone
const android = userAgent.match(/(Android);?[\s\/]+([\d.]+)?/)
const iphone = userAgent.match(/(iPhone\sOS)\s([\d_]+)/)
// 如果是Android或iPhone,则跳转至移动站点
if (android || iphone) {
location.href = 'http://m.itcast.cn'
}})();

注意:立即执行函数格式,可以是:

  1. (function () {})()
  2. !/+/~function() {} ()

histroy对象

history (历史)是对象,主要管理历史记录, 该对象与浏览器地址栏的操作相对应,如前进、后退等

使用场景

history对象一般在实际开发中比较少用,但是会在一些OA 办公系统中见到。

67604783479

常见方法:

67604784659

<body>
<button class="back">←后退</button>
<button class="forward">前进→</button>
<script>
// histroy对象

// 1.前进
const forward = document.querySelector('.forward')
forward.addEventListener('click', function () {
// history.forward()
history.go(1)
})
// 2.后退
const back = document.querySelector('.back')
back.addEventListener('click', function () {
// history.back()
history.go(-1)
})
</script>
</body>

本地存储(今日重点)

本地存储:将数据存储在本地浏览器中,准确讲是存放到硬盘中!相当于一个仓库!容器!

image-20230607152355183

常见的使用场景:https://todomvc.com/examples/vanilla-es6/ 页面刷新数据不丢失

好处:

1、设置、读取方便,页面刷新或者关闭不丢失数据,实现数据持久化

2、容量较大,存储在浏览器对象中有两个,sessionStoragelocalStorage,每个约 5M 左右!

localStorage(重点)

作用: 可以将数据永久存储在本地,除非手动删除,否则关闭页面依旧存在,不丢失数据

特性:可以多窗口共享(不跨域),以键值对的形式存储,并且存储的是字符串, 省略了window

67604963508

存储数据(增加和修改)——方法

语法:localStorage.setItem('key','value')

注意:存储的是字符串,不要写成变量了,如果存在变量名,值为字符,也可以!

查看数据——方法

语法:localStorage.getItem('key'),返回键对应的值!

删除数据——方法

语法:localStorage.removeItem('key'),手动删除数据

注意:无论放的是字符还是数字,取出来的都是字符串!

<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>本地存储-localstorage</title>
</head>

<body>
<script>
// 本地存储 - localstorage 存储的是字符串
// 1. 存储
localStorage.setItem('age', 18)

// 2. 获取
console.log(typeof localStorage.getItem('age'))

// 3. 删除
localStorage.removeItem('age')
</script>
</body>

</html>

sessionStorage(了解)

生命周期为关闭浏览器窗口,一关就没!

特性:

  • 用法跟localStorage基本相同
  • 区别是:当页面浏览器被关闭时,存储在 sessionStorage 的数据会被清除

存储:sessionStorage.setItem(key,value)

获取:sessionStorage.getItem(key)

删除:sessionStorage.removeItem(key)

localStorage 存储复杂数据类型

问题:本地只能存储字符串,无法存储复杂数据类型。复杂类型存的时候转化成字符串,取不出来

解决:需要将复杂数据类型转换成 JSON字符串——复杂数据类型字符串(使用方法),再存储到本地

存储

把复杂类型转化为字符串

语法:JSON.stringify(复杂数据类型)

存放的是个字符串,虽然样子长得跟对象一样!——称为JSON对象

JSON字符串:

  • 首先是一个字符串
  • 属性名使用双引号引起来,不能单引号
  • 属性值如果是字符串型也必须双引号
<body>
<script>
// 本地存储复杂数据类型
const goods = {
name: '小米',
price: 1999
}
// localStorage.setItem('goods', goods)
// console.log(localStorage.getItem('goods'))

// 1. 把对象转换为JSON字符串 JSON.stringify
localStorage.setItem('goods', JSON.stringify(goods))
// console.log(typeof localStorage.getItem('goods'))

</script>
</body>

取出

问题:因为本地存储里面取出来的是字符串,不是对象,无法直接使用

**解决: **把取出来的字符串转换为对象

把字符串转化为对象

语法:JSON.parse(JSON字符串)

例如:JSON.parse(localStorage.getItem('goods'))

<body>
<script>
// 本地存储复杂数据类型
const goods = {
name: '小米',
price: 1999
}
// localStorage.setItem('goods', goods)
// console.log(localStorage.getItem('goods'))

// 1. 把对象转换为JSON字符串 JSON.stringify
localStorage.setItem('goods', JSON.stringify(goods))
// console.log(typeof localStorage.getItem('goods'))

// 2. 把JSON字符串转换为对象 JSON.parse
console.log(JSON.parse(localStorage.getItem('goods')))

</script>
</body>

处理数组的方法——字符串前的预处理!

1、遍历数组

forEach()方法,遍历数组

  • 语法:数组.forEach(function(item,index,arr){})
  • 传递一个函数作为参数,数组有多少个数据,这个函数就执行多少次!
    • item:数据的每一项
    • index:索引
    • arr:原始数组
  • 返回值:无
arr.forEach(function(item,index,arr){
arr[index] = item + 'px'
})

2、映射数组

语法:数组.map(function(item,index,arr){}),处理每个数据后,返回一个新数组

使用场景:

map 可以遍历数组处理数据,并且返回新的数组,以return的方式书写映射条件

const newArr = arr.map(function(item,index,arr){
return item + 'px'
})

语法:

<body>
<script>
const arr = ['red', 'blue', 'pink']
// 1. 数组 map方法 处理数据并且 返回一个数组
const newArr = arr.map(function (ele, index) {
// console.log(ele) // 数组元素
// console.log(index) // 索引号
return ele + '颜色'
})
console.log(newArr)
</script>
</body>

map 也称为映射。映射是个术语,指两个元素的集之间元素相互“对应”的关系。

注意:map重点在于有返回值,forEach没有返回值(undefined)

3、数组拼接成字符

数组.join(连接符),返回值一个连接好的字符串

作用:join() 方法用于把数组中的所有元素,转换一个字符串

将数组变成一个字符串,形式为数据+连接符,原数组无变化!

let str1 = arr.join()	// 返回的字符串是XX,XX,XX
let str2 = arr.join('') // 返回的字符串是XXXXXX
let str = arr.join('-') // 返回的字符串是XX-XX-XX
<body>
<script>
const arr = ['red', 'blue', 'pink']

// 1. 数组 map方法 处理数据并且 返回一个数组
const newArr = arr.map(function (ele, index) {
// console.log(ele) // 数组元素
// console.log(index) // 索引号
return ele + '颜色'
})
console.log(newArr)

// 2. 数组join方法 把数组转换为字符串
// 小括号为空则逗号分割
console.log(newArr.join()) // red颜色,blue颜色,pink颜色
// 小括号是空字符串,则元素之间没有分隔符
console.log(newArr.join('')) //red颜色blue颜色pink颜色
console.log(newArr.join('|')) //red颜色|blue颜色|pink颜色
</script>
</body>

综合案例

  • 使用映射和拼接的方法,渲染页面的思路解析

前提:再本地已经存储了数据,现在可以通过localStorage的方法拿到数据!

  1. 有多少条数据,就生成多少个tr——用map()遍历数组,处理数据同时生成tr,返回数组
  2. 把数据填充到tr中
  3. 把带有很多tr的数据,转化为字符串,再赋值给tbody内容——使用join()方法,把数组变成字符串
`tbody.innnerHTML = `str`
// 参考数据
const initData = [
{
stuId: 1001,
uname: '欧阳霸天',
age: 19,
gender: '男',
salary: '20000',
city: '上海',
}
]
const tbody = document.querySelector('tbody')
// 1. 渲染页面,可以写成函数
// 1.1 把数据存入到本地
localStorage.setItem('trdata', JSON.stringify(initData))
// 1.2 从本地获取数据,转化回数组格式,存起来
const arr = JSON.parse(localStorage.getItem('trdata'))
// 1.3 处理数据,把数组中的数据转化成行、每个单元格内的数据,使用映射!return
const trArr = arr.map(function (ele) {
return `
<tr>
<td>${ele.stuId}</td>
<td>${ele.uname}</td>
<td>${ele.age}</td>
<td>${ele.gender}</td>
<td>${ele.salary}</td>
<td>${ele.city}</td>
<td>
<a href="javascript:">删除</a>
</td>
</tr>
`
})
// 1.4 把生成的字符串数组,转化成字符串,并赋值给tbody
tbody.innerHTML = trArr.join(' ')
  • 把表单提交的数据,保存到本地
  1. 阻止表单默认提交
  2. input表单非空判断
  3. 获取的数据生成对象
  4. 获取本地的数组,把生成的对象,追加到后面,再存储到本地(如果有就替换,没有就添加)
  5. 渲染页面、重置表单
  • 删除表单的行,也就是删除本地数据
  1. 采用事件委托,给tbody绑定点击事件,筛选每个删除单元格触发
  2. 给每一行的删除单元格添加编号,也就是映射时,添加自定义属性,然后获取到这个编号
  3. 把本地数据转化为数组,删除对应编号的数据,再从新存储到本地,重新渲染页面

总结

  1. 无非涉及的思路,把数据添加到页面中,重点是数据从哪里来?什么类型,怎么添加?
  2. 数据从哪里来?
  3. 程序中自己设置,比如数据较少时,可以使用自定义数组,如日期相关的星期几
  4. 外部数据,比如本地存储的数据
  5. 需要的数据类型?
  6. 由于本地存放的只能是字符串,所以,复杂数据需要类似转码的操作,进行数据处理
  7. 怎么添加数据?
  8. 也需要对数据进行数据,生成想要的数据格式,才能放到页面中去,比如map(),映射数组,把数据转化成想要的形式,一般是再补充相关的结构,让数据具有某种格式,再到最后怎么把所有数据变成一整条数据,就需要借助join(),拼接数组成字符,形成庞大的一条字符数据
  9. 核心是对数据的处理,先处理数据,再进行简单的DOM节点操作,就能实现页面中数据的增删改查!也可以直接对数据进行处理,网页元素绑定事件与数据产生联系!

Web APIs - 第6天笔记——正则表达式

Web APIs 第六天

目标:能够利用正则表达式完成小兔鲜注册页面的表单验证,具备常见的表单验证能力

  • 正则表达式
  • 综合案例
  • 阶段案例

正则表达式

正则表达式(Regular Expression)是一种字符串匹配的模式(规则)——超市里挑水果的攻略!

也称为规则表达式,是种字符串的操作工具!语法格式为一组特殊字符构成的匹配模式!匹配字符!是程序语言的一种通用工具,许多语言都支持,就是为了方便验证输入的字符是否合规!

使用场景:

  • 验证表单:用户只能输入英文字母、数字、下划线,昵称可以输入中文等匹配!

  • 例如验证表单:手机号表单要求用户只能输入11位的数字 (匹配)

  • 过滤掉页面内容中的一些敏感词(替换),或从字符串中获取我们想要的特定部分(提取)等

1676079666366

正则表达式原理

正则实例不用编译,正则字面量需要先编译,执行匹配时,是一个字符一个字符的匹配!

image-20230608163050237

正则基本使用

先是设置匹配规则,然后是利用方法处理字符,比如根据规则筛选出对应的字符!

定义规则——正则表达式

字面量形式,正则表达式也是对象

const reg =  /表达式/
  • 其中/ /是正则表达式字面量,输入空格也算是字符!

使用正则对象的方法

1、test()方法 用来查看正则表达式与指定的字符串是否匹配,是一个字符一个字符往下匹配!

  • 如果正则表达式与指定的字符串匹配 ,返回true,否则false
<body>
<script>
// 正则表达式的基本使用
const str = 'web前端开发'
// 1. 定义规则
const reg = /web/

// 2. 使用正则 test()
console.log(reg.test(str)) // true 如果符合规则匹配上则返回true
console.log(reg.test('java开发')) // false 如果不符合规则匹配上则返回 false
</script>
</body>

2、exec()方法,检索(查找)符合规则的字符串!

相当于在一个指定字符串中,执行一个搜索匹配,如果匹配成功,返回一个带键值数组,否则返回null

regObj.exec()

元字符

正则表达式中,书写的内容分成普通字符和元字符,普通字符是直接匹配,元字符是对普通字符的限定,或者某种匹配方法的简写!

  1. 普通字符:
  • 大多数的字符仅能够描述它们本身,这些字符称作普通字符,例如所有的字母和数字。
  • 普通字符只能够匹配字符串中与它们相同的字符。
  • 比如,规定用户只能输入英文26个英文字母,普通字符的话 /[abcdefghijklmnopqrstuvwxyz]/
  1. 元字符(特殊字符)
  • 是一些具有特殊含义的字符,可以极大提高了灵活性和强大的匹配功能。
  • 比如,规定用户只能输入英文26个英文字母,换成元字符写法: /[a-z]/

注意:

  1. 元字符是某些特殊含义字符,来表示某种匹配规则,边界符(开头和结尾的规则),量词(重复次数),字符类(特殊含义)
  2. 是一个字符一个字符的匹配的,每个字符前面或者后面可以加限定符!

边界符

正则表达式中的边界符(位置符)用来提示字符所处的位置,主要有两个字符

67608008165

^放再字符的前面,表示以什么字符开始,$放在字符的后面,表示以什么字符结尾!

如果 ^ 和 $ 在一起,表示必须是精确匹配,只有正则表达式内的字符串!^二哈$

<body>
<script>
// 元字符之边界符
// 1. 匹配开头的位置 ^
const reg = /^web/
console.log(reg.test('web前端')) // true
console.log(reg.test('前端web')) // false
console.log(reg.test('前端web学习')) // false
console.log(reg.test('we')) // false

// 2. 匹配结束的位置 $
const reg1 = /web$/
console.log(reg1.test('web前端')) // false
console.log(reg1.test('前端web')) // true
console.log(reg1.test('前端web学习')) // false
console.log(reg1.test('we')) // false

// 3. 精确匹配 ^ $
const reg2 = /^web$/
console.log(reg2.test('web前端')) // false
console.log(reg2.test('前端web')) // false
console.log(reg2.test('前端web学习')) // false
console.log(reg2.test('we')) // false
console.log(reg2.test('web')) // true
console.log(reg2.test('webweb')) // flase
</script>
</body>

量词

量词用来设定某个模式出现的次数,比如让只能输入数字,只能出现6次,用于匹配验证码!

67608018538

放在字符的后面,表示这个字符出现0次及以上,当配合精确匹配时,表示可以没有,或者仅有!

{n,m}

放在字符的后面,表示这个字符出现n次到m次!

注意: 逗号左右两侧千万不要出现空格

<body>
<script>
// 元字符之量词
// 1. * 重复次数 >= 0 次
const reg1 = /^w*$/
console.log(reg1.test('')) // true
console.log(reg1.test('w')) // true
console.log(reg1.test('ww')) // true
console.log('-----------------------')

// 2. + 重复次数 >= 1 次
const reg2 = /^w+$/
console.log(reg2.test('')) // false
console.log(reg2.test('w')) // true
console.log(reg2.test('ww')) // true
console.log('-----------------------')

// 3. ? 重复次数 0 || 1
const reg3 = /^w?$/
console.log(reg3.test('')) // true
console.log(reg3.test('w')) // true
console.log(reg3.test('ww')) // false
console.log('-----------------------')


// 4. {n} 重复 n 次
const reg4 = /^w{3}$/
console.log(reg4.test('')) // false
console.log(reg4.test('w')) // flase
console.log(reg4.test('ww')) // false
console.log(reg4.test('www')) // true
console.log(reg4.test('wwww')) // false
console.log('-----------------------')

// 5. {n,} 重复次数 >= n
const reg5 = /^w{2,}$/
console.log(reg5.test('')) // false
console.log(reg5.test('w')) // false
console.log(reg5.test('ww')) // true
console.log(reg5.test('www')) // true
console.log('-----------------------')

// 6. {n,m} n =< 重复次数 <= m
const reg6 = /^w{2,4}$/
console.log(reg6.test('w')) // false
console.log(reg6.test('ww')) // true
console.log(reg6.test('www')) // true
console.log(reg6.test('wwww')) // true
console.log(reg6.test('wwwww')) // false

// 7. 注意事项: 逗号两侧千万不要加空格否则会匹配失败

</script>

字符类——范围

表示字符的范围,定义的规则限定在某个范围,比如只能是英文字母,或者数字等等,用表示范围内任一字符!

67608029616

[]匹配字符集合

只要待匹配的字符串中,包含[]内任一字符,都返回true!通常用来限定输入字符的类型!

注意:

  1. 如果添加精确匹配,待匹配的字符串只能是[]其中一个!
  2. 如果添加精确匹配,再加上量词,待匹配的每一个字符,只能从[]选,可以出现N次!

案例:只能输入英文字母加数字

// 限定一个字符时
/^[A-Za-z0-9]$/.test('输入的字符')
// 限定n个字符时
/^[A-Za-z0-9]{4,6}$/.test('输入的字符')

QQ号限定

/^[1-9][0-9]{4,16}$/ 
  1. 只能是1到9开头
  2. 由于是精确匹配,从第二位开始,每个字符必须是0到9的数字(去掉$,则可输入中文或者字母)
  3. 输入5到17位字符

[^]

[^]内写^,表示对选择除了范围之外,取反[^0-9]:除了0到9的字符!

<body>
<script>
// 元字符之范围 []
// 1. [abc] 匹配包含的单个字符, 多选1
const reg1 = /^[abc]$/
console.log(reg1.test('a')) // true
console.log(reg1.test('b')) // true
console.log(reg1.test('c')) // true
console.log(reg1.test('d')) // false
console.log(reg1.test('ab')) // false

// 2. [a-z] 连字符 单个
const reg2 = /^[a-z]$/
console.log(reg2.test('a')) // true
console.log(reg2.test('p')) // true
console.log(reg2.test('0')) // false
console.log(reg2.test('A')) // false
// 想要包含小写字母,大写字母 ,数字
const reg3 = /^[a-zA-Z0-9]$/
console.log(reg3.test('B')) // true
console.log(reg3.test('b')) // true
console.log(reg3.test(9)) // true
console.log(reg3.test(',')) // flase

// 用户名可以输入英文字母,数字,可以加下划线,要求 6~16位
const reg4 = /^[a-zA-Z0-9_]{6,16}$/
console.log(reg4.test('abcd1')) // false
console.log(reg4.test('abcd12')) // true
console.log(reg4.test('ABcd12')) // true
console.log(reg4.test('ABcd12_')) // true

// 3. [^a-z] 取反符
const reg5 = /^[^a-z]$/
console.log(reg5.test('a')) // false
console.log(reg5.test('A')) // true
console.log(reg5.test(8)) // true

</script>
</body>

预定义类——推荐使用

某些常见模式的简写方式,区分字母和数字

67608035363

67608037232

解释:

  1. 精确匹配
  2. 前4个字符是数字
  3. 第5个字符,是-
  4. 接下来可以输入一个或者两个数字
  5. 接下来输入-
  6. 接下来可以输入一个或者两个数字

总结:一个字符一个字符去看,看看这个字符都带有什么样的限定或者范围规则!

案例:如果想输入中文1-10个字符作为名字

/^[\S\W]{1,10}$/

|

//内可以写|,来多个匹配条件,或

替换和修饰符

修饰符

约束正则表达式,在执行中的细节行为,是否区分大小写,是否匹配全部

放在正则表达式后面/^[\S\W]$/gi

字符类 说明
i 单词ignore的缩写,匹配时,不区分大小写
g 单词global的缩写,匹配所有满足(全局查找)

字符串替换方法

replace 替换方法,可以完成字符的替换,使用正则表达式去寻找对应字符串,替换成其它文本

  • 语法:字符串.replace(换下内容,换上内容)
  • 返回值:替换好的字符串

image-20230608182813982

<body>
<script>
// 替换和修饰符
const str = '欢迎大家学习前端,相信大家一定能学好前端,都成为前端大神'
// 1. 替换 replace 需求:把前端替换为 web
// 1.1 replace 返回值是替换完毕的字符串
// const strEnd = str.replace(/前端/, 'web') 只能替换一个
</script>
</body>

修饰符约束正则执行的某些细节行为,如是否区分大小写、是否支持多行匹配等

  • i 是单词 ignore 的缩写,正则匹配时字母不区分大小写
  • g 是单词 global 的缩写,匹配所有满足正则表达式的结果
<body>
<script>
// 替换和修饰符
const str = '欢迎大家学习前端,相信大家一定能学好前端,都成为前端大神'
// 1. 替换 replace 需求:把前端替换为 web
// 1.1 replace 返回值是替换完毕的字符串
// const strEnd = str.replace(/前端/, 'web') 只能替换一个

// 2. 修饰符 g 全部替换
const strEnd = str.replace(/前端/g, 'web')
console.log(strEnd)
</script>
</body>

案例:表单验证

1、获取验证码

// 分析
// 点击时获取,发生倒计时,并且倒计时结束后,才能重新获取
// 1. 获取元素,给链接元素绑定点击事件
const code = document.querySelector('.xtx-form-item .code')
// 1.1 补充个节流的
let todo = true
code.addEventListener('click', function () {
if(todo){
todo = false
let i = 5
code.innerHTML = `0${i}后再获取`
// 1.2倒计时
const timerId = setInterval(function () {
i--
code.innerHTML = `0${i}后再获取`
// 1.3 结束倒计时,才能重新点击获取
if(i === 0){
code.innerHTML = `重新获取`
clearInterval(timerId)
todo = true
}
},1000)
}
})

2、验证表单

补充:change事件:当填写的表单,内容发生改变时,才会触发事件

input.addEventListener('change', function () {

})
  1. 其它表单

复制,粘贴,修改局部规则!

  1. 验证密码时,改为两个控件值相同判断

  2. 同意的处理思路

    • 如果使用自带的checkbox,通过属性checked判断是否勾选上
    • 如果使用类名属性,来设置切换效果,通过classList.toggle()方法增删类名,并通过classList.contains()方法,来判断是否选择我同意!
// classList.contains()看元素是否包含类,如果有则返回true ,没有则返回false
  1. 由于把验证表单的事件,全部封装成函数,当调用时,执行函数,并返回true 或 false
  2. 再给每个验证表单的函数都调用,并判断执行结构,并根据结果来阻止表单提交,全部都通过,表单提交才不会被阻止,也就是提交按钮也能实现验证表单,并需要全部通过才行!
  3. 非常棒的思路!
  4. 如果只是设置flag=true来判断,只能在输入时才能验证表单,点击提交(如果任一不通过就阻止提交)无任何反应!
const password = document.querySelector('[name="password"]')
password.addEventListener('change',verifypassword)
function verifypassword() {
// 2.1 定义规则
const reg = /^1(3\d|4[5-9]|5[0-35-9]|6[567]|7[0-8]|8\d|9[0-35-9])\d{8}$/
// 2.2 另一种写法
if(!reg.test(password.value)){
password.nextElementSibling.innerHTML = '请输入6-12位字母数字下画线的名字'
return false
}
password.nextElementSibling.innerHTML = ''
return true
}
const confirm = document.querySelector('[name="confirm"]')
confirm.addEventListener('change',verifyconfirm)
function verifyconfirm() {
// 2.2 另一种写法
if(confirm.value !== password.value){
confirm.nextElementSibling.innerHTML = '请输入6-12位字母数字下画线的名字'
return false
}
confirm.nextElementSibling.innerHTML = ''
return true
}
const queren = document.querySelector('.icon-queren')
queren.addEventListener('click',verifyqueren)
function verifyqueren() {
queren.classList.toggle('icon-queren2')
}

const form = document.querySelector('form')
form.addEventListener('submit',function (e) {
if(!queren.classList.contains('icon-queren2')){
alert('请先勾上我同意')
e.preventDefault()
}
// 点击提交,全部通过才行
if(!verifyname()) e.preventDefault()
if(!verifyphone()) e.preventDefault()
if(!verifypassword()) e.preventDefault()
if(!verifyconfirm()) e.preventDefault()
})

3、登录页面

  1. 切换下化线
  2. 切换盒子,显示和隐藏
// 1. 点击出现下画线,事件委托
const tab_nav = document.querySelector('.tab-nav')
// 2. 显示对应的那个盒子
const panes = document.querySelectorAll('.tab-pane')
tab_nav.addEventListener('click', function (e) {
if(e.target.tagName === 'A'){
// 找到在tab_nav下的带有active类名的元素,去掉类名,再给当前点击的添加类名
tab_nav.querySelector('.active').classList.remove('active')
e.target.classList.add('active')
// 先隐藏全部盒子,再显示当前点击的盒子
for(let i = 0; i < panes.length; i++){
panes[i].style.display = 'none'
}
panes[e.target.dataset.id].style.display = 'block'
}
})

// 勾选我同意,注意是给表单绑定提交事件,而不是按钮点击!
const remember = document.querySelector('.remember')
const form = document.querySelector('form')
// 存值,跳转
const uname = document.querySelector('[name=username]')
form.addEventListener('submit', function (e) {
e.preventDefault()
if(!remember.checked){
return alert('请先勾选上我同意')
}
// 存入名字
localStorage.setItem('xtx-name', uname.value)
// 跳转到首页
location.href = './index.html'
})

4、退出登录

  1. 渲染
  2. 删除数据,重新渲染
const li1 = document.querySelector('.xtx_navs li:first-child')
const li2 = li1.nextElementSibling
function getname() {
// 每次点击按钮,都去查询一遍,有没有值
const uname = localStorage.getItem('xtx-name')
if(uname){
li1.innerHTML = `<a href="javascript:;"><i class="iconfont icon-user"> ${uname}</i></a>`
li2.innerHTML = '<a href="javascript:;">退出登录</a>'
} else {
li1.innerHTML = '<a href="./login.html">请先登录</a>'
li2.innerHTML = '<a href="javascript:;">退出登录</a>'
}
}
// 一开始,利用函数去渲染生成名字
getname()
// 点击时,利用函数再次渲染生成登录
li2.addEventListener('click', function () {
// 可以添加判断,当存在时才删除,可以不添加
localStorage.removeItem('xtx-name')
getname()
})

补充:

正则插件

1676080548639

change 事件

给input注册 change 事件,值被修改并且失去焦点后触发

判断是否有类

1676080618794

元素.classList.contains() 看看有没有包含某个类,如果有则返回true,么有则返回false

总结:

  1. 不设置严格匹配时,主要功能是用来查找有没有,是否存在某些字符(不限制类型)
  2. 设置严格模式下,可以对一串字符,一个字符一个字符去设置按顺序去严格匹配
  3. 可以通过范围给每一个字符设置一种匹配规则,也可以通过量词让多个字符使用一种规则
  4. 边界符用于设置字符开头的一个字符,是什么,或者是什么范围
/^1[0-9]{10}$/

// 这就是普通人的电话,第一位是1开头,后面的10位数字,每个数字是0-9的范围内!
  1. 规则是一个字符,一个字符的去设置,特别是在精确模式下
  2. 非精确模式,是查找是否存在,因为是一长串字符串中,存在一个满足条件即可
/你好/
// 代表着,一长串字符中存在‘你好’这连着的两个字,就行!比如'就你好家伙'

/^你好$/
// 代表着,一长串字符必须由‘你好’构成,比如'你好你好你好你好你好'
  1. 如果不需要的话,千万不要有空格,空格也会被解析成一个字符
  2. / /正则字面量,里面的字符是一个一个数,比如/^12$/,里面匹配了两个字符,第一个必须是1,第二个字符必须是2,且就只能匹配两个字符
  3. 再比如/^[0-5][2-4]$/,里面匹配了两个字符,第一个字符必须是0到5之间的一个,第二个字符必须是2到4之间的一个,且就只能匹配两个字符
  4. 还比如/^[0-5]{5}]$/,里面匹配了个字符,且每个字符必须是0到5之间的某一个!

Web APIs - 第7天笔记——个人实战文档

参考效果如下地址:http://erabbit.itheima.net/#/product/3995139

本次实战主要分为以下几个模块。

顶部导航模块

需求:

  1. 顶部导航开始不显示
  2. 等页面滑到主导航栏,这个新顶部导航栏滑动下拉显示,并且改为固定定位
  3. 等页面滑到上面,新顶部导航栏隐藏

分析:

  1. 初始隐藏,由于等下是下拉显示,所以隐藏方式是,上边距为负数,盒子往上推隐藏在页面之外
  2. 选个盒子作为限位,使用滚动事件,移到限位盒子的高度时,让导航栏上边距为0

图片切换模块

分析:

  1. 鼠标经过事件,让自己加边框,让中盒子,换上当前图片!
  2. 小盒子很多,可以设置事件委托,这样就比较简洁,具有鼠标移入的,且有冒泡的事件是mouseovermouseout 会有冒泡效果,设置鼠标移入!中盒子图片的链接是小盒子图片

放大镜效果

个人分析:

分析:右边大盒子哪儿来的?

得出:当鼠标移动到中盒子,出现的!

分析:左边中盒子中,遮罩怎么来的?

得出:鼠标移入显示的,说明一个事件,触发了两个行为!

分析:左边中盒子中的遮罩为什么可以移动?

得出:由于是绝对定位,位置由父元素的定位,再加上top,left等决定,父元素为中盒子,改变CSS

分析:遮罩移动依据呢,跟着谁一起动,二者关系?

得出:跟着鼠标一起动,鼠标动1像素,遮罩动1像素,且要不断触发事件,不断更新位置,所以是鼠标的移动事件!也就是mousemove,这个事件是鼠标移动1像素,就触发一个事件,调用响应函数去获取鼠标位置

分析:求鼠标位置,相对中盒子左上角的距离是多少?

把距离设置成(x,y),鼠标的位置可以多种方式获取,通过事件触发时,当前鼠标的位置,也就是鼠标移动事件中,鼠标的位置,查找到三种方法

  1. e.clientX/Y 光标相对浏览器窗口的位置
  2. e.offsetX/Y 光标相于当前 DOM 元素的位置
  3. e.pageX/Y光标相对于页面的位置

经过测试,虽然第2种最简单,但实际效果易出BUG!第1、3种都可以,这里重点推荐相对于窗口的位置,也就是第1种,正好可以与中盒子的条件一致,middle.getBoundingClientRect().x,则计算x=鼠标的可视窗口距离-中盒子的可视窗口距离,y同理可得!

分析:求遮罩移动范围,与鼠标移动的关系

如果遮罩的左上角与鼠标一致:那么移动范围就是中盒子的大小!0<x<4000<y<400

但是遮罩的中心与鼠标一致:那么范围就缩小到:100<x<300,或者100<y<300,满足这两者其中之一便可移动,并且鼠标移动范围x<100时,不移动遮罩,x>300时,也不移动遮罩!y同理!

遮罩移动时,遮罩的中心与鼠标一致,也就意味,遮罩相对鼠标,向左移动100px(一半),上移100px也就是(xpx)+(-100px)展开是x-100y-100,注意隐式转化为字符串!才能设置给CSS!

if(x < 100){
layer.style.left = '0px'
}else if(x > 300){
layer.style.left = '200px'
}
// 条件:仅鼠标移动到遮罩中心以内的区域,遮罩才能跟随移动!初始设置:遮罩位置移到鼠标中心100px
else{
layer.style.left = x-100 + 'px'
}
// 这里把X和Y分别移动,互不干扰!x不移动时y还可以移动,同理y不移动,x也可以移动!
if(y < 100){
layer.style.top = '0px'
}else if(y > 300){
layer.style.top = '200px'
}
else{
layer.style.top = y-100 + 'px'
large.style.backgroundPositionY = -2*y+200 + 'px'
}

分析:右侧大盒子显示图片

右侧大盒子图片,也是在鼠标移入中盒子时,显示小盒子中的图片,在前几步中,添加显示图片!

然后是跟随移动,大盒子照片通过背景方式,插入到大盒子更简单,并且设置成800乘800px大小,2倍3倍都是可以的!

分析:大盒子图片跟随谁移动,与谁有关系

依旧是找关系,这个背景图片移动,与谁相关?当然与左侧中盒子中鼠标移动,更具体讲是遮罩移动相关,也就是遮罩移动时,才移动,也就是100<x<300,或者100<y<300时,既然与遮罩一致,就跟遮罩移动放在一起动

分析:大盒子图片怎么移动,和遮罩移动时的具体关系

由于图片显示是大盒子不动,图动,那么图要朝相反方向移动,在盒子内才能展示一致,想象一张完整的地图摊平在桌子上,拿个放大镜移动查看地图任一地方,现在要求放大镜不动,地图朝相反运动

由于图大小设置是2倍关系,那么就是朝着相反方向的2倍,-(x-100)*2展开就是,-2x +200 px

另一种分析:

业务分析:

①:鼠标经过对应小盒子,左侧中等盒子显示对应中等图片

②: 鼠标经过中盒子,右侧会显示放大镜效果的大盒子

③: 黑色遮罩盒子跟着鼠标来移动

④: 鼠标在中等盒子上移动,大盒子的图片跟着显示对应位置

思路分析:

①:鼠标经过小盒子,左侧中等盒子显示对应中等图片

  1. 获取对应的元素
  2. 采取事件委托的形式,监听鼠标经过小盒子里面的图片, 注意此时需要使用 mouseover 事件,因为需要事件冒泡触发small
  3. 让鼠标经过小图片的爸爸li盒子,添加类,其余的li移除类(注意先移除,后添加)
  4. 鼠标经过小图片,可以拿到小图片的src, 可以做两件事
    • 让中等盒子的图片换成这个 这个小图片的src
    • 让大盒子的背景图片,也换成这个小图片的 src (稍后做)

②: 鼠标经过中等盒子,右侧大盒子显示

  1. 用到鼠标经过和离开,鼠标经过中盒子,大盒子 利用 display 来显示和隐藏

  2. 鼠标离开不会立马消失,而是有200ms的延时,用户体验更好,所以尽量使用定时器做个延时 setTimeout

  3. 显示和隐藏也尽量定义一个函数,因为鼠标经过离开中等盒子,会显示隐藏,同时,鼠标经过大盒子,也会显示和隐藏

  4. 给大盒子里面的背景图片一个默认的第一张图片

③: 黑色遮罩盒子跟着鼠标来移动

  1. 先做鼠标经过 中等盒子,显示隐藏 黑色遮罩 的盒子

  2. 让黑色遮罩跟着鼠标来走, 需要用到鼠标移动事件 mousemove

  3. 让黑色盒子的移动的核心思想:不断把鼠标在中等盒子内的坐标给黑色遮罩层 let top 值,这样遮罩层就可以跟着移动了

    • 需求

      • 我们要的是 鼠标在 中等盒子内的坐标, 没有办法直接得到
      • 得到1: 鼠标在页面中的坐标
      • 得到2: 中等盒子在页面中的坐标
    • 算法

      • 得到鼠标在页面中的坐标 利用事件对象的 pageX
      • 得到middle中等盒子在页面中的坐标 middle.getBoundingClientRect()
      • 鼠标在middle 盒子里面的坐标 = 鼠标在页面中的坐标 - middle 中等盒子的坐标
      • 黑色遮罩层不断得到 鼠标在middle 盒子中的坐标 就可以移动起来了

      注意 y坐标特殊,需要减去 页面被卷去的头部

      为什么不用 box.offsetLet 和 box.offsetTop 因为这俩属性跟带有定位的父级有关系,很容被父级影响,而getBoundingClientRect() 不受定位的父元素的影响

    • 限定遮罩的盒子只能在middle 内部移动,需要添加判断

      • 限定水平方向 大于等于0 并且小于等于 400
      • 限定垂直方向 大于等于0 并且小于等于 400
    • 遮罩盒子移动的坐标:

      • 声明一个 mx 作为移动的距离
      • 水平坐标 x 如果 小于等于100 ,则移动的距离 mx 就是 0 不应该移动
      • 水平坐标 如果 大于等于100 并且小于300,移动的距离就是 mx - 100 (100是遮罩盒子自身宽度的一半)
      • 水平坐标 如果 大于等于300,移动的距离就是 mx 就是200 不应该在移动了
      • 其实我们发现水平移动, 就在 100 ~ 200 之间移动的
      • 垂直同理
let mx = 0, my = 0;
if (x <= 100) mx = 0
if (x > 100 && x < 300) mx = x - 100
if (x >= 300) mx = 200

if (y <= 100) my = 0
if (y > 100 && y < 300) my = y - 100
if (y >= 300) my = 200
  • 大盒子图片移动的计算方法:
    • 中等盒子是 400px 大盒子 是 800px 的图片
    • 中等盒子移动1px, 大盒子就应该移动2px, 只不过是负值
large.style.backgroundPositionX = - 2 * mx + 'px'
large.style.backgroundPositionY = - 2 * my + 'px'

放大镜完整代码:

 const small = document.querySelector('.small')
const middle = document.querySelector('.middle')
const large = document.querySelector('.large')
const fstImg = small.querySelector('img')
large.style.backgroundImage = `url(${fstImg.src})`
// 1. 通过事件委托,给图片绑定鼠标移入事件
small.addEventListener('mouseover', function (e) {
if(e.target.tagName === 'IMG'){
// 1.1 添加边框样式
this.querySelector('.active').classList.remove('active')
e.target.parentNode.classList.add('active')
// 1.2 中盒子中显示图片
middle.children[0].src = e.target.src
large.style.backgroundImage = `url(${e.target.src})`
}
})

// 2. 鼠标经过中盒子,显示大盒子
middle.addEventListener('mouseenter', show)
middle.addEventListener('mouseleave', hide)
// 2.1 防止事件流中,太快速的移动导致该show时,hide还没结束,等hide结束时隐藏,需先结束hide,再显示
let timerId = null
function show() {
clearTimeout(timerId)
large.style.display = 'block'
}
function hide() {
timerId = setTimeout(function () {
large.style.display = 'none'
}, 200)
}

// 3.鼠标经过大盒子,显示和隐藏
large.addEventListener('mouseenter', show)
large.addEventListener('mouseleave', hide)

// 4. 鼠标在中盒子中移动,显示遮罩
const layer = document.querySelector('.layer')
middle.addEventListener('mouseenter', function () {
layer.style.display = 'block'
})
middle.addEventListener('mouseleave', function () {
layer.style.display = 'none'
})

// 5. 移动遮罩
middle.addEventListener('mousemove', function (e) {
// 5.1 计算出,鼠标相对于中盒子的坐标
// let x = e.pageX - middle.getBoundingClientRect().x
// let y = e.pageY - middle.getBoundingClientRect().y - document.documentElement.scrollTop

let x = e.clientX - middle.getBoundingClientRect().x
let y = e.clientY - middle.getBoundingClientRect().y
// 5.2 仅当在盒子内,遮罩才动
if(x > 0 && x < 400 && y > 0 && y < 400){
if(x < 100){
layer.style.left = '0px'
}else if(x > 300){
layer.style.left = '200px'
}
// 条件:仅鼠标移动到遮罩中心以内的区域,遮罩才能跟随移动!初始设置:遮罩位置移到鼠标中心100px
else{
layer.style.left = x-100 + 'px'
// 大盒子跟随移动,两倍图大小,那么两倍移动速度,初始位置为鼠标中心200px
large.style.backgroundPositionX = -2*x+200 + 'px'
}
if(y < 100){
layer.style.top = '0px'
}else if(y > 300){
layer.style.top = '200px'
}
else{
layer.style.top = y-100 + 'px'
large.style.backgroundPositionY = -2*y+200 + 'px'
}

}
})

其他模块

此模块可以根据自己时间添加

点击模块

分析:

两兄弟,控制变量自增自减,给这两兄弟绑定点击事件,并渲染变量的值,出现在中间的盒子中

tab栏切换模块

分析:

所有这种类似切换的,都可以看成是多层盒子叠加在一起,点击周围盒子或者按钮,控制对应盒子显示和隐藏!可以是透明度,可以是消失!

返回顶部模块

页面滚动底部,可以出现一个侧边栏,点击返回顶部,可以返回顶部

分析:

简单,给按钮绑定点击事件,控制页面移动scrollTop属性,可读写,赋值就能到卷去的高度!

总结:

  1. 无非让某些盒子触发事件,再让某些盒子发生一些HTML或者CSS的增删改!

  2. 所以分析时,可以独立成两个方面去分析,再综合之间的联系盒子间的关系,把共同关系设置成函数,当事件发生时,再调用这个函数就是!

  3. 选择合适的事件

  4. 除了事件源可能与响应函数中,待改变的有关系,更重要的是要抽丝破茧,去分析,需要完成的效果中,不同元素之间的关系,这有涉及到HTML结构和CSS的初始样式!这三者是相互影响的!

  5. 怎么查看事件中,响应函数中,需要完成的效果中,哪些元素有关系,可以按照一个事件触发一个行为这样,简单直接的分析,逐步越挖越深

  6. 元素数字间关系,或者相互影响时,之间的关系,每次只找一层,每层只找两种一种关系!比如在放大镜中,左边中盒子中遮罩移动,与右边大盒子图片移动的关系!先分清除,具体是哪个盒子与哪个盒子之间的关系,这里先是鼠标与遮罩的关系,然后是遮罩与大盒子图片的关系,也可以看成是小遮罩与大盒子是一一对应的关系,1:2放大!实际上是图片移动,那么就是图片与遮罩方向相反的关系!

  7. 先看鼠标与遮罩这一层,这一层中,遮罩是相对中盒子移动,鼠标是整个可视窗口移动,求出鼠标在中盒子移动时,相对于中盒子的距离!

  8. 再忽视鼠标与遮罩,只看遮罩与图片的关系,当遮罩移动1像素,图片朝相反方向移动2像素!

  9. 难点是找到(x,y)相对位置后,遮罩在这个范围的不同情况,可以分成不同条件下移动不同距离!两种方式:

    一种是找到这种新关系设置成(mx,my),不同情形下,对应给(mx,my)分配不同值!(简单)

    二是直接判断,(x,y)与遮罩位置,逻辑上的关系!逻辑上是X在[0,400]必须移动,Y在[0,400]必须移动,并且X与Y互不干扰,互不干扰是多个独立分支,X在[0,400]必须移动可以看成是逻辑上的多重分支,必选其一执行,然后把[0,400]分成三种不同情形,分别执行不同的结构就是!(需要逻辑强,语法结构熟)