综合案例
小兔鲜页面注册
分析业务模块
-
发送验证码模块
用户点击之后,显示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}// 合法的 就清空spanspan.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}// 合法的 就清空spanspan.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}// 合法的 就清空spanspan.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}// 合法的 就清空spanspan.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}// 合法的 就清空spanspan.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}// 合法的 就清空spanspan.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//当前元素添加activetab_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.获取第一个小liconst 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')// 当前元素的爸爸添加 activee.target.parentNode.classList.add('active')// 拿到当前小图片的 src// console.log(e.target.src)// 让中等盒子里面的图片,src 更换为 小图片srcmiddle.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().leftlet 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 = 0if (x < 100) mx = 0if (x >= 100 && x <= 300) mx = x - 100if (x > 300) mx = 200if (y < 100) my = 0if (y >= 100 && y <= 300) my = y - 100if (y > 300) my = 200layer.style.left = mx + 'px'layer.style.top = my + 'px'// 大盒子的背景图片要跟随 中等盒子移动 存在的关系是 2倍 large.style.backgroundPositionX = -2 * mx + 'px'large.style.backgroundPositionY = -2 * my + 'px'}})