综合案例
小兔鲜页面注册
分析业务模块
-
发送验证码模块
用户点击之后,显示05 秒后重新获取
时间到了,自动改为重新获取
//1.发送短信验证码模块
const code=document.querySelector('.code')
let flag=true//通过一个变量来控制 节流阀
// 1.1 点击事件
code.addEventListener('click',function(){
if(flag){
flag=false//时间未到点击之后不可触发事件
let i=5
// 点击完毕之后立马触发
code.innerHTML=`0${i}秒后重新获取`
let timerID=setInterval(function(){
i--
code.innerHTML=`0${i}秒后重新获取`
if(i===0){
clearInterval(timerID)
flag=true//时间结束 点击之后可以触发事件
// 重新获取
code.innerHTML=`重新获取`
}
},1000)
}
})
-
各个表单验证模块
用户名验证(注意封装函数 verifyxxx),失去焦点触发这个函数
- 正则/^[a-zA-Z0-9-_]$/{6,16}
- 如果不符合要求,则出现提示信息 并return false中断程序
- 否则 则返回return true
- 之所以返回布尔值 是为了最后的提交按钮做准备
- 侦听使用change事件,当鼠标离开了表单,并且表单值发生了变化时触发(类似京东效果)
const input =document.querySelector('input') input.addEventListener('change',function(){ console.log(11); })
// 2.验证用户名
// 2.1获取用户名表单
const username=document.querySelector('[name=username]')
username.addEventListener('change',verfiyName)//函数名不加括号
// 2.3封装vertifyName函数
function verfiyName(){
const span=username.nextElementSibling
// console.log(11)
// 2.4 定规则
const reg=/^[a-zA-Z0-9-_]{6,16}$/
if(!reg.test(username.value)){
// console.log(11)
span.innerText='输入不合法,请输入6~16位'
return false
}
// 合法的 就清空span
span.innerText=''
return true
}
手机号验证
正则:/^1(3\d|4[5-9]|5[0-35-9]|6[567]|7[0-8]|8\d|9[0-35-9])\d{8}$/
其余同上
验证码验证
正则:/^\d{6}$/
其余同上
密码验证
正则:/^[a-zA-Z0-9-_]{6,20}$/
其余同上
再次密码验证
如果本次密码不等于上面输入的密码则返回错误信息
其余同上
我同意模块
添加类 .icon-queren2 则是默认选中样式,可以使用toggle切换类
表单提交模块
使用submit提交事件
-
勾选已经阅读统一模块
如果没有勾选同意协议,则提示需要勾选
classList.contains()看看有没有包含某个类,如果有则返回true,没有则返回false
-
下一步验证全部模块
只要上面有一个input验证不通过就不同意提交
如果上面input表单 只要有模块返回的是false 则阻止提交
(function(){
//1.发送短信验证码模块
const code=document.querySelector('.code')
let flag=true//通过一个变量来控制 节流阀
// 1.1 点击事件
code.addEventListener('click',function(){
if(flag){
flag=false//时间未到点击之后不可触发事件
let i=5
// 点击完毕之后立马触发
code.innerHTML=`0${i}秒后重新获取`
let timerID=setInterval(function(){
i--
code.innerHTML=`0${i}秒后重新获取`
if(i===0){
clearInterval(timerID)
flag=true//时间结束 点击之后可以触发事件
// 重新获取
code.innerHTML=`重新获取`
}
},1000)
}
})
});
// 2.验证用户名
// 2.1获取用户名表单
const username=document.querySelector('[name=username]')
username.addEventListener('change',verfiyName)//函数名不加括号
// 2.3封装vertifyName函数
function verfiyName(){
const span=username.nextElementSibling
// console.log(11)
// 2.4 定规则
const reg=/^[a-zA-Z0-9-_]{6,16}$/
if(!reg.test(username.value)){
// console.log(11)
span.innerText='输入不合法,请输入6~16位'
return false
}
// 合法的 就清空span
span.innerText=''
return true
}
// 3.验证手机号
// 2.1获取手机表单
const phone=document.querySelector('[name=phone]')
phone.addEventListener('change',verfiyPhone)//函数名不加括号
// 2.3封装verfiyPhone函数
function verfiyPhone(){
const span=phone.nextElementSibling
// console.log(11)
// 2.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)){
// console.log(11)
span.innerText='输入不合法,请输入正确的手机号码'
return false
}
// 合法的 就清空span
span.innerText=''
return true
}
// 4.验证验证码
// 2.1获取验证码表单
const code=document.querySelector('[name=code]')
code.addEventListener('change',verfiyCode)//函数名不加括号
// 2.3封装verfiycode函数
function verfiyCode(){
const span=code.nextElementSibling
// console.log(11)
// 2.4 定规则
const reg=/^\d{6}$/
if(!reg.test(code.value)){
// console.log(11)
span.innerText='输入不合法,请输入6位数字'
return false
}
// 合法的 就清空span
span.innerText=''
return true
}
// 5.验证密码框
// 2.1获取密码表单
const password=document.querySelector('[name=password]')
password.addEventListener('change',verfiypassword)//函数名不加括号
// 2.3封装verfiypassword函数
function verfiypassword(){
const span=password.nextElementSibling
// console.log(11)
// 2.4 定规则
const reg=/^[a-zA-Z0-9-_]{6,20}$/
if(!reg.test(password.value)){
// console.log(11)
span.innerText='输入不合法,6~20位数字字母符号组成'
return false
}
// 合法的 就清空span
span.innerText=''
return true
}
// 6.密码的再次验证
// 2.1获取再次验证表单
const confirm=document.querySelector('[name=confirm]')
confirm.addEventListener('change',verfiyconfirm)//函数名不加括号
// 2.3封装verfiyconfirm函数
function verfiyconfirm(){
const span=confirm.nextElementSibling
// console.log(11)
// 当前表单的值不等于密码框的值就是错误的
if(confirm.value!==password.value){
// console.log(11)
span.innerText='两次密码输入不一致'
return false
}
// 合法的 就清空span
span.innerText=''
return true
}
// 7.我同意
const queren=document.querySelector('.icon-queren')
queren.addEventListener('click',function(){
// 原来有的就删掉 原来没有的就添加
this.classList.toggle('icon-queren2')
})
// 8.提交模块
const form=document.querySelector('form')
form.addEventListener('submit',function(e){
// 判断是否勾选我同意模块 如果有icon-queren2说明就勾选了 否则没勾选\
if(!queren.classList.contains('icon-queren2')){
e.preventDefault()
return alert('请勾选同意协议')
}
//依次判断上面的每个input是否通过,只要有一个没通过就阻止
if(!(verfiyName()&verfiyCode()&verfiyPhone()&verfiyconfirm()&verfiypassword())){
e.preventDefault()
}
})
登录页
点击切换盒子
使用事件委托时,尽量不要在盒子套的很复杂的时候用,如果li标签里包了一个a元素,a元素里又包了个img元素,此时若给a标签设置事件委托,就不容易实现,因为点击的时候点击的是img元素。
// 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
//当前元素添加active
tab_nav.querySelector('.active').classList.remove('active')
e.target.classList.add('active')
for(let i=0;i<pane.length;i++){
// 先干掉所有人 for循环
pane[i].style.display='none'
}
// 让对应序号的大pane 显示
pane[e.target.dataset.id].style.display='block'
}
})
点击登陆可以跳转页面
- 先阻止默认行为
- 如果没有勾选同意,则提示要勾选
- required属性不能为空
- 假设登陆成功
- 把用户名记录到本地存储当中
- 同时跳转首页 location.href
// 点击提交模块
const form=document.querySelector('form')
const agree=document.querySelector('[name=agree]')
const username=document.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'
})
小兔鲜首页页面
步骤:
最好写个渲染函数,因为一会退出还要用到
1.如果本地存储有记录的用户名,读取本地存储数据
需要把用户名写到第一个li里面
格式:<a href="javascript:;"><i class="iconfont icon-user">用户名</i></a>
因为登陆了,所以第二个里面的文字变为:退出登录
格式:<a href="javascript:;">退出登录</a>
2.如果本地没有数据,则复原为默认的结构
3,点击退出登录,删除本地数据,并重新渲染函数
// 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')
// console.log(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()
})
放大镜效果
业务分析:
- 鼠标经过对应小盒子,左侧中等盒子显示对应中等图片
1.获取对应的元素
2.采取事件委托的形式,监听鼠标经过小盒子里面扽图片,注意此时需要使用mouseover事件,因为需要事件冒泡触发small
3.让鼠标经过小图片,可以拿到小图片的src,可以做两件事
- 让中等盒子的图片换成这个小图片的src
- 让大盒子的背景图片,也换成这个小图片的src
// 1. 获取三个盒子
// 2. 小盒子 图片切换效果
const small = document.querySelector('.small')
// 中盒子
const middle = document.querySelector('.middle')
// 大盒子
const large = document.querySelector('.large')
// 2. 事件委托
small.addEventListener('mouseover', function (e) {//事件冒泡
if (e.target.tagName === 'IMG') {
// console.log(111)
// 排他 干掉以前的 active li 上面
this.querySelector('.active').classList.remove('active')
// 当前元素的爸爸添加 active
e.target.parentNode.classList.add('active')
// 拿到当前小图片的 src
// console.log(e.target.src)
// 让中等盒子里面的图片,src 更换为 小图片src
middle.querySelector('img').src = e.target.src
// 大盒子更换背景图片
large.style.backgroundImage = `url(${e.target.src})`
}
})
- 鼠标经过中盒子,右侧会显示放大镜效果的大盒子
1.用到鼠标经过和i离开,鼠标经过中盒子,大盒子利用dislay来显示和隐藏
2,鼠标离开不会立马消失,而是有200ms的延迟,用户体验更好,所以尽量使用定时器做个延迟 settimeout
3.显示和隐藏也尽量定义一个函数,因为鼠标经过离开中等盒子,会显示隐藏,同时,鼠标经过大盒子,也会显示和隐藏
4,给大盒子里面的背景图片一个默认的第一张图片
// 3. 鼠标经过中等盒子, 显示隐藏 大盒子
middle.addEventListener('mouseenter', show)
middle.addEventListener('mouseleave', hide)
let timeId = null
// 显示函数 显示大盒子
function show() {
// 先清除定时器
clearTimeout(timeId)
large.style.display = 'block'
}
// 隐藏函数 隐藏大盒子
function hide() {
timeId = setTimeout(function () {
large.style.display = 'none'
}, 200)
}
// 4. 鼠标经过大盒子, 显示隐藏 大盒子
large.addEventListener('mouseenter', show)
large.addEventListener('mouseleave', hide)
- 黑色遮罩盒子跟着鼠标来移动
- 先做鼠标经过小盒子small盒子,显示隐藏黑色遮罩的盒子
- 让黑色遮罩跟着鼠标来走,需要用到鼠标移动事件 mousermove
- 让黑色盒子移动的核心思想:不断把鼠标在中等盒子内的坐标给遮罩层left top,这样遮罩层就可以跟着移动了
算法:
- 得到鼠标在页面中的坐标,利用事件对象的pageX
- 得到中等盒子在页面中的坐标 middle.getBoundingClientRect()
- 鼠标在中等盒子中的坐标=鼠标在页面中的坐标-middle中等盒子的坐标
- 黑色遮罩层不断得到 鼠标在中等盒子中的坐标 就可以移动起来了
- 注意y坐标特殊,需要减去页面被卷去的头部
- 不用offsetLeft和offsetTop的原因:因为这两个属性跟带有定位的父级有关系,很容易被父级影响,而getBoundingClientRect()不受定位父元素的影响
限定遮罩的盒子只能在middle内部移动,需要添加判断
- 限定水平方向 大于等于0并且小于等于400
- 限定垂直方向大于等于0并且小于等于400
遮罩盒子移动的坐标:
- 声明一个mx作为移动的距离
- 水平坐标x如果小于等于100,则移动的距离mx就是0,不应该移动
- 水平坐标如果大于等于100并且小于300,移动的距离就是mx-100(100)是遮罩层盒子自身宽度的一半
- 水平坐标如果大于等于300,移动的距离就是mx=200不用再移动了
- 其实水平移动就是在100~200之间移动的
- 鼠标在中等盒子上移动,大盒子的图片跟着显示对应位置
// 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) {
// let x = 10, y = 20
// console.log(11)
// 鼠标在middle 盒子里面的坐标 = 鼠标在页面中的坐标 - middle 中等盒子的坐标
// console.log(e.pageX)鼠标在页面中的坐标
// middle 中等盒子的坐标
// console.log(middle.getBoundingClientRect().left)
let x = e.pageX - middle.getBoundingClientRect().left
let y = e.pageY - middle.getBoundingClientRect().top - document.documentElement.scrollTop//解决页面滚动时框下移的问题,减去被卷去的头部
// console.log(x, y)
// 黑色遮罩移动 在 middle 盒子内 限定移动的距离
if (x >= 0 && x <= 400 && y >= 0 && y <= 400) {
// 黑色盒子不是一直移动的
// 声明2个变量 黑色盒子移动的 mx my变量
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
layer.style.left = mx + 'px'
layer.style.top = my + 'px'
// 大盒子的背景图片要跟随 中等盒子移动 存在的关系是 2倍
large.style.backgroundPositionX = -2 * mx + 'px'
large.style.backgroundPositionY = -2 * my + 'px'
}
})