BOM浏览器对象模型
什么是BOM
- BOM(Browser Object Model)即浏览器对象模型,它提供了独立于内容而与浏览器窗口进行交互的对象,其核心对象是 window;
- BOM由一系列相关的对象构成,并且每个对象都提供了很多方法与属性;
- BOM缺乏标准,JavaScript语法的标准化组织是ECMA,DOM的标准化组织是W3C,DOM最初是Netscape浏览器标准的一部分;
BOM的构成
- window对象是浏览器的顶级对象,它具有双重角色;
(1)、它是JS访问浏览器窗口的一个接口;
(2)、它是一个全局对象。定义在全局作用域中的变量、函数都会变成window对象的属性和方法;在调用的时候可以省略window,对话框alert()
prompt()
等就属于window对象方法;
注意:window下的一个特殊属性windw.name
var num=10;
console.log(num) // 10
console.log(window.num)// 10
function fn(){
console.log(111)
}
fn();
window.fn();
window对象的常见事件
1、窗口加载事件
window.onload=function(){}
或者
window.addEventListener('load',function(){})
window.onload
是窗口(页面)加载事件,当文档内容完全加载完成会触发该事件(包括图像、脚本文件、CSS文件等),就调用的处理函数;
<script>
window.onload=function(){
var btn=document.querySelector('button');
btn.addEventListener('click',function(){
alert('111')
})
}
</script>
<button>点击</button>
- 注意:
(1)、有了window.onload
就可以把JS代码写到页面元素的上方,因为onload是等页面内容全部加载完毕,再去执行处理函数;
(2)、window.onload
【传统注册事件方式】只能写一次,如果有多个,会以最后一个window.onload
为准;
(3)、如果使用addEventListener
则没有限制;
//提倡写法(没有限制)
<script>
window.addEventListener('load',function(){
var btn=document.querySelector('button');
btn.addEventListener('click',function(){
alert('111')
})
})
</script>
<button>点击</button>
window.addEventListener('DOMContentLoaded',function(){})
DOMContentLoaded事件触发时,仅当DOM元素加载完成,不包括样式表、图片、flash等等(所以他的速度更快);IE9以上支持;如果页面的图片很多的话,从用户访问到onload触发可能需要较长的时间,交互效果就不能实现,必然影响用户体验,此时用DOMContentLoaded
事件比较合适;- 注意:
(1)、load
等页面内容全部加载完毕,包含页面dom元素、图片、flash、css等等;
(2)、DOMContentLoaded
是dom加载完毕,不包含图片、flash、css等就可以执行,加载速度比load
更快一些;
2、调整窗口大小事件
window.onresize=function(){};
window.addEventListener('resize',function(){});
window.onresize
是调整窗口大小加载事件,当触发时就调用的处理函数;- 注意:
(1)、只要窗口大小发生像素变化,就会触发这个事件;
(2)、我们经常利用这个事件完成响应式布局。window.innerWidth
当前屏幕的宽度;
<!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>调整窗口大小事件</title>
<style>
div {
width: 200px;
height: 100px;
background-color: pink;
}
</style>
</head>
<body>
<script>
window.addEventListener('load', function () {
var div = document.querySelector('div')
window.addEventListener('resize', function () {
console.log(window.innerWidth);
if (window.innerWidth <= 800) {
div.style.display = 'none'
} else {
div.style.display = 'block'
}
})
})
</script>
<div></div>
</body>
</html>
定时器
- window对象提供了两种定时器:
setTimeout()
和setInterval()
setTimeout()
定时器
window.setTimeout(调用函数,[延迟的毫秒数])
这个window在调用的时候可以省略;setTimeout()
方法用于设置一个定时器,该定时器在定时器到期后执行调用函数;这个延时时间单位是毫秒,但是可以省略,如果省略默认的是0;- 这个调用函数可以直接写函数,还可以写函数名;还有一个写法
'函数名()'
function callback(){
console.log(11)
}
setTimeout(callback,300)
setTimeout('callback()',300)//不提倡使用
- 页面中可以有很多定时器,我们经常给定时器加标识符(名字);
setTimeout()
这个调用函数我们也称为【回调函数 callback】;普通函数是按照代码顺序直接调用的,而这个函数,需要等待时间,时间到了才去调用这个函数,因此称为回调函数;
停止setTimeout()
定时器的window.clearTimeout(timeout ID)
window.clearTimeout(timeout ID)
取消了先前通过调用setTimeout()
建立的定时器;- window可以省略;
- 括号里面的参数就是定时器的标识符;
setInterval('回调函数',间隔的毫秒数)
定时器
setInterval()
方法重复调用一个函数,每隔这个时间,就去调用一次回调函数;
注意:
(1)、setTimeout()
延时时间到了,就去调用这个回调函数,只调用一次,就结束了这个定时器;
(2)、setInterval()
每隔这个延时时间,就去调用这个回调函数,会调用很多次,重复调用这个函数;
案例:倒计时案例
<!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>倒计时案例</title>
<style>
div {
width: 150px;
height: 50px;
color: aliceblue;
display: flex;
justify-content: space-around;
}
.hour,
.minute,
.second {
width: 40px;
height: 40px;
line-height: 40px;
text-align: center;
background-color: black;
}
</style>
</head>
<body>
<div>
<span class="hour">1</span>
<span class="minute">2</span>
<span class="second">3</span>
</div>
<script>
// 1、获取元素
var hour = document.querySelector('.hour');
var minute = document.querySelector('.minute');
var second = document.querySelector('.second');
/*
注意:第一次执行也是间隔毫秒数,因此刚刷新页面会有空白;
最好采取封装函数的方式,这样可以先调动一次这个函数,防止刚开始刷新页面有空白问题;
*/
countDown()
// 2、开启定时器
setInterval(countDown, 1000);
var inputTime = +new Date('2023-7-6 11:00:00');//返回的是用户输入时间总的毫秒数
function countDown() {
var nowTime = +new Date(); //返回的是当前时间总的毫秒数
var times = (inputTime - nowTime) / 1000;//times是剩余时间总的秒数
var h = parseInt(times / 60 / 60 % 24);//小时
//h = h < 10 ? '0' + h : h;
console.log(h);
hour.innerHTML = h;//把剩余的小时给,小时黑色盒子
var m = parseInt(times / 60 % 60);
m = m < 10 ? '0' + m : m;
minute.innerHTML = m;
var s = parseInt(times % 60);
s = s < 10 ? '0' + s : s;
second.innerHTML = s;
}
</script>
</body>
</html>
clearInterval
清除定时器
window.clearInterval(intervalID)
clearInterval
方法取消了先前通过调用setInterval()
建立的定时器;- 注意:
window
可以省略;里面的参数就是定时器的标识符;
<body>
<button class="begin">开启</button>
<button class="stop">停止</button>
<script>
var begin = document.querySelector('.begin');
var stop = document.querySelector('.stop');
var timer = null //全局变量,null是一个空对象,如果不赋初始值容易报undefined错;(注意:将timer定义为全局变量,使下面的函数内部都能访问)
begin.addEventListener('click', function () {
timer = setInterval(function () {
console.log('你好哇');
}, 1000)
})
stop.addEventListener('click', function () {
clearInterval(timer)
})
</script>
</body>
案例:发送短信案例(点击按钮之后,该按钮60秒之内不能再次点击,防止重复发送短信)
<body>
<!-- 点击按钮之后,该按钮60秒之内不能再次点击,防止重复发送短信 -->
手机号:
<input type="number"> <button>发送</button>
<script>
var btn = document.querySelector('button');
var time = 3; //定义剩下的秒数
btn.addEventListener('click', function () {
btn.disabled = true;
var timer = setInterval(function () {
if (time == 0) {
// 清除定时器和复原按钮
clearInterval(timer)
btn.disabled = false;
btn.innerHTML = '发送';
time = 3; //这个3需要重新开始
} else {
btn.innerHTML = '还剩下' + time + '秒';
time--;
}
}, 1000)
})
</script>
</body>
this
指向问题
this
的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定this
到底指向谁,一般情况下this
的最终指向的是那个调用它的对象;- 1、全局作用域 或者 普通函数中this指向 【全局对象 window(浏览器顶级对象 window)】(注意定时器里面的this指向 window);
console.log(this);
function fn() {
console.log(this);
}
fn() //其实是 window.fn() 调用,省略了window
- 2、方法调用中谁调用this指向谁;
var o = {
sayHi: function () {
console.log(this); //this指向的是 o 这个对象 {sayHi: ƒ}
}
}
o.sayHi()
- 3、构造函数中this指向构造函数实例;
function Fun(){
console.log(this);//this 指向的是fun 实例对象 Fun {}
}
var fun=new Fun();
JS执行队列
- JS是单线程。JavaScript 语言的一大特点就是【单线程】,也就是说,同一个时间只能做一件事。这是因为JavaScript 这门脚本语言诞生的使命所致——JavaScript 是为处理页面中用户的交互,以及操作DOM而诞生的。比如我们对某个DOM元素进行添加和删除操作,不能同时进行,应该先进行添加,之后再删除;单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。这样所导致的问题是:如果JS执行的时间过长,这样就会造成页面渲染不连贯,导致页面渲染加载阻塞。
- 为了解决这个问题,利用多核CPU的计算能力,HTML5 提出 Web Worker 标准,允许JavaScript 脚本创建多个线程。于是,JS中出现了同步和异步。
同步和异步
- 同步:前一个任务结束后再执行后一个任务,程序的执行顺序和任务的排列顺序是一致的、同步的。
- 异步:在做一件事情时,因为这件事情会花费很长时间,在做这件事的同时,还可以去处理其他事情;
- 它们的本质区别:这条流水线上各个流程的执行顺序不同;
- 同步任务:同步任务都在主线程执行,形成一个【执行栈】。
- 异步任务:JS的异步是通过【回调函数】实现的。一般而言,异步任务有以下三种类型:
(1)、普通事件,如:click
resize
等;
(2)、资源加载,如:load
error
等;
(3)、定时器,包括:setInterval
setTimeout
等;
异步任务相关【回调函数】添加到【任务队列】中(任务队列也称为【消息队列】) - JS执行机制:
(1)、先执行执行栈中的同步任务;
(2)、遇到异步任务时,将异步任务(回调函数)放入任务队列中;
(3)、一旦执行栈中所有同步任务执行完毕,系统就会按次序读取 任务队列 中的异步任务,于是被读取的异步任务结束等待状态,进入执行栈,开始执行;
- 由于主线程不断的重复获得任务、执行任务、再获取任务、再执行,所以这种机制被称为事件循环(event loop)。
location
对象
window
对象给我们提供了一个location
属性用于获取或设置窗体的URL
,并且可以用于解析URL
,因为这个属性返回的是一个对象,所以我们将这个属性也称为location
对象;URL
:统一资源定位符(Uniform Resource Locator
)是互联网上标准的地址。互联网上的每个文件都有一个唯一的URL,它包含的信息指出文件的位置以及浏览器应该怎么处理它。
location
对象的属性
- 重点记住:
href
和search
<body>
<button>点击</button>
<script>
var btn=document.querySelector('button');
btn.addEventListener('click',function(){
console.log(window.location.href);//此处的window一般省略不写
location.href='https://www.baidu.com/' //点击跳转页面
})
</script>
</body>
案例:5秒钟之后自动跳转页面
<body>
<button>点击</button>
<div></div>
<script>
var btn = document.querySelector('button');
var div = document.querySelector('div');
// btn.addEventListener('click',function(){
//console.log(window.location.href);//此处的window可以省略
// location.href='https://www.baidu.com/'
// })
var time = 5;
function fun() {
if (time == 0) {
location.href = 'https://www.baidu.com/'
} else {
div.innerHTML = '你将在' + time + '秒钟之后跳转到首页'
time--
}
}
fun()
setInterval(fun, 1000)
</script>
</body>
案例:获取URL参数数据(主要练习数据在不同页面中的传递)
<!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>登录页</title>
</head>
<body>
<form action="index.html">
用户名:
<input type="text" name="uname">
<input type="submit" value="登录">
</form>
</body>
</html>
<!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>首页</title>
</head>
<body>
<h3>首页</h3>
<div></div>
<script>
console.log(location.search);// ?uname=输入的用户名
// 1、先去掉? substr('起始的位置',截取几个字符)
var params = location.search.substr(1);//从下标为1的字符开始截取,截取到最后一位
console.log(params); //uname=输入的用户名
// 2、利用 = 把字符串分割为数组 split('=')
var arr = params.split('=')
console.log(arr);//['uname', '输入的用户名']
var div=document.querySelector('div');
// 3、把数据写入div中
div.innerHTML=arr[1]+'欢迎你'
</script>
</body>
</html>
location
常见方法
navigator
对象
navigator
对象包含有关浏览器的信息,它有很多属性,我们最常用的是userAgent
,该属性可以返回由客户机发送服务器的user-agent
头部的值。
history
对象
window
对象给我们提供了一个history
对象,与浏览器历史记录进行交互。该对象包含用户(在浏览器窗口中)访问过的URL。
history
对象一般在实际开发中比较少用,但是会在一些OA办公系统中见到;
PC端网页特效
元素偏移量offset
系列
offsetLeft
和offsetTop
获取元素偏移
offset
翻译过来就是偏移量,我们使用offset
系列相关属性可以动态的得到该元素的位置(偏移)、大小等;- 获得元素距离带有定位父元素的位置;
- 获得元素自身的大小(宽度高度);
- 注意:返回的数值都不带单位;
<!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>offset系列属性</title>
<style>
body,
html {
margin: 0;
padding: 0;
}
.father {
width: 200px;
height: 200px;
background-color: pink;
margin: 150px;
}
.son {
width: 100px;
height: 100px;
background-color: purple;
margin-left: 45px;
}
</style>
</head>
<body>
<div class="father">
<div class="son"></div>
</div>
<script>
// offset系列
var father = document.querySelector('.father');
var son = document.querySelector('.son');
// 可以得到元素的 偏移 位置 返回的不带单位的数值 (记得设置body的margin为0)
console.log(father.offsetTop);//150 (margin: 100px;)
console.log(father.offsetLeft);//150 (margin: 100px;)
// 它以带有定位的父元素为准,如果没有父亲或者父亲没有定位,则以 body 为准;
console.log(son.offsetLeft);// 195 (父元素的 margin 值 + 子元素的 margin 值)
// 可以得到元素的 大小 宽度 和 高度
</script>
</body>
</html>
offsetWidth
和offsetHeight
获取元素大小
<!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>offset系列属性</title>
<style>
body,
html {
margin: 0;
padding: 0;
}
.w{
width: 200px;
height: 200px;
background-color: skyblue;
margin: 0 auto 200px;
}
</style>
</head>
<body>
<div class="w"></div>
<script>
// 可以得到元素的大小 宽度 和 高度(是包含 border + padding + width)
console.log(w.offsetWidth);//200 (因为盒子的宽高是200 200)
console.log(w.offsetHeight);//200
</script>
</body>
</html>
offsetParent
返回带有定位的父亲,否则返回的是body
// 返回带有定位的父亲,否则返回的是body
console.log(son.offsetParent);//如果son的父元素开了定位,就返回其父元素;没有开定位,就返回body
console.log(son.parentNode);//返回父亲,是最近一级的父亲,不管父亲有没有定位
offset
与style
的区别
案例:获取鼠标在盒子内的坐标
<!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>获取鼠标在盒子内的坐标</title>
<style>
.box {
width: 200px;
height: 200px;
background-color: pink;
}
</style>
</head>
<body>
<!--
案例分析:
1、我们在盒子内点击,想要得到鼠标距离盒子左右的距离;
2、首先得到鼠标在页面中的坐标(e.pageX,e.pageY);
3、其次得到盒子在页面中的距离(box.offsetLeft,box.offsetTop);
4、用鼠标距离页面的坐标减去盒子在页面中的距离,得到 鼠标在盒子内的坐标;
5、补充:如果想要移动一下鼠标,就要获取最新的坐标,使用鼠标移动事件 mousemove ;
-->
<div class="box"></div>
<script>
var box = document.querySelector('.box');
box.addEventListener('mousemove', function (e) {
console.log(e.pageX);
console.log(e.pageY);
var x = e.pageX - this.offsetLeft;
var y = e.pageY - this.offsetTop;
this.innerHTML = 'x坐标是' + x + 'y坐标是' + y
})
</script>
</body>
</html>
案例:模态框拖拽
<!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>模态框拖拽</title>
<style>
h6,
a {
padding: 0;
margin: 0;
}
.login {
display: none;
width: 512px;
height: 280px;
position: fixed;
border: #ebebeb solid 1px;
left: 50%;
top: 50%;
background: #ffffff;
box-shadow: 0px 0px 20px #ddd;
z-index: 9999;
transform: translate(-50%, -50%);
}
.login-title {
width: 100%;
margin: 10px 0px 0px 0px;
text-align: center;
line-height: 40px;
height: 40px;
font-size: 18px;
position: relative;
cursor: move;
}
.login-input-content {
margin-top: 20px;
}
.login-button {
width: 50%;
margin: 30px auto 0px auto;
line-height: 40px;
font-size: 14px;
border: #ebebeb 1px solid;
text-align: center;
}
.login-bg {
display: none;
width: 100%;
height: 100%;
position: fixed;
top: 0px;
left: 0px;
background: rgba(0, 0, 0, .3);
}
a {
text-decoration: none;
color: #000000;
}
.login-button a {
display: block;
}
.login-input input .list-input {
float: left;
line-height: 35px;
height: 35px;
width: 350px;
border: #ebebeb 1px solid;
text-indent: 5px;
}
.login-input {
overflow: hidden;
margin: 0px 0px 20px 0px;
}
.login-input label {
float: left;
width: 90px;
padding-right: 10px;
text-align: right;
line-height: 35px;
height: 35px;
font-size: 14px;
}
.login-title span {
position: absolute;
font-size: 12px;
right: -20px;
top: -30px;
background: #ffffff;
border: #ebebeb solid 1px;
width: 40px;
height: 40px;
border-radius: 20px;
}
</style>
</head>
<body>
<!--
案例分析:弹出框,我们也称为模态框
1、点击弹出层,会弹出模态框,并且显示灰色半透明的遮挡层;
2、点击关闭按钮,可以关闭模态框,并且同时关闭灰色半透明遮挡层;
3、鼠标放到模态框最上面一行,可以按住鼠标拖拽模态框在页面中移动;
4、鼠标松开,可以停止拖动模态框移动;
5、在页面中拖拽的原理:鼠标按下并且移动,之后松开鼠标;
6、触发事件是 鼠标按下:mousedown,鼠标移动:mousemove,鼠标松开:mouseup;
7、拖拽过程:鼠标移动过程中,获得最新的值赋给模态框的 left 和 top 值,这样模态框可以跟着鼠标走了;
8、鼠标按下触发的事件源是 最上面一行,就是 id 为 title;
9、鼠标的坐标 减去 鼠标在盒子内的坐标,才是模态框真正的位置;
10、鼠标按下,我们要得到鼠标在盒子内的坐标;
11、鼠标移动,就让模态框的坐标 设置为:鼠标坐标减去盒子坐标即可,注意:移动事件写到按下事件里面;
-->
<div class="login-header"><a id="link" href="javascript:;">点击,弹出登录框</a></div>
<div id="login" class="login">
<div id="title" class="login-title">登录会员
<span><a id="closeBtn" href="javascript:void(0);" class="close-login">关闭</a></span>
</div>
<div class="login-input-content">
<div class="login-input">
<label for="">用户名:</label>
<input type="text" placeholder="请输入用户名" name="info[username]" id="username" class="username">
</div>
<div class="login-input">
<label for="">登录密码:</label>
<input type="password" placeholder="请输入登录密码" name="info[password]" id="password" class="password">
</div>
</div>
<div id="loginBtn" class="login-button"><a href="javascript:void(0);" id="login-button-submit"></a></div>
</div>
<!-- 遮盖层 -->
<div id="bg" class="login-bg"></div>
<script>
// 1、获取元素
var login = document.querySelector('.login');
var mask = document.querySelector('.login-bg');
var link = document.querySelector('#link');
var closeBtn = document.querySelector('#closeBtn');
var title = document.querySelector('#title');
// 2、点击弹出层这个链接 link,让 mask 和 login 显示出来;
link.addEventListener('click', function () {
mask.style.display = 'block';
login.style.display = 'block';
})
// 3、点击 closeBtn 就隐藏 mask 和 login;
closeBtn.addEventListener('click', function () {
mask.style.display = 'none';
login.style.display = 'none';
})
// 4、开始拖拽
// (1)、当我们鼠标按下,就获得鼠标在盒子内的坐标;
title.addEventListener('mousedown', function (e) {
console.log('111');
var x = e.pageX - login.offsetLeft;
var y = e.pageY - login.offsetTop;
// (2) 鼠标移动的时候,把鼠标在页面中的坐标,减去 鼠标在盒子内的坐标就是模态框的left和top值
document.addEventListener('mousemove', move)
function move(e) {
login.style.left = e.pageX - x + 'px';
login.style.top = e.pageY - y + 'px';
}
// (3) 鼠标弹起,就让鼠标移动事件移除
document.addEventListener('mouseup', function () {
document.removeEventListener('mousemove', move);
})
})
</script>
</body>
</html>
案例:仿京东放大镜效果
/*
案例分析:
1、鼠标经过小图片盒子,黄色的遮挡层 和 大图片盒子显示,离开隐藏2个盒子功能;
2、把鼠标坐标给遮挡层不合适,因为遮挡层坐标以父盒子为准;
3、首先是获得鼠标在盒子的坐标;
4、之后把数值给遮挡层作为 left 和 top 值;
5、遮挡层不能超出小图片盒子的范围;
6、如果小于0,就把坐标设置为0;
7、如果大于遮挡层最大的移动距离,就把坐标设置为最大的移动距离;
8、遮挡层的最大移动距离:小图片盒子宽度 减去 遮挡层盒子宽度;
*/
// 入口函数,让当页面上的布局加载完再加载动画效果
window.addEventListener('load', function () {
var preview_img = document.querySelector('.preview_img');
var mask = this.document.querySelector('.mask');
var big = this.document.querySelector('.big');
var bigImg = this.document.querySelector('.bigImg');
// 1、当我们鼠标经过 preview_img 就显示和隐藏 mask 遮挡层 和 big 大盒子;
preview_img.addEventListener('mouseover', function () {
mask.style.display = 'block';
big.style.display = 'block';
})
preview_img.addEventListener('mouseout', function () {
mask.style.display = 'none';
big.style.display = 'none';
})
// 2、鼠标移动的时候,让黄色的盒子跟着鼠标来走
preview_img.addEventListener('mousemove', function (e) {
// (1)、先计算出鼠标在盒子内的坐标
var x = e.pageX - this.offsetLeft;
var y = e.pageY - this.offsetTop;
// (2)、减去盒子高度300的一半 是 150,就是我们 mask 的最终 left 和 top 了;此处 mask 的 left 和 top 不写死,防止盒子宽度发生变化;
// (3)、我们 mask 移动的距离
var maskX = x - mask.offsetWidth / 2;
var maskY = y - mask.offsetHeight / 2;
// (4)、如果x坐标小于了0,就让他停在0的位置;
// 遮挡层的最大移动距离(正方形,宽高相等);
var maskMax = preview_img.offsetWidth - mask.offsetWidth;
if (maskX <= 0) {
maskX = 0
} else if (maskX >= maskMax) {
maskX = maskMax
}
if (maskY <= 0) {
maskY = 0
} else if (maskY >= maskMax) {
maskY = maskMax
}
mask.style.left = maskX + 'px';
mask.style.top = maskY + 'px';
// 大图片的移动距离,遮挡层移动距离 * 大图片最大移动距离 / 遮挡层的最大移动距离
// 大图片最大移动距离
var bigMax = bigImg.offsetWidth - big.offsetWidth;
// 大图片的移动距离 X Y(注意:需要给图片加上定位,否则无法实现移动)
var bigX = maskX * bigMax / maskMax;
var bigY = maskY * bigMax / maskMax;
bigImg.style.left = -bigX + 'px';
bigImg.style.top = -bigY + 'px';
})
})
元素可视区client
系列
client
翻译过来就是客户端,我们使用client
系列的相关属性来获取元素可视区的相关信息。通过client
系列的相关属性可以动态的得到该元素的边框大小、元素大小等;
<!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>元素可视区client系列</title>
<style>
div {
width: 200px;
height: 200px;
background-color: pink;
border: 10px solid red; /* 不包含 border */
padding: 10px; /* 包含 padding */
}
</style>
</head>
<body>
<div></div>
<script>
//client 宽度 和 我们 offsetWidth 最大的区别就是 不包含边框;
var div = document.querySelector('div');
console.log(div.clientWidth);//220 包含padding
</script>
</body>
</html>
案例:淘宝flexible.js
源码分析
立即执行函数
- 立即执行函数:不需要调用,立马能够自己执行的函数;
- 也可以传递多个参数进来;
- 如果有多个立即执行函数,结尾要用分号分隔开;
- 立即执行函数最大的作用就是,独立创建了一个作用域;里面所有的变量都是局部变量,不会有命名冲突的情况;
<script>
//2、立即执行函数写法
// 写法一:
(function () { })();
// 写法二:
(function () { }());
(function(){
console.log(2);
})(); //第二个小括号可以看作是调用函数
(function(a){
console.log(a);//1
})(1) ;
(function (a, b) {
console.log(a + b);//1
var num = 10;//num是一个局部变量
})(1, 2); //3
</script>
pageshow
和 load
事件分析
- 下面3种情况都会刷新页面都会触发
load
事件:
(1)、a
标签的超链接;
(2)、F5或者刷新按钮(强制刷新);
(3)、前进、后退按钮; - 但是,火狐中有个特点,有个“往返缓存”,这个缓存中不仅保存着页面数据,还保存了
DOM
和JavaScript
的状态;实际上是将整个页面都保存在了内存里;所以,此时后退按钮不能刷新页面; - 此时可以使用
pageshow
事件来触发,这个事件在页面显示时触发,无论页面是否来自缓存。在重新加载页面中,pageshow
会在load
事件触发后触发;根据事件对象中的persisted
来判断是否是缓存中的页面触发的pageshow
事件,注意这个事件给window
添加;
(function flexible(window, document) {
// 获取的html 的根元素
var docEl = document.documentElement
// dpr 物理像素比
var dpr = window.devicePixelRatio || 1
// adjust body font size 设置我们body 的字体大小
function setBodyFontSize() {
// 如果页面中有body 这个元素 就设置body的字体大小
if (document.body) {
document.body.style.fontSize = (12 * dpr) + 'px'
} else {
// 如果页面中没有body 这个元素,则等着 我们页面主要的DOM元素加载完毕再去设置body
// 的字体大小
document.addEventListener('DOMContentLoaded', setBodyFontSize)
}
}
setBodyFontSize();
// set 1rem = viewWidth / 10 设置我们html 元素的文字大小
function setRemUnit() {
var rem = docEl.clientWidth / 10 //将屏幕大小划分成10等份
docEl.style.fontSize = rem + 'px'
}
setRemUnit()
// reset rem unit on page resize 当我们页面尺寸大小发生变化的时候,要重新设置下rem 的大小
window.addEventListener('resize', setRemUnit)
// pageshow 是我们重新加载页面触发的事件
window.addEventListener('pageshow', function(e) {
// e.persisted 返回的是true 就是说如果这个页面是从缓存取过来的页面,也需要从新计算一下rem 的大小
if (e.persisted) {
setRemUnit()
}
})
// detect 0.5px supports 有些移动端的浏览器不支持0.5像素的写法
if (dpr >= 2) {
var fakeBody = document.createElement('body')
var testElement = document.createElement('div')
testElement.style.border = '.5px solid transparent'
fakeBody.appendChild(testElement)
docEl.appendChild(fakeBody)
if (testElement.offsetHeight === 1) {
docEl.classList.add('hairlines')
}
docEl.removeChild(fakeBody)
}
}(window, document))
元素滚动scroll
系列
scroll
翻译过来就是滚动,我们使用scroll
系列的相关属性可以动态的得到该元素的大小、滚动距离等等;scroll
是自身实际内容的大小;如一个div
内内容比较多,超出盒子高度,返回的是实际内容的大小;
页面被卷去的头部
- 如果浏览器的高(或宽)度不足以显示整个页面时,会自动出现滚动条。当滚动条向下滚动时,页面上面被隐藏掉的高度,我们就称为页面被卷去的头部。滚动条在滚动时会触发
onscroll
事件。
案例:仿淘宝固定右侧侧边栏
<!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>仿淘宝固定右侧侧边栏</title>
<style>
.slider-bar {
position: absolute;
left: 50%;
top: 300px;
margin-left: 600px;
width: 45px;
height: 130px;
background-color: pink;
}
.w {
width: 1200px;
margin: 10px auto;
}
.header {
height: 150px;
background-color: purple;
}
.banner {
height: 250px;
background-color: skyblue;
}
.main {
height: 1000px;
background-color: yellowgreen;
}
span {
display: none;
position: absolute;
bottom: 0;
}
</style>
</head>
<body>
<!--
案例分析:
1、原先侧边栏是绝对定位;
2、当页面滚动到一定位置,侧边栏改为固定定位;
3、页面继续滚动,会让返回顶部显示出来;
4、需要用到页面滚动事件 scroll,因为是页面滚动,所以事件源是 document;
5、滚动到某个位置,就是判断页面被卷去的上部值;
6、页面被卷去的头部,可以通过 window.pageYOffset 获得;如果是被卷去的左侧 window.pageXOffset;
7、元素被卷去的头部是:element.scrollTop;如果是页面被卷去的头部则是:window.pageYOffset;
-->
<div class="slider-bar">
<span class="goBack">返回顶部</span>
</div>
<div class="header w">头部区域</div>
<div class="banner w">banner区域</div>
<div class="main w">主体部分</div>
<script>
// 1、获取元素
var sliderbar = document.querySelector('.slider-bar');
var banner = document.querySelector('.banner');
console.log(banner.offsetTop);
// banner.offsetTop 就是被卷去头部的大小,一定要写到滚动的外面;
var bannerTop = banner.offsetTop;
//当我们侧边栏固定定位之后应该变化的数值;
var sliderbarTop = sliderbar.offsetTop - bannerTop;
var main = document.querySelector('.main');
var goBack = document.querySelector('.goBack');
var mainTop = main.offsetTop;
// 2、页面滚动事件 scroll
document.addEventListener('scroll', function () {
// 3、当页面被卷去的头部大于等于了 172 时,侧边栏就要改为固定定位;
if (window.pageYOffset >= bannerTop) {
sliderbar.style.position = 'fixed';
sliderbar.style.top = sliderbarTop + 'px';
} else {
sliderbar.style.position = 'absolute';
sliderbar.style.top = '300px';
}
// 4、当页面滚动到main盒子,就显示 goBack 模块;
if (window.pageYOffset >= bannerTop) {
goBack.style.display = 'block';
} else {
goBack.style.display = 'none';
}
})
</script>
</body>
</html>
三大系列总结
offset
系列经常用于获得元素位置offsetLeft
offsetTop
;client
经常用于获取元素大小clientWidth
clientHeight
;scroll
经常用于获取滚动距离scrollTop
scrollLeft
;- 注意:页面滚动的距离通过
window.pageXOffset
获得;
mouseenter
和mouseover
的区别
- 当鼠标移动到元素上时,就会触发
mouseenter
事件; - 类似
mouseover
,它们两者之间的差别是: mouseover
鼠标经过自身盒子会触发,经过子盒子还会触发。mouseenter
只会经过自身盒子触发;- 之所以这样,就是因为
mouseenter
不会冒泡; - 跟
mouseenter
搭配mouseleave
同样不会冒泡;
动画函数封装
动画实现原理
- 核心原理:通过定时器
setInterval()
不断移动盒子位置;
<!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>动画原理</title>
<style>
div {
position: absolute;
left: 0;
width: 100px;
height: 100px;
background-color: pink;
}
</style>
</head>
<body>
<!--
实现步骤:
(1)、获得盒子当前位置;
(2)、让盒子在当前位置加上1个移动距离;
(3)、利用定时器不断重复这个操作;
(4)、加一个结束定时器的条件;
(5)、注意此元素需要添加定位,才能使用`element.style.left`;
-->
<div></div>
<script>
var div = document.querySelector('div')
var timer = setInterval(function () {
if (div.offsetLeft >= 400) {
// 停止定时器
clearInterval(timer)
}
div.style.left = div.offsetLeft + 1 + 'px';
}, 30)
</script>
</body>
</html>
动画函数简单封装
- 注意:函数需要传递2个参数,【动画对象】和【移动到的距离】;
<!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>动画原理</title>
<style>
div {
position: absolute; /* 一定要加定位,否则动画不生效 */
left: 0;
width: 100px;
height: 100px;
background-color: pink;
}
</style>
</head>
<body>
<!--
实现步骤:
(1)、获得盒子当前位置;
(2)、让盒子在当前位置加上1个移动距离;
(3)、利用定时器不断重复这个操作;
(4)、加一个结束定时器的条件;
(5)、注意此元素需要添加定位,才能使用`element.style.left`;
-->
<div></div>
<script>
// 简单动画函数封装,obj 目标对象;target 目标位置;
function animate(obj, target) {
var timer = setInterval(function () {
if (obj.offsetLeft >= target) {
// 停止定时器
clearInterval(timer);
}
obj.style.left = obj.offsetLeft + 1 + 'px';
}, 30)
}
var div = document.querySelector('div');
animate(div, 300);
</script>
</body>
</html>
动画函数给不同元素记录不同定时器
- 如果多个元素都使用这个动画函数,每次都要
var
声明定时器,我们可以给不同的元素使用不同的定时器(自己专门用自己的定时器); - 核心原理:利用js是一门动态语言,可以很方便的给当前对象添加属性;
<!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>动画原理</title>
<style>
div {
position: absolute;/* 一定要加定位,否则动画不生效 */
top: 50px;
left: 0;
width: 100px;
height: 100px;
background-color: pink;
}
</style>
</head>
<body>
<!--
实现步骤:
(1)、获得盒子当前位置;
(2)、让盒子在当前位置加上1个移动距离;
(3)、利用定时器不断重复这个操作;
(4)、加一个结束定时器的条件;
(5)、注意此元素需要添加定位,才能使用`element.style.left`;
-->
<div></div>
<button>点击开始行走</button>
<script>
// var obj = {};
// obj.name = 'andy';
// 简单动画函数封装,obj 目标对象;target 目标位置;
function animate(obj, target) {
// 当我们不断的点击按钮,这个元素的速度会越来越快,因为开启了太多的定时器;
// 解决方案就是:让元素只有一个定时器执行;
// 先清除以前的定时器,只保留当前的一个定时器执行;
clearInterval(obj.timer);
//var timer = setInterval(function () { //优化之前的写法
obj.timer = setInterval(function () { //优化后:不用var声明变量也就不用去内存中重新开辟空间了;给不同的元素指定了不同的定时器
if (obj.offsetLeft >= target) {
// 停止定时器
clearInterval(timer);
} else {
obj.style.left = obj.offsetLeft + 1 + 'px';
}
}, 30)
}
var div = document.querySelector('div');
var btn = document.querySelector('button');
// 当我们不断的点击按钮,这个元素的速度会越来越快,因为开启了太多的定时器;
// 解决方案就是:让元素只有一个定时器执行; 先清除以前的定时器,只保留当前的一个定时器执行;
btn.addEventListener('click', function () {
animate(div, 300);
})
</script>
</body>
</html>
缓动效果原理
- 缓动动画就是让元素运动速度有所变化,最常见的就是让速度慢慢停下来;
- 思路:
(1)、让盒子每次移动的距离慢慢变小,速度就会慢慢落下来;
(2)、核心算法:(目标值 - 现在的位置) / 10 作为每次移动的距离(即步长);
(3)、停止的条件是:让当前盒子位置等于目标位置就停止定时器;
<!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>缓动动画</title>
<style>
div {
/* 一定要加定位,否则动画不生效 */
position: absolute;
top: 50px;
left: 0;
width: 100px;
height: 100px;
background-color: pink;
}
</style>
</head>
<body>
<div></div>
<button>点击开始行走</button>
<!--
思路:
(1)、让盒子每次移动的距离慢慢变小,速度就会慢慢落下来;
(2)、核心算法:(目标值 - 现在的位置) / 10 作为每次移动的距离(即步长);
(3)、停止的条件是:让当前盒子位置等于目标位置就停止定时器;
-->
<script>
function animate(obj, target) {
clearInterval(obj.timer);
obj.timer = setInterval(function () {
// 步长值写到定时器的里面
var step = (target - obj.offsetLeft) / 10;
if (obj.offsetLeft == target) {
clearInterval(timer);
} else {
// 把每次加1 这个步长值改为一个慢慢变小的值,步长公式:(目标值 - 现在的位置) / 10
obj.style.left = obj.offsetLeft + step + 'px';
}
}, 15)
}
var div = document.querySelector('div');
var btn = document.querySelector('button');
btn.addEventListener('click', function () {
animate(div, 500);
})
//匀速动画:盒子当前的位置 + 固定的值
// 缓动动画:盒子当前的位置 + 变化的值 【(目标值 - 现在的位置) / 10】
</script>
</body>
</html>
缓动动画多个目标值之间移动
- 可以让动画函数从800移动到500;
- 当我们点击按钮时,判断步长是正直还是负值;
1、如果是正值,则步长往大了取整;
2、如果是负值,则步长向小了取整;
<!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>缓动动画</title>
<style>
div {
/* 一定要加定位,否则动画不生效 */
position: absolute;
top: 80px;
left: 0;
width: 100px;
height: 100px;
background-color: pink;
}
</style>
</head>
<body>
<div></div>
<button class="btn500">点击开始行走500</button>
<button class="btn800">点击开始行走800</button>
<!--
思路:
(1)、让盒子每次移动的距离慢慢变小,速度就会慢慢落下来;
(2)、核心算法:(目标值 - 现在的位置) / 10 作为每次移动的距离(即步长);
(3)、停止的条件是:让当前盒子位置等于目标位置就停止定时器;
(4)、注意步长值需要取整;
-->
<script>
function animate(obj, target) {
clearInterval(obj.timer);
obj.timer = setInterval(function () {
// 步长值写到定时器的里面;把步长值改为整数,不要出现小数问题;
//var step = Math.ceil((target - obj.offsetLeft) / 10);
var step = (target - obj.offsetLeft) / 10;
step = step > 0 ? Math.ceil(step) : Math.floor(step);
if (obj.offsetLeft == target) {
clearInterval( obj.timer);
} else {
// 把每次加1 这个步长值改为一个慢慢变小的值,步长公式:(目标值 - 现在的位置) / 10
obj.style.left = obj.offsetLeft + step + 'px';
}
}, 15)
}
var div = document.querySelector('div');
var btn500 = document.querySelector('.btn500');
var btn800 = document.querySelector('.btn800');
btn500.addEventListener('click', function () {
animate(div, 500);
})
btn800.addEventListener('click', function () {
animate(div, 800);
})
//匀速动画:盒子当前的位置 + 固定的值;
//缓动动画:盒子当前的位置 + 变化的值 【(目标值 - 现在的位置) / 10】;
</script>
</body>
</html>
缓动动画添加回调函数
- 回调函数原理:函数可以作为一个参数。将这个函数作为参数传到另一个函数里面,当那个函数执行完之后,再执行传进去的这个函数,这个过程就叫做回调;
- 回调函数写到定时器结束里面;
<!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>缓动动画</title>
<style>
div {
/* 一定要加定位,否则动画不生效 */
position: absolute;
top: 80px;
left: 0;
width: 100px;
height: 100px;
background-color: pink;
}
</style>
</head>
<body>
<div></div>
<button class="btn500">点击开始行走500</button>
<button class="btn800">点击开始行走800</button>
<!--
思路:
(1)、让盒子每次移动的距离慢慢变小,速度就会慢慢落下来;
(2)、核心算法:(目标值 - 现在的位置) / 10 作为每次移动的距离(即步长);
(3)、停止的条件是:让当前盒子位置等于目标位置就停止定时器;
(4)、注意步长值需要取整;
-->
<script>
function animate(obj, target, callback) {
console.log(callback); // callback = function(){} 调用的时候 callback()
clearInterval(obj.timer);
obj.timer = setInterval(function () {
// 步长值写到定时器的里面;把步长值改为整数,不要出现小数问题;
//var step = Math.ceil((target - obj.offsetLeft) / 10);
var step = (target - obj.offsetLeft) / 10;
step = step > 0 ? Math.ceil(step) : Math.floor(step);
if (obj.offsetLeft == target) {
clearInterval(obj.timer);
// 回调函数写到定时器结束里面
if (callback) {
// 调用函数
callback();
}
} else {
// 把每次加1 这个步长值改为一个慢慢变小的值,步长公式:(目标值 - 现在的位置) / 10
obj.style.left = obj.offsetLeft + step + 'px';
}
}, 15)
}
var div = document.querySelector('div');
var btn500 = document.querySelector('.btn500');
var btn800 = document.querySelector('.btn800');
btn500.addEventListener('click', function () {
animate(div, 500);
})
btn800.addEventListener('click', function () {
animate(div, 800, function () {
div.style.backgroundColor = 'red';
});
})
//匀速动画:盒子当前的位置 + 固定的值;
//缓动动画:盒子当前的位置 + 变化的值 【(目标值 - 现在的位置) / 10】;
</script>
</body>
</html>
动画函数的使用
- 动画函数封装到单独js文件里面:因为以后经常使用这个动画函数,可以单独封装到一个js文件里面,使用的时候引用这个js文件即可;
<!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>引用animate动画函数</title>
<style>
.sliderbar {
position: fixed;
right: 0;
bottom: 100px;
width: 40px;
height: 40px;
text-align: center;
line-height: 40px;
cursor: pointer;
color: #fff;
}
.con {
position: absolute;
left: 0;
top: 0;
width: 200px;
height: 40px;
background-color: purple;
z-index: -1;
}
</style>
<script src="54.animate.js"></script>
</head>
<body>
<div class="sliderbar">
<span>←</span>
<div class="con">问题反馈</div>
</div>
<script>
/*
1、当我们鼠标经过 sliderbar ,就会让 con 这个盒子滑动到左侧;
2、当我们鼠标离开 sliderbar,就会让 con 这个盒子滑动到右侧;
*/
var sliderbar = document.querySelector('.sliderbar');
var con = document.querySelector('.con');
sliderbar.addEventListener('mouseenter', function () {
animate(con, -160, function () {
// 当动画执行完毕,就把 ← 改为 →
sliderbar.children[0].innerHTML = '→';
});
})
sliderbar.addEventListener('mouseleave', function () {
animate(con, 0, function () {
// 当动画执行完毕,就把 ← 改为 →
sliderbar.children[0].innerHTML = '←';
});
})
</script>
</body>
</html>
网页轮播图
- 轮播图也称为焦点图;
- 功能需求:
(1)、鼠标经过轮播图模块,左右按钮显示,离开隐藏左右按钮;
(2)、点击右侧按钮一次,图片往左播放一张,以此类推,左侧按钮同理;
(3)、图片播放的同时,下面小圆圈模块跟随一起变化;
(4)、点击小圆圈,可以播放相应图片;
(5)、鼠标不经过轮播图,轮播图也会自动播放图片;
(6)、鼠标经过轮播图模块,自动播放停止; - 动态生成小圆圈:
(1)、核心思路:小圆圈的个数要跟图片张数一致;
(2)、所以首先先得到ul里面图片的张数(图片放入li里面,所以就是li的个数);
(3)、利用循环动态生成小圆圈(这个小圆圈要放入ol里面);
(4)、创建节点createElement('li')
;
(5)、插入节点ol.appendChild(li)
; - 小圆圈的排他思想:
(1)、点击当前小圆圈,就添加current
类;
(2)、其余的小圆圈,就移除current
类;
(3)、注意:我们在刚才生成小圆圈的同时,就可以直接绑定这个点击事件了; - 点击小圆圈滚动图片:
(1)、此时用到animate.js
动画函数,将js
文件引入。(注意:因为index.js
依赖animate.js
,所以,引入时animate.js
要写到index.js
上面);
(2)、使用动画函数的前提,该元素必须有定位;
(3)、注意是ul
移动,而不是li
;
(4)、滚动图片的核心算法:点击某个小圆圈,就让图片滚动 【小圆圈的索引号 x 图片的宽度】作为ul
移动距离;
(5)、此时需要知道小圆圈的索引号:我们可以在生成小圆圈的时候,给他设置一个自定义属性,点击的时候获取这个自定义属性即可; - 点击右侧按钮一次,就让图片滚动一次:
(1)、声明一个变量num
,点击一次,自增1,让这个变量乘以图片宽度,就是ul的滚动距离; - 图片无缝滚动原理:
(1)、把ul
第一个li
复制一份,放到ul
的最后面;
(2)、当图片滚动到克隆的最后一张图片时,让ul
快速的、不做动画的跳到最左侧,即left=0
;
(3)、同时num
赋值为0,可以从新开始滚动图片了; - 克隆第一张图片:
(1)、克隆ul
第一个li
,cloneNode()
加true
就是深克隆,复制里面的子节点;false
浅克隆;
(2)、添加到ul
最后面appendChild
; - 点击右侧按钮,小圆圈跟随变化:
(1)、最简单的做法是再声明一个变量circle
,每次点击自增1;注意:左侧按钮也需要这个变量,因此要声明全局变量;
(2)、但是,图片有5张,我们小圆圈只有4个少一个,必须加一个判断条件;
(3)、如果circle == 4
,就重新复原为0; - 自动播放功能:
(1)、添加一个定时器;
(2)、自动播放轮播图,实际就类似于点击了右侧按钮;
(3)、此时我们使用【手动调用】右侧按钮点击事件arrow_r.click()
;
(4)、鼠标经过focus
就停止定时器;
(5)、鼠标离开focus
就开启定时器;
window.addEventListener('load', function () {
// 1、获取元素
var arrow_l = this.document.querySelector('.arrow-l');
var arrow_r = this.document.querySelector('.arrow-r');
var focus = this.document.querySelector('.focus');
var focusWidth = focus.offsetWidth;
// 2、鼠标经过 focus,就显示隐藏左右按钮;
focus.addEventListener('mouseenter', function () {
arrow_l.style.display = 'block';
arrow_r.style.display = 'block';
clearInterval(timer);
timer = null;//清除定时器bianl
})
focus.addEventListener('mouseleave', function () {
arrow_l.style.display = 'none';
arrow_r.style.display = 'none';
timer = setInterval(function () {
// 手动调用点击事件
arrow_r.click();
}, 2000)
})
// 3、动态生成小圆圈,有几张图,我就生成几个小圆圈;
var ul = focus.querySelector('ul'); // 获取 focus 里面的 ul;
var ol = focus.querySelector('.circle');
// console.log(ul.children.length);
for (var i = 0; i < ul.children.length; i++) {
// 创建一个小 li
var li = document.createElement('li');
// 记录当前小圆圈的索引号,通过自定义属性来做;
li.setAttribute('index', i);
// 把小li插入到ol里面
ol.appendChild(li);
// 4、小圆圈的排他思想 我们可以直接在生成小圆圈的同时直接绑定点击事件
li.addEventListener('click', function () {
// 干掉所有人 把所有的小li 清除 current 类名
for (var i = 0; i < ol.children.length; i++) {
ol.children[i].className = '';
}
// 留下我自己 当前的小li 设置 current 类名
this.className = 'current';
// 5、点击小圆圈移动图片,当然移动的是ul
// ul的移动距离 = 小圆圈的索引号 x 图片的宽度 注意是负值
// 当我们点击了某个li,就拿到当前小li的索引号
var index = this.getAttribute('index');
//当我们点击了某个小li,就拿到当前小li的索引号给 num
num = index;
//当我们点击了某个小li,就拿到当前小li的索引号给 clrcle
clrcle = index;
console.log('index:' + index);
console.log('num:' + num);
console.log('circle:' + circle);
animate(ul, -index * focusWidth);
})
}
// 把ol里面的第一个小li设置类名为current;
ol.children[0].className = 'current';
// 6、克隆第一张图片(li)放到ul最后面;(在生成小圆圈的后面克隆的,所以页面上还是只有4个小圆圈)
var first = ul.children[0].cloneNode(true);
ul.appendChild(first);
// 7、点击右侧按钮,图片滚动一张
var num = 0;
var circle = 0; //circle 用来控制小圆圈的播放
// 右侧按钮
arrow_r.addEventListener('click', function () {
// 如果走到了最后复制的一张图片,此时,我们的ul要快速复原 left改为0;
if (num == ul.children.length - 1) {//图片的长度减一
ul.style.left = 0;
num = 0;
}
num++;
console.log('num:' + num);
animate(ul, -num * focusWidth);
// 8、点击右侧按钮,小圆圈跟随一起变化,可以再声明一个变量控制小圆圈的播放;
circle++;
console.log('circle:' + circle);
// 如果 circle==4,说明走到最后我们克隆的这张图片了,我们就复原;
if (circle == ol.children.length) {
circle = 0;
}
// 调用函数
circleChange();
})
// 9、左侧按钮
arrow_l.addEventListener('click', function () {
// 如果走到了最后复制的一张图片,此时,我们的ul要快速复原 left改为0;
if (num == 0) {//图片的长度减一
num = ul.children.length - 1;
ul.style.left = -num * focusWidth + 'px';
}
num--;
console.log('num:' + num);
animate(ul, -num * focusWidth);
// 8、点击右侧按钮,小圆圈跟随一起变化,可以再声明一个变量控制小圆圈的播放;
circle--;
console.log('circle:' + circle);
// 如果 circle<0,说明第一张图片,则小圆圈要改为第4个小圆圈;
// if (circle < 0) {
// circle = ol.children.length - 1;
// }
circle = circle < 0 ? ol.children.length - 1 : circle;
// 调用函数
circleChange();
})
function circleChange() {
// 先清除其余小圆圈的current类名
for (var i = 0; i < ol.children.length; i++) {
ol.children[i].className = '';
}
// 留下当前的小圆圈的current类名
ol.children[circle].className = 'current';
}
// 10、自动播放轮播图
var timer = setInterval(function () {
// 手动调用点击事件
arrow_r.click();
}, 1000)
})
节流阀以及逻辑中断应用
节流阀
- 防止轮播图按钮连续点击造成播放过快;
- 节流阀目的:当上一个函数动画内容执行完毕,再去执行下一个函数动画,让事件无法连续触发;
- 核心实现思路:利用回调函数,添加一个变量来控制,锁住函数和解锁函数;
(1)、开始设置一个变量var flag=true;
(2)、if(flag){flag=false;do something}
关闭水龙头;
(3)、利用回调函数,动画执行完毕,flag=true
打开水龙头;
function animate(obj, target, callback) {
// console.log(callback); callback = function() {} 调用的时候 callback()
// 先清除以前的定时器,只保留当前的一个定时器执行
clearInterval(obj.timer);
obj.timer = setInterval(function() {
// 步长值写到定时器的里面
// 把我们步长值改为整数 不要出现小数的问题
// var step = Math.ceil((target - obj.offsetLeft) / 10);
var step = (target - obj.offsetLeft) / 10;
step = step > 0 ? Math.ceil(step) : Math.floor(step);
if (obj.offsetLeft == target) {
// 停止动画 本质是停止定时器
clearInterval(obj.timer);
// 回调函数写到定时器结束里面
// if (callback) {
// // 调用函数
// callback();
// }
callback && callback();//逻辑中断应用
}
// 把每次加1 这个步长值改为一个慢慢变小的值 步长公式:(目标值 - 现在的位置) / 10
obj.style.left = obj.offsetLeft + step + 'px';
}, 15);
}
案例:返回顶部
- 滚动窗口至文档中的特定位置,
window.scroll(x,y)
; - 注意:里面的x, y 不跟单位,直接写数字即可;
- 带有动画的返回顶部:此时可以继续使用我们封装的动画函数;只需要把所有的left相关的值改为跟页面垂直滚动距离相关就可以了;页面滚动了多少,可以通过
window.pageYOffset
得到; - 最后是页面滚动,使用
window.scroll(x,y)
;
<!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>仿淘宝固定右侧侧边栏</title>
<style>
.slider-bar {
position: absolute;
left: 50%;
top: 300px;
margin-left: 600px;
width: 45px;
height: 130px;
background-color: pink;
}
.w {
width: 1200px;
margin: 10px auto;
}
.header {
height: 150px;
background-color: purple;
}
.banner {
height: 250px;
background-color: skyblue;
}
.main {
height: 1000px;
background-color: yellowgreen;
}
span {
display: none;
position: absolute;
bottom: 0;
}
</style>
</head>
<body>
<!--
案例分析:
1、原先侧边栏是绝对定位;
2、当页面滚动到一定位置,侧边栏改为固定定位;
3、页面继续滚动,会让返回顶部显示出来;
4、需要用到页面滚动事件 scroll,因为是页面滚动,所以事件源是 document;
5、滚动到某个位置,就是判断页面被卷去的上部值;
6、页面被卷去的头部,可以通过 window.pageYOffset 获得;如果是被卷去的左侧 window.pageXOffset;
7、元素被卷去的头部是:element.scrollTop;如果是页面被卷去的头部则是:window.pageYOffset;
-->
<div class="slider-bar">
<span class="goBack">返回顶部</span>
</div>
<div class="header w">头部区域</div>
<div class="banner w">banner区域</div>
<div class="main w">主体部分</div>
<script>
// 1、获取元素
var sliderbar = document.querySelector('.slider-bar');
var banner = document.querySelector('.banner');
console.log(banner.offsetTop);
// banner.offsetTop 就是被卷去头部的大小,一定要写到滚动的外面;
var bannerTop = banner.offsetTop;
//当我们侧边栏固定定位之后应该变化的数值;
var sliderbarTop = sliderbar.offsetTop - bannerTop;
var main = document.querySelector('.main');
var goBack = document.querySelector('.goBack');
var mainTop = main.offsetTop;
// 2、页面滚动事件 scroll
document.addEventListener('scroll', function () {
// 3、当页面被卷去的头部大于等于了 172 时,侧边栏就要改为固定定位;
if (window.pageYOffset >= bannerTop) {
sliderbar.style.position = 'fixed';
sliderbar.style.top = sliderbarTop + 'px';
} else {
sliderbar.style.position = 'absolute';
sliderbar.style.top = '300px';
}
// 4、当页面滚动到main盒子,就显示 goBack 模块;
if (window.pageYOffset >= bannerTop) {
goBack.style.display = 'block';
} else {
goBack.style.display = 'none';
}
})
// 当我们点击了返回顶部模块,就让窗口滚动到页面的最上方;
goBack.addEventListener('click', function () {
// window.scroll(x, y); 里面的x, y 不跟单位,直接写数字即可;
//window.scroll(0, 0); // 滚动的太快
//缓慢滚动到顶部
//因为是窗口滚动,所以对象是window;因为是滚动到页面的最顶端,所以target是0;
animate(window, 0);
})
// 动画函数
function animate(obj, target, callback) {
console.log(callback); // callback = function(){} 调用的时候 callback()
clearInterval(obj.timer);
obj.timer = setInterval(function () {
// 步长值写到定时器的里面;把步长值改为整数,不要出现小数问题;
//var step = Math.ceil((target - obj.offsetLeft) / 10);
var step = (target - window.pageYOffset) / 10;
step = step > 0 ? Math.ceil(step) : Math.floor(step);
if (window.pageYOffset == target) {
clearInterval(obj.timer);
// 回调函数写到定时器结束里面
if (callback) {
// 调用函数
callback();
}
} else {
window.scroll(0, window.pageYOffset + step)
}
}, 15)
}
</script>
</body>
</html>
案例:筋斗云案例
- 鼠标经过某个小li,筋斗云跟着到当前小li位置;
- 鼠标离开这个小li,筋斗云复原为原来的位置;
- 鼠标点击了某个小li,筋斗云就会留在点击这个小li的位置;
- 案例分析:
(1)、利用动画函数做动画效果;
(2)、原先筋斗云的起始位置是0;
(3)、鼠标经过某个li,把当前小li的offsetLeft
位置作为目标值即可;
(4)、鼠标离开某个小li,就把目标值设为0;
(5)、如果点击了某个小li,就把li当前的位置存起来,作为筋斗云的起始位置;
<!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>筋斗云导航栏案例</title>
<style>
* {
margin: 0;
padding: 0
}
ul {
list-style: none;
}
body {
background-color: black;
}
.c-nav {
width: 900px;
height: 42px;
background: #fff url(images/rss.png) no-repeat right center;
margin: 100px auto;
border-radius: 5px;
position: relative;
}
.c-nav ul {
position: absolute;
}
.c-nav li {
float: left;
width: 83px;
text-align: center;
line-height: 42px;
}
.c-nav li a {
color: #333;
text-decoration: none;
display: inline-block;
height: 42px;
}
.c-nav li a:hover {
color: white;
}
.cloud {
position: absolute;
left: 0;
top: 0;
width: 83px;
height: 42px;
background: url(images/cloud.gif) no-repeat;
}
</style>
<script src="./animate.js"></script>
<script>
window.addEventListener('load', function () {
// 1、获取元素
var cloud = document.querySelector('.cloud');
var c_nav = document.querySelector('.c-nav');
var lis = c_nav.querySelectorAll('li');
// 给所有的小li绑定事件
// 这个current作为筋斗云的起始位置
var current = 0;
for (var i = 0; i < lis.length; i++) {
// (1)、鼠标经过把当前小li的位置作为目标值;
lis[i].addEventListener('mouseenter', function () {
animate(cloud, this.offsetLeft);
});
// (2)、鼠标离开就回到起始的位置;(第一次鼠标离开回到0的位置)
lis[i].addEventListener('mouseleave', function () {
animate(cloud, current);
});
// (3)、当鼠标点击就把当前位置作为目标值;
lis[i].addEventListener('click', function () {
current = this.offsetLeft;
});
}
})
</script>
</head>
<body>
<!--
案例分析:
(1)、利用动画函数做动画效果;
(2)、原先筋斗云的起始位置是0;
(3)、鼠标经过某个li,把当前小li的`offsetLeft`位置作为目标值即可;
(4)、鼠标离开某个小li,就把目标值设为0;
-->
<div id="c_nav" class="c-nav">
<span class="cloud"></span>
<ul>
<li class="current"><a href="#">首页新闻</a></li>
<li><a href="#">师资力量</a></li>
<li><a href="#">活动策划</a></li>
<li><a href="#">企业文化</a></li>
<li><a href="#">招聘信息</a></li>
<li><a href="#">公司简介</a></li>
<li><a href="#">我是佩奇</a></li>
<li><a href="#">啥是佩奇</a></li>
</ul>
</div>
</body>
</html>
移动端网页特效
触屏事件
- 移动端浏览器兼容性较好,我们不需要考虑以前
js
的兼容性问题,可以放心的使用原生js
书写效果,但是移动端也有自己独特的地方,比如:触屏事件touch
(也称触摸事件),Android
和IOS
都有; touch
对象代表一个触摸点。触摸点可能是一根手指,也可能是一根触摸笔。触屏事件可响应用户手指(或触控笔)对屏幕或者触控板操作;
<!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>移动端touch触摸事件</title>
<style>
div {
width: 100px;
height: 100px;
background-color: pink;
}
</style>
</head>
<body>
<div></div>
<script>
var div = document.querySelector('div');
div.addEventListener('touchstart', function () {
console.log('111');
})
</script>
</body>
</html>
触摸事件对象TouchEvent
touchstart
touchmove
touchend
三个事件都会各自有事件对象;TouchEvent
是一类描述手指在触摸平面(触摸屏、触摸板等)的状态变化的事件。这类事件用于描述一个或多个触点,使开发者可以检测触点的移动,触点的增加和减少等等;
e.targetTouches[0]
就可以得到正在触摸dom元素的第一个手指的相关信息,比如:手指的坐标等等;
移动端拖动元素
touchstart
touchmove
touchend
可以实现拖动元素;- 但是,拖动元素需要当前手指的坐标值,我们可以使用
targetTouches[0]
里面的pageX
和pageY
; - 移动端拖动的原理:手指移动中,计算出手指移动的距离。然后用盒子原来的位置 + 手指移动的距离;
- 手指移动的距离:手指滑动中的位置 减去 手指刚开始触摸的位置;
- 拖动元素三步曲:
(1)、触摸元素touchstart
:获取手指初始坐标,同时获得盒子原来的位置;
(2)、移动手指touchmove
:计算手指的滑动距离,并且移动盒子;
(3)、离开手指:touchend
;
注意:手指移动也会触发滚动屏幕,所以这里要阻止默认的屏幕滚动e.preventDefault()
;
<!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>移动端touch触摸事件</title>
<style>
div {
width: 100px;
height: 100px;
background-color: pink;
position: absolute;
top: 0;
left: 0;
}
</style>
</head>
<body>
<div></div>
<script>
var div = document.querySelector('div');
var startX = 0; //获取手指初始坐标
var startY = 0;
var x = 0; //获得盒子原来的位置
var y = 0;
div.addEventListener('touchstart', function (e) {
// 获取手指初始坐标
startX = e.targetTouches[0].pageX;
startY = e.targetTouches[0].pageY;
x = this.offsetLeft;
y = this.offsetTop;
});
div.addEventListener('touchmove', function (e) {
// 计算手指的移动距离,手指移动之后的坐标减去手指初始的坐标
var moveX = e.targetTouches[0].pageX - startX;
var moveY = e.targetTouches[0].pageY - startY;
// 移动我们的盒子,盒子原来的位置+手指移动的距离
this.style.left = x + moveX + 'px';
this.style.top = y + moveY + 'px';
e.preventDefault();//阻止屏幕滚动的默认行为
})
</script>
</body>
</html>
案例:移动端轮播图
- 案例分析:
(1)、自动播放功能;
(2)、开启定时器;
(3)、移动端移动,可以使用translate
移动;
(4)、想要图片优雅的移动,添加过渡效果;
(5)、自动播放功能–无缝滚动:注意:我们判断条件是要等到图片滚动完毕再去判断,就是过渡完成后判断;此时,需要添加检测过渡完成事件transitionend
;
(6)、判断条件:如果索引号等于3,说明走到最后一张图片,此时索引号要复原为0;
(7)、如果索引号小于0,说明是倒着走,索引号等于2;此时图片,去掉过渡效果,然后移动;
补充:classList
属性:
- 是HTML5新增的一个属性,返回元素的类名,但是ie10以上版本支持;
- 该属性用于在元素中添加,移除及切换CSS类。有以下方法:
(1)、添加类:element.classList.add('类名')
(2)、移除类:element.classList.remove('类名')
(3)、切换类(自动判断有没有某个类名,有就将其去掉,没有就将其加上):element.classList.toggle('类名')
<!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>classList的使用</title>
<style>
.bg {
background-color: black;
}
</style>
</head>
<body>
<div class="one two"></div>
<button>开关灯</button>
<script>
// classList返回元素的类名
var div = document.querySelector('div');
//console.log(div.classList);
// 1、添加类名,是在后面追加类名,不会覆盖以前的类名;
div.classList.add('three');//注意:three前面不需要加点
//console.log(div.classList);
// 2、删除类名
div.classList.remove('three');
//console.log(div.classList);
// 3、切换类名(自动判断有没有某个类名,有就将其去掉,没有就将其加上)
var btn = document.querySelector('button');
btn.addEventListener('click', function () {
document.body.classList.toggle('bg');
})
</script>
</body>
</html>
window.addEventListener('load', function () {
// 1、获取元素
var focus = document.querySelector('.focus');
var ul = focus.children[0];
// 获得focus的宽度
var w = focus.offsetWidth;
var ol = focus.children[1];
// 2、利用定时器自动轮播图片
var index = 0;
var timer = setInterval(function () {
index++;
var translatex = -index * w;
ul.style.transition = 'all .3s';
ul.style.transform = 'translateX(' + translatex + 'px)';
}, 2000)
// 等着我们过渡完成之后,再去判断,监听过渡完成的事件 transitionend;
// 判断条件:如果索引号等于3,说明走到最后一张图片,此时索引号要复原为0;
ul.addEventListener('transitionend', function () {
// 无缝滚动
if (index >= 3) {
index = 0;
// 去掉过渡效果,这样让我们的ul快速的跳到目标位置;
ul.style.transform = 'none';
// 利用最新的索引号乘以宽度 去滚动图片
var translatex = -index * w;
ul.style.transform = 'translateX(' + translatex + 'px)';
} else if (index < 0) {
index = 2;
ul.style.transition = 'none';
var translatex = -index * w;
ul.style.transform = 'translateX(' + translatex + 'px)';
}
// 3、小圆点跟随变化效果:
//(3)、但是,是等着过渡结束之后变化,所以这个写到 transitionend 事件里面;
//(1)、把ol里面li带有current类名的选出来去掉类名 (remove);
ol.querySelector('.current').classList.remove('current');
// (2)、让当前索引号的小li加上 current (add);
ol.children[index].classList.add('current');
})
// 4、手指滑动轮播图:本质就是ul跟随手指移动,简单说就是移动端拖动元素;
// (1)、触摸元素 touchstart:获取手指初始坐标;
var startX = 0;
var moveX = 0;//后面我们会使用这个移动距离所以要定义一个全局变量;
var flag = false;
ul.addEventListener('touchstart', function (e) {
startX = e.targetTouches[0].pageX;
// 手指触摸的时候就停止定时器
clearInterval(timer);
});
// (2)、移动手指 touchmove:计算手指的滑动距离,并且移动盒子;
ul.addEventListener('touchmove', function (e) {
// 计算移动距离
moveX = e.targetTouches[0].pageX - startX;
// 移动盒子:盒子原来的位置 + 手指移动的距离
var translatex = -index * w + moveX;
// 手指拖动的时候,不需要动画效果所以要取消过渡效果
ul.style.transition = 'none';
ul.style.transform = 'translateX(' + translatex + 'px)';
flag = true;//如果用户手指移动过我们再去判断,否则不做判断效果;
e.preventDefault();//阻止滚动屏幕的行为;
});
//离开手指touchend:根据移动距离去判断是回弹还是播放上一张下一张;
ul.addEventListener('touchend', function (e) {
//(1)、如果移动距离大于50像素,我们就播放上一张或者下一张;
if (flag) {
if (Math.abs(moveX) > 50) { //Math.abs() 取绝对值
// 如果是右滑就是,播放上一张 moveX 是正值
if (moveX > 0) {
index--;
} else {
// 如果是左滑就是,播放下一张 moveX 是负值
index++;
}
var translatex = -index * w;
ul.style.transition = 'all .3s';
ul.style.transform = 'translateX(' + translatex + 'px)';
} else {
// (2)、如果移动距离小于50像素,我们就回弹;
var translatex = -index * w;
ul.style.transition = 'all .1s';
ul.style.transform = 'translateX(' + translatex + 'px)';
}
}
// 手指离开重新开启定时器
clearInterval(timer);
timer = setInterval(function () {
index++;
var translatex = -index * w;
ul.style.transition = 'all .3s';
ul.style.transform = 'translateX(' + translatex + 'px)';
}, 2000)
})
})
案例:返回顶部
- 当页面滚动到某个地方,就显示,否则隐藏;点击可以返回顶部;
- 事件:
scroll
页面滚动事件; - 如果被卷去的头部(
window.pageYOffset
)大于某个数值; - 点击返回顶部,
window.scroll(0,0)
;
window.addEventListener('load', function () {
// 1、获取元素
var focus = document.querySelector('.focus');
var ul = focus.children[0];
// 获得focus的宽度
var w = focus.offsetWidth;
var ol = focus.children[1];
// 2、利用定时器自动轮播图片
var index = 0;
var timer = setInterval(function () {
index++;
var translatex = -index * w;
ul.style.transition = 'all .3s';
ul.style.transform = 'translateX(' + translatex + 'px)';
}, 2000)
// 等着我们过渡完成之后,再去判断,监听过渡完成的事件 transitionend;
// 判断条件:如果索引号等于3,说明走到最后一张图片,此时索引号要复原为0;
ul.addEventListener('transitionend', function () {
// 无缝滚动
if (index >= 3) {
index = 0;
// 去掉过渡效果,这样让我们的ul快速的跳到目标位置;
ul.style.transform = 'none';
// 利用最新的索引号乘以宽度 去滚动图片
var translatex = -index * w;
ul.style.transform = 'translateX(' + translatex + 'px)';
} else if (index < 0) {
index = 2;
ul.style.transition = 'none';
var translatex = -index * w;
ul.style.transform = 'translateX(' + translatex + 'px)';
}
// 3、小圆点跟随变化效果:
//(3)、但是,是等着过渡结束之后变化,所以这个写到 transitionend 事件里面;
//(1)、把ol里面li带有current类名的选出来去掉类名 (remove);
ol.querySelector('.current').classList.remove('current');
// (2)、让当前索引号的小li加上 current (add);
ol.children[index].classList.add('current');
})
// 4、手指滑动轮播图:本质就是ul跟随手指移动,简单说就是移动端拖动元素;
// (1)、触摸元素 touchstart:获取手指初始坐标;
var startX = 0;
var moveX = 0;//后面我们会使用这个移动距离所以要定义一个全局变量;
var flag = false;
ul.addEventListener('touchstart', function (e) {
startX = e.targetTouches[0].pageX;
// 手指触摸的时候就停止定时器
clearInterval(timer);
});
// (2)、移动手指 touchmove:计算手指的滑动距离,并且移动盒子;
ul.addEventListener('touchmove', function (e) {
// 计算移动距离
moveX = e.targetTouches[0].pageX - startX;
// 移动盒子:盒子原来的位置 + 手指移动的距离
var translatex = -index * w + moveX;
// 手指拖动的时候,不需要动画效果所以要取消过渡效果
ul.style.transition = 'none';
ul.style.transform = 'translateX(' + translatex + 'px)';
flag = true;//如果用户手指移动过我们再去判断,否则不做判断效果;
e.preventDefault();//阻止滚动屏幕的行为;
});
//离开手指touchend:根据移动距离去判断是回弹还是播放上一张下一张;
ul.addEventListener('touchend', function (e) {
//(1)、如果移动距离大于50像素,我们就播放上一张或者下一张;
if (flag) {
if (Math.abs(moveX) > 50) { //Math.abs() 取绝对值
// 如果是右滑就是,播放上一张 moveX 是正值
if (moveX > 0) {
index--;
} else {
// 如果是左滑就是,播放下一张 moveX 是负值
index++;
}
var translatex = -index * w;
ul.style.transition = 'all .3s';
ul.style.transform = 'translateX(' + translatex + 'px)';
} else {
// (2)、如果移动距离小于50像素,我们就回弹;
var translatex = -index * w;
ul.style.transition = 'all .1s';
ul.style.transform = 'translateX(' + translatex + 'px)';
}
}
// 手指离开重新开启定时器
clearInterval(timer);
timer = setInterval(function () {
index++;
var translatex = -index * w;
ul.style.transition = 'all .3s';
ul.style.transform = 'translateX(' + translatex + 'px)';
}, 2000)
})
// 返回顶部模块
var goBack = document.querySelector('.goBack');
var nav = document.querySelector('nav');
window.addEventListener('scroll', function () {
if (window.pageYOffset >= nav.offsetTop) {
goBack.style.display = 'block';
} else {
goBack.style.display = 'none';
}
})
goBack.addEventListener('click', function () {
window.scroll(0, 0);
})
})
click
延时解决方案
- 移动端
click
事件会有300ms的延时,原因是移动端屏幕双击会缩放(double tap to zoom
)页面; - 解决方案:
(1)、禁止缩放。浏览器禁用默认的双击缩放行为并且去掉300ms的点击延迟;<meta name="viewport" content="user-scalable=no">
(2)、利用touch
事件自己封装这个事件解决300ms延迟;(该函数缺点:一次只能给一个元素使用)
原理就是:
a、当我们手指触摸屏幕,记录当前触摸时间;
b、当我们手指离开屏幕,用离开的时间减去触摸的时间;
c、如果时间小于150ms,并且没有滑动过屏幕,那么我们就定义为点击;
(3)、使用插件。fastclick
插件解决300ms延迟;
什么是插件?fastclick
、Swiper
、superslide
(https://www.superslide2.com)、iscroll
(https://github.com/cubiq/iscroll)、移动端视频插件zy.media.js
- 移动端要求的是快速开发,所以我们经常会借助于一些插件来帮我们完成操作,那么什么是插件呢 ?
- JS插件就是JS文件。它遵循一定规范编写,方便程序展示效果,拥有特定功能且方便调用。如轮播图和瀑布流插件;
- 特点:它一般是为了解决某个问题而专门存在,其功能单一,并且比较小。我们以前写的
animate.js
也算是一个最简单的插件; fastclick
插件的使用:
<!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>fastclick插件使用</title>
<style>
div {
width: 50px;
height: 50px;
background-color: aqua;
}
</style>
<script src="fastclick.js"></script>
</head>
<body>
<div></div>
<script>
// 从官网github复制而来
if ('addEventListener' in document) {
document.addEventListener('DOMContentLoaded', function () {
FastClick.attach(document.body);
}, false);
}
</script>
</body>
</html>
- 移动端视频插件
zy.media.js
;(https://github.com/ireaderlab/zyMedia) - H5给我们提供了
video
标签,但是浏览器的支持情况不同。不同的视频格式文件,我们可以通过source
解决;但是,外观样式、还有暂停、播放、全屏等功能我们只能自己写代码解决。这个时候我们可以使用插件方式来制作。
移动端常用开发框架
- 框架:顾名思义就是一套架构,它会基于自身的特点向用户提供一套较为完整的解决方案。框架的控制权在框架本身,使用者要按照框架所规定的某种规范进行开发。
- 插件一般是为了解决某个问题而专门存在,其功能单一,并且比较小;
- 前端常用的框架有:
Bootstrap
Vue
Angular
React
等。既能开发PC端,也能开发移动端; - 前端常用的移动端插件有:
swiper
iscroll
superslide
; - 框架:大而全,一整套解决方案;小而专一,某个功能的解决方案;
本地存储
- 本地存储特性:
(1)、数据存储在用户浏览器中;
(2)、设置、读取方便、甚至页面刷新不丢失数据;
(3)、容量较大,sessionStorage
约5M,localStorage
约20M;
(4)、只能存储字符串,可以将对象JSON.stringify()
编码后存储;
window.sessionStorage
- 生命周期为关闭浏览器窗口;
- 在同一个窗口(页面)下数据可以共享;
- 以键值对的形式存储使用;
- 存储数据:
sessionStorage.setItem(key,value)
; - 获取数据:
sessionStorage.getItem(key)
; - 删除数据:
sessionStorage.removeItem(key)
; - 删除所有数据:
sessionStorage.clear()
;
<!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>本地存储之sessionStorage</title>
</head>
<body>
<input type="text">
<button class="set">存储数据</button>
<button class="get">获取数据</button>
<button class="remove">删除数据</button>
<button class="del">清空所有数据</button>
<script>
var ipt = document.querySelector('input');
var set = document.querySelector('.set');
var get = document.querySelector('.get');
set.addEventListener('click', function () {
// 当我们点击了之后,就可以把表单里面的值存储起来
var val = ipt.value;
sessionStorage.setItem('uname', val);
})
get.addEventListener('click', function () {
// 当我们点击了之后,就可以把表单里面存储的值获取过来
console.log(sessionStorage.getItem('uname'));
})
</script>
</body>
</html>
window.localStorage
- 生命周期永久生效,除非手动删除,否则关闭页面也会存在;
- 可以多窗口(页面)共享(同一浏览器可以共享);
- 以键值对的形式存储使用;
- 存储数据:
localStorage.setItem(key,value)
; - 获取数据:
localStorage.getItem(key)
; - 删除数据:
localStorage.removeItem(key)
; - 删除所有数据:
localStorage.clear()
;
案例:记住用户名
<!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>记住用户名案例</title>
</head>
<body>
<!--
如果勾选记住用户名,下次用户打开浏览器,就在文本框里面自动显示上次登录的用户名;
案例分析:
1、把数据存起来,用到本地存储;
2、关闭页面,也可以显示用户名,所以用到localSorage;
3、打开页面,先判断是否有这个用户名,如果有,就在表单里面显示用户名,并且勾选复选框;
4、当复选框发生改变的时候 change 事件;
5、如果勾选就存储,否则就移除;
-->
<input type="text" id="username"> <input type="checkbox" name="" id="remember"> 记住用户名
<script>
var username = document.querySelector('#username');
var remember = document.querySelector('#remember');
if (localStorage.getItem('username')) {
username.value = localStorage.getItem('username');
remember.checked = 'true';
}
remember.addEventListener('change', function () {
if (this.checked) {
localStorage.setItem('username', username.value);
} else {
localStorage.removeItem('username');
}
})
</script>
</body>
</html>
- 存储时转化成字符串,获取时转化成对象;
<script>
var todolist = [{
title: '我今天吃八个馒头',
done: false
}, {
title: '我今天学习jq',
done: false
}, ];
// localStorage.setItem("todo", todolist);
// 1. 本地存储里面只能存储字符串的数据格式 把我们的数组对象转换为字符串格式 JSON.stringify()
localStorage.setItem("todo", JSON.stringify(todolist));
var data = localStorage.getItem("todo");
// console.log(typeof data);
// console.log(data[0].title);
// 2. 获取本地存储的数据 我们需要把里面的字符串数据转换为 对象格式 JSON.parse()
data = JSON.parse(data);
console.log(data);
console.log(data[0].title);
</script>