目录
1. 小兔鲜注册页面验证
2. 阶段案例(登录、首页页面)(重点)
3. 小兔鲜放大镜效果
1. 小兔鲜注册页面验证
验证码模块有个小问题:
连续点击获取验证码会导致触发多次计时器,会导致计时出现问题,为了解决这个问题,可以设置一个变量来控制。如 flag = true 当触发点击事件时,首先判断 flag,为真则进入函数,否则不执行,进入函数后首先将 flag取反,然后当计时结束时,再设置 flag = true.
侦听使用 change 事件,当鼠标离开了表单,并且表单值发生了变化时触发
对于同意模块使用的是小图标,而不是单选多选框,勾选的话应该为 i标签再添加一个类icon-queren2
classList.contains( ) 看看有没有包含某个类,如果有则返回 true,没有则返回false
<script>
// 使用立即执行函数,避免变量污染
(function(){
// 1.发送短信验证码模块
const code = document.querySelector('.xtx-form a.code')
let flag = true //通过一个变量来控制
// 1.1 点击事件
code.addEventListener('click', function(){
if (flag) {
// 取反了,不能马上第二次点击
flag = false
let i = 5
code.innerHTML = `0${i}s后重新获取`
let timer = setInterval(function(){
i--
code.innerHTML = `0${i}s后重新获取`
if (i === 0) {
clearInterval(timer)
code.innerHTML = `重新获取`
// 到时间了,可以开启 flag了
flag = true
}
}, 1000)
}
})
})();
// 2. 验证用户名
// 2.1 获取用户名表单
const username = document.querySelector('.xtx-form [name=username]')
// 2.2 使用change 事件 值发生变化的时候
username.addEventListener('change', verifyName)
// 2.3 封装函数
function verifyName() {
// console.log(00);
const span = username.nextElementSibling
// 2.4 定规则 用户名
const reg = /^[a-zA-Z0-9-_]{6,10}$/
if (!reg.test(username.value)) {
span.innerHTML = '用户名输入有误,请输入6~10位'
return false
}
// 2.5 合法的 就清空 span
username.nextElementSibling.innerHTML = ''
return true
}
// 3. 验证手机号
// 3.1 获取手机号表单
const phone = document.querySelector('.xtx-form [name=phone]')
// 3.2 使用change 事件 值发生变化的时候
phone.addEventListener('change', verifyPhone)
// 3.3 封装函数
function verifyPhone() {
// console.log(00);
const span = phone.nextElementSibling
// 3.4 定规则 手机号
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}$/
if (!reg.test(phone.value)) {
span.innerHTML = '输入有误,请输入正确的手机号'
return false
}
// 3.5 合法的 就清空 span
phone.nextElementSibling.innerHTML = ''
return true
}
// 4. 验证验证码
// 4.1 获取验证码表单
const code = document.querySelector('.xtx-form [name=code]')
// 4.2 使用change 事件 值发生变化的时候
code.addEventListener('change', verifyCode)
// 4.3 封装函数
function verifyCode() {
// console.log(00);
const span = code.nextElementSibling
// 4.4 定规则 验证码
const reg = /^\d{6}$/
if (!reg.test(code.value)) {
span.innerHTML = '输入有误,请输入正确的验证码'
return false
}
// 4.5 合法的 就清空 span
code.nextElementSibling.innerHTML = ''
return true
}
// 5. 验证密码
// 5.1 获取密码表单
const password = document.querySelector('.xtx-form [name=password]')
// 5.2 使用change 事件 值发生变化的时候
password.addEventListener('change', verifyPassword)
// 5.3 封装函数
function verifyPassword() {
// console.log(00);
const span = password.nextElementSibling
// 5.4 定规则 密码
const reg = /^[a-zA-Z0-9-_]{6,20}$/
if (!reg.test(password.value)) {
span.innerHTML = '输入有误,6~20位数字字母符号组成'
return false
}
// 5.5 合法的 就清空 span
password.nextElementSibling.innerHTML = ''
return true
}
// 6. 密码的再次验证
// 6.1 获取再次验证密码表单
const confirm = document.querySelector('.xtx-form [name=confirm]')
// 6.2 使用change 事件 值发生变化的时候
confirm.addEventListener('change', verifyConfirm)
// 6.3 封装函数
function verifyConfirm() {
// console.log(00);
const span = confirm.nextElementSibling
// 当前表单的值不等于密码框的值就是错误的
if (confirm.value !== password.value) {
span.innerHTML = '两次输入密码不一致'
return false
}
// 6.4 合法的 就清空 span
confirm.nextElementSibling.innerHTML = ''
return true
}
// 7. 我同意模块
const agree = document.querySelector('.xtx-form-item .icon-queren')
agree.addEventListener('click', function(){
// 切换类,原来有就删掉,原来没有就添加
this.classList.toggle('icon-queren2')
})
// 8. 表单提交模块
const form = document.querySelector('form')
form.addEventListener('submit', function(e){
// 判断是否勾选我同意模块,如果有 icon-queren2 说明就勾选了,否则就没勾选
if(!agree.classList.contains('icon-queren2')) {
alert('请勾选同意协议')
// 阻止提交
e.preventDefault()
}
// 依次判断上面的每个框框是否通过, 只要有一个没有通过的就阻止
// console.log(verifyName());
if (!verifyName()) e.preventDefault()
if (!verifyPhone()) e.preventDefault()
if (!verifyCode()) e.preventDefault()
if (!verifyPassword()) e.preventDefault()
if (!verifyConfirm()) e.preventDefault()
})
</script>
2. 阶段案例(登录、首页页面)(重点)
小兔鲜登录页面
tab栏切换时,由于对应的 .tab-pane两个盒子没有设置 active类,所以最好不要为其设置active类,不能使用排他思想,故可将所有的 .tab-pane获得,将其隐藏起来(for循环),然后再为对应的盒子设置显示。
h5新属性 required属性 表示不能为空
<script>
// 1.tab栏切换
const tab_nav = document.querySelector('.tab-nav')
const pane = document.querySelectorAll('.tab-pane')
// 1.1事件监听,使用事件委托
tab_nav.addEventListener('click', function(e){
if (e.target.tagName === 'A') {
// 排他思想,去掉上一个active
tab_nav.querySelector('.active').classList.remove('active')
// 当前元素添加active
e.target.classList.add('active')
// 先干掉所有人,for循环
for(let i = 0; i < pane.length; i++) {
pane[i].style.display = 'none'
}
// 让对应序号的 大pane显示
pane[e.target.dataset.id].style.display = 'block'
}
})
// 2. 点击登录跳转页面
const form = document.querySelector('form')
const agree = form.querySelector('[name=agree]')
const username = form.querySelector('[name=username]')
form.addEventListener('submit', function(e){
e.preventDefault()
// 判断是否勾选同意协议
if(!agree.checked){
return alert('请勾选同意协议')
}
// 将用户名记录到本地存储中
localStorage.setItem('xtx-uname', username.value)
// 跳转到首页
location.href = 'index.html'
})
</script>
小兔鲜首页页面
注意在渲染的时候渲染的是小 li 而不是 a ,因为当显示用户名时会切换链接地址和添加小图标,同时对于第二个是退出登录还是请先注册的链接地址也不一样,故给 li 的innerHTML渲染。
<script>
// 1. 获取li 元素
const li1 = document.querySelector('.xtx_navs li:first-child')
const li2 = li1.nextElementSibling
// 2.最好做个渲染函数
function render() {
// 2.1 读取本地存储的用户名
const uname = localStorage.getItem('xtx-uname')
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="./register.html">免费注册</a>`
}
}
render() //调用函数
// 2. 点击退出登录
li2.addEventListener('click', function(){
// 删除本地存储的数据
localStorage.removeItem('xtx-uname')
// 重新渲染
render()
})
</script>
3. 小兔鲜放大镜效果
业务分析:
①:鼠标经过对应小盒子,左侧中等盒子显示对应中等图片
②: 鼠标经过中盒子,右侧会显示放大镜效果的大盒子
③: 黑色遮罩盒子跟着鼠标来移动
④: 鼠标在中等盒子上移动,大盒子的图片跟着显示对应位置
思路分析:
①:鼠标经过小盒子,左侧中等盒子显示对应中等图片
1. 获取对应的元素
2. 采取事件委托的形式,监听鼠标经过小盒子里面的图片, 注意此时需要使用 `mouseover` 事件,因为需要事件冒泡触发small,所以以后对于鼠标经过事件想用事件委托时使用 mouseover
3. 让鼠标经过小图片的爸爸li盒子,添加类,其余的li移除类(注意先移除,后添加)
4. 鼠标经过小图片,可以拿到小图片的src, 可以做两件事
- 让中等盒子的图片换成这个 这个小图片的src
- 让大盒子的背景图片,也换成这个小图片的 src (稍后做)
②: 鼠标经过中等盒子,右侧大盒子显示
1. 用到鼠标经过和离开,鼠标经过中盒子,大盒子 利用 display 来显示和隐藏
2. 鼠标离开不会立马消失,而是有200ms的延时,用户体验更好,所以尽量使用定时器做个延时 setTimeout
3. 显示和隐藏也尽量定义一个函数,因为鼠标经过离开中等盒子,会显示隐藏,同时,鼠标经过大盒子,也会显示和隐藏
4. 给大盒子里面的背景图片一个默认的第一张图片(去CSS样式中添加,因为在JS中写的大盒子背景是鼠标移入点击事件时把小盒子的图片地址给大盒子,但当页面一打开时,大盒子没有背景图片,此时移入中等盒子不会出现背景图片,有bug,所以我们应该给大盒子一个默认的第一张图片)
function hide() {
// 鼠标离开不会立马消失,而是有200ms的延时,使用setTimeout函数
setTimeout(function(){
large.style.display = 'none'
}, 250)
}
如果我们采用上面代码,会出现问题:鼠标一离开就开启一个定时器,所以当我们快速离开进入中等盒子时,会开启多个定时器,导致鼠标即使移入了中等盒子,但是由于定时器的隐藏效果会导致图片不显示。
因此,我们应该当鼠标进入中等盒子时,关掉定时器,采用如下写法:
let timer = null
function show() {
// 先清除定时器
clearTimeout(timer)
large.style.display = 'block'
}
// 隐藏函数 隐藏大盒子
function hide() {
// 鼠标离开不会立马消失,而是有200ms的延时,使用setTimeout函数
timer = setTimeout(function(){
large.style.display = 'none'
}, 250)
}
③: 黑色遮罩盒子跟着鼠标来移动
1. 先做鼠标经过 中等盒子,显示隐藏 黑色遮罩 的盒子
2. 让黑色遮罩跟着鼠标来走, 需要用到鼠标移动事件 mousemove
3. 让黑色盒子的移动的核心思想:不断把鼠标在中等盒子内的坐标给黑色遮罩层 let top 值,这样遮罩层就可以跟着移动了
- 需求
- 我们要的是 鼠标在 中等盒子内的坐标, 没有办法直接得到
- 得到1: 鼠标在页面中的坐标
- 得到2: 中等盒子在页面中的坐标
- 算法
- 得到鼠标在页面中的坐标 利用事件对象的 pageX
- 得到middle中等盒子在页面中的坐标 middle.getBoundingClientRect()
- 鼠标在middle 盒子里面的坐标 = 鼠标在页面中的坐标 - middle 中等盒子的坐标
- 黑色遮罩层不断得到 鼠标在middle 盒子中的坐标 就可以移动起来了
>注意 y坐标特殊,需要减去 页面被卷去的头部 ,因为getBoundingClientRect()获取的是相对于可视窗口的坐标,当页面滚动时,会出现问题
>为什么不用 box.offsetLeft 和 box.offsetTop 因为这俩属性跟带有定位的父级有关系,很容被父级影响,而getBoundingClientRect() 不受定位的父元素的影响
- 限定遮罩的盒子只能在middle 内部移动,需要添加判断
- 限定水平方向 大于等于0 并且小于等于 400
- 限定垂直方向 大于等于0 并且小于等于 400
通过console.log(middle.getBoundingClientRect());得到中等盒子的坐标如下图
- 遮罩盒子移动的坐标:
- 声明一个 mx 作为移动的距离
- 水平坐标 x 如果 小于等于100 ,则移动的距离 mx 就是 0 不应该移动
- 水平坐标 如果 大于等于100 并且小于300,移动的距离 mx就是 x - 100 (100是遮罩盒子自身宽度的一半)
- 水平坐标 如果 大于等于300,移动的距离就是 mx 就是200 不应该在移动了
- 其实我们发现水平移动, 就在 100 ~ 200 之间移动的
- 垂直同理
// 声明两个变量, 黑色盒子移动的距离
let mx = 0
let 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, 只不过是负值
~~~JavaScript
large.style.backgroundPositionX = - 2 * mx + 'px'
large.style.backgroundPositionY = - 2 * my + 'px'
<script>
// 顶部导航栏自动滑出
(function(){
const sticky = document.querySelector('.sticky')
const header = document.querySelector('.xtx_header .wrapper')
const headerTop = header.offsetTop
const headerHeight = header.clientHeight
const top = headerTop + headerHeight
window.addEventListener('scroll', function(){
const n = document.documentElement.scrollTop
if (n >= top) {
sticky.style.top = '0px'
} else {
sticky.style.top = '-80px'
}
})
})();
// 三个盒子的故事,放大镜效果
// 1. 获取三个盒子
const small = document.querySelector('.small')
const middle = document.querySelector('.middle')
const large = document.querySelector('.large')
// 2. 鼠标经过对应的小盒子,左侧中等盒子显示对应的图片
// 事件委托,注意必须使用mouseover,因为事件委托利用的是冒泡原理,而mouseenter不能冒泡
small.addEventListener('mouseover', function(e){
if (e.target.tagName === 'IMG') {
// 排他思想 干掉以前的 active li 上面
this.querySelector('.active').classList.remove('active')
// 当前元素的爸爸li 添加 active
e.target.parentNode.classList.add('active')
// console.log(middle.children[0]);
// 让中等盒子里面的图片,src 更换为 小图片src
middle.querySelector('img').src = e.target.src
// 大盒子更换背景图片,注意图片背景的属性值为 url()
large.style.backgroundImage = `url(${e.target.src})`
}
})
// 3. 鼠标经过中等盒子,显示隐藏大盒子
middle.addEventListener('mouseenter', show)
middle.addEventListener('mouseleave', hide)
// 显示函数 显示大盒子
let timer = null
function show() {
// 先清除定时器
clearTimeout(timer)
large.style.display = 'block'
}
// 隐藏函数 隐藏大盒子
function hide() {
// 鼠标离开不会立马消失,而是有200ms的延时,使用setTimeout函数
timer = setTimeout(function(){
large.style.display = 'none'
}, 250)
}
// 4. 鼠标经过大盒子,显示隐藏大盒子
large.addEventListener('mouseenter', show)
large.addEventListener('mouseleave', hide)
// 5. 做鼠标经过 中等盒子,显示隐藏 黑色遮罩 的盒子
const layer = document.querySelector('.layer')
middle.addEventListener('mouseenter', function(){
layer.style.display = 'block'
})
middle.addEventListener('mouseleave', function(){
layer.style.display = 'none'
})
// 6. 移动黑色遮罩盒子
middle.addEventListener('mousemove', function(e){
// 鼠标在中等盒子的坐标 = 鼠标在页面的坐标 - middle 盒子的坐标
// console.log(middle.getBoundingClientRect().x);
// console.log(middle.getBoundingClientRect().left);
let x = e.pageX - middle.getBoundingClientRect().left
let y = e.pageY - middle.getBoundingClientRect().top - document.documentElement.scrollTop
// 黑色遮罩移动 在middle 盒子内 限定移动距离
if (x >= 0 && x <= 400 && y >= 0 && y <= 400) {
// 黑色盒子不是一直移动的
// 声明两个变量, 黑色盒子移动的距离
let mx = 0
let 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
layer.style.left = mx + 'px'
layer.style.top = my + 'px'
// 大盒子的背景图片要跟随 中等盒子移动 存在的关系是 2倍
large.style.backgroundPositionX = -2 * mx + 'px'
large.style.backgroundPositionY = -2 * my + 'px'
}
})
//商品详情和商品评价切换
const tabHead = document.querySelector('.tab-head')
tabHead.addEventListener('click', function(e){
if(e.target.tagName === 'A') {
const active = document.querySelector('.tab-head .active')
if (active) active.classList.remove('active')
e.target.classList.add('active')
const id = e.target.dataset.id
const a = id == 2 ? 3 : 2
document.querySelector(`.main .cont>div:nth-child(${id})`).style.display = 'block'
document.querySelector(`.main .cont>div:nth-child(${a})`).style.display = 'none'
}
})
/*
//点击尺寸
(function () {
const dd = document.querySelector('.item #size');
dd.addEventListener('click', function (e) {
if (e.target.tagName == 'SPAN') {
const active = document.querySelector('.item #size .active');
if (active) active.classList.remove('active');
e.target.classList.add('active');
}
});
})();
//点击颜色
(function () {
const dd = document.querySelector('.item #color');
dd.addEventListener('click', function (e) {
if (e.target.tagName == 'IMG') {
const active = document.querySelector('.item #color img.active');
if (active) active.classList.remove('active');
e.target.classList.add('active');
}
});
})(); */
</script>