BOM:浏览器对象模型
BOM概述
什么是 BOM
- BOM(Browser Object Model)即浏览器对象模型,它提供了独立于内容而与浏览器窗口进行交互的对象,其核心对象是window。比如窗口的后退前进刷新,滚动条的滚动
- BOM由一系列相关的对象构成,并且每个对象都提供了很多方法与属性
- BOM缺乏标准,JavaScript语法的标准化组织是ECMA,DOM的标准化组织是W3C,BOM最初是Netscape浏览器标准的一部分。因此只学习一些兼容性比较好的方法和属性
- 浏览器中显示的部分就是文档也就是dom操作的部分,而整个浏览器就是BOM操作的部分
BOM 的构成
window 对象是浏览器的顶级对象,它具有双重角色。
- window对象是js访问浏览器窗口的一个接口,(通过这个接口可以改变窗口大小等浏览器状态)
- 它是一个全局对象。定义在全局作用域中的变量,函数都会变成window对象的属性和方法。若定义 var num=10; 访问时可以直接输出:console.log(num); 新的方法是可以输出console.log(window.num);
在调用的时候可以省略window,前面学习的对话框都属于window对象方法,如alert()、prompt()等。
注意:window下的一个特殊属性 window.name
窗口加载事件
window.onload
window.onload = function(){}
或者
window.addEventListener('load',function(){})
window.onload是窗口(页面)加载事件,当文档内容完全加载后会触发该事件(包括图像、脚本文件、CSS、文件等),就调用的处理函数。
为什么要使用window.onload呢?
因为 JavaScript 中的函数方法需要在 HTML 文档渲染完成后才可以使用,如果没有渲染完成,此时的 DOM 树是不完整的,这样在调用一些 JavaScript 代码时就可能报出"undefined"错误。
尤其是当把js内容写到外部js中时
注意:
- window.onload传统注册事件方式只能写一次,如果有多个,会以最后一个window.onload为准
- 如果使用addEventListener则没有限制
DOMContentLoaded
document.addEventListener('DOMContentLoaded',function(){})
DOMContentLoaded事件触发时,仅当DOM加载完成,不包括样式表,图片,flash 等等。
ie9以上才支持
如果页面的图片很多的话,从用户访问到onload触发可能需要较长的时间,交互效果就不能实现,必然影响用户的体验,此时用DOMContentLoaded事件比较合适
<script>
// window.onload = function () {
// var btn = document.querySelector('button');
// btn.addEventListener('click', function () {
// alert('点击我');
// })
// }
window.addEventListener('load', function () {
var btn = document.querySelector('button');
btn.addEventListener('click', function () {
alert('点击我');
})
})
window.addEventListener('load', function () {
alert(22);
})
document.addEventListener('DOMContentLoaded', function () {
alert(33);
})
// load 是等页面内容全部加载完毕,包含页面dom元素 图片 flash css等
// DOMContentLoaded 是DOM加载完毕,不包含图片 flash css 等就可以执行,加载速度比load块一点
</script>
调整窗口大小事件
window.onresize = funciton(){}
window.addEventListener('resize',function(){})
window.onresize 是调整窗口大小加载事件,当触发时就调用的处理函数(比如拖动浏览器窗口大小)
注意:
- 只要窗口大小发生像素变化,就会触发这个事件
- 我们经常利用这个事件完成响应式布局。window.innerWidth可以显示当前屏幕的宽度
<div></div>
<script>
var div = document.querySelector('div');
window.onload = function () {
window.addEventListener('resize', function () {
// console.log(window.innerWidth);
// console.log('biaohuale ');
// 只要屏幕的宽度小于1000px就隐藏div,否则就显示
if (window.innerWidth <= 1000) {
div.style.display = 'none';
} else {
div.style.display = 'block';
}
})
}
</script>
定时器
两种定时器
window对象给我们提供了2个非常好用的方法——定时器
- setTimeout()
- setInterval()
setTimeout()定时器
window.setTimeout(调用函数,[延迟的毫秒数]);
setTimeout()方法用于设定一个定时器,该定时器在定时器到期后执行调用函数
<script>
// 1.setTimeout
// 语法规范: window.setTimeout(调用函数,延迟时间);
// 1.这个window在调用的时候可以省略
// 2.这个延时时间单位是毫秒,但是可以省略,如果省略默认是0,单位是毫秒
// 3.这个调用函数可以直接写函数,还可以写函数名;还有一个写法就是 '函数名()'
// 4.页面中可能有很多个定时器,我们经常给定时器加标识符
// setTimeout(function () {
// console.log('time is over');
// }, 2000)
function callback() {
console.log('爆炸啦');
}
// 方法一:写函数名
// setTimeout(callback, 3000);
// 方法二:'函数名()' 不提倡
// setTimeout('callback()', 3000);
// 给定时器加标识符
var timer1 = setTimeout(callback, 3000);
</script>
注意:
- window可以省略
- 这个调用函数可以直接写函数,或者写函数名或者采取字符串’函数名()'三种形式。第三种不推荐
- 延迟的毫秒数省略默认是0,如果写,必须是毫秒
- 因为定时器可能有很多,所以我们经常给定时器赋值一个标识符
setTimeout()
这个调用函数我们也称为回调函数callback;普通函数是按照代码顺序直接调用。而这个函数需要等待一定的时间,时间到了才去调用这个函数,因此称为回调函数。
简单理解:回调, 就是回头调用的意思。上一件事干完,再回头再调用这个函数。
以前讲的element.onclick = function(){}
或者 element.addEventListener('click',fn)
; 里面的函数也都是回调函数
一案例:
案例分析:
核心思路:5秒之后,就把这个广告隐藏起来;并且使用setTimeout
<div>这是一个广告</div>
<script>
const div = document.querySelector('div');
window.setTimeout(fn, 5000);
function fn() {
div.style.display = 'none'
}
</script>
停止 setTimeout()定时器
window.clearTimeout(timeoutID)
clearTimeout()方法取消了先前通过调用setTImeout()建立的定时器
注意:
- window可以省略
- 里面的参数就是定时器的标识符
<button>点击停止定时器</button>
<script>
var btn = document.querySelector('button');
var timer = setTimeout(function () {
console.log('baozhale');
}, 5000);
btn.addEventListener('click', function () {
clearTimeout(timer);
})
</script>
setInterval()定时器
window.setInterval(回调函数,[间隔的毫秒数]);
setInterval()方法重复调用一个函数,每隔这个时间,就去调用一次回调函数。
注意:
- window可以省略
- 这个调用函数可以直接写函数,或者写函数名或者采取字符串’函数名()'三种形式
- 间隔的毫秒数省略默认是0,如果写,必须是毫秒,表示每隔多少毫秒就自动调用这个函数
- 因为定时器可能有很多,所以我们经常给定时器赋值一个标识符
区别:setInterval方法是重复调用一个函数,每隔这个时间就调用
<script>
// 1.setInterval
// 语法规范:window.setInterval(调用函数,延时时间);
setInterval(function () {
console.log('继续输出');
}, 1000);
// 2.setTimeout 延时时间到了,就去调用这个回调函数,只调用一次,就结束了这个定时器
// 3.setInterval 每隔这个延时时间,就去调用这个回调函数,会调用很多次,重复调用这个函数
</script>
一个定时器案例:
- 这个倒计时是不断变化的,因此需要定时器自动变化(setInterval)
- 三个黑色盒子里面分别存放时分秒
- 三个黑色盒子利用innerHTML放入计算的小时分钟秒数
- 第一次执行也是间隔毫秒数,因此刚刚刷新页面会有空白
- 采取封装函数的方式,这样可以先调用一次这个函数,防止刚开始刷新页面有空白问题
<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');// 秒数的黑色盒子
// 本来在函数里面 现在要放在函数外面,为了避免传参,var定义的是全局变量
var inputTime = +new Date('2020-7-7 23:00:00'); // 返回的是用户输入时间总的毫秒数
countDown(); // 我们先调用一次这个函数,防止第一次刷新有空白
setInterval(countDown, 1000);
// 2.开启定时器
function countDown() {
var nowTime = +new Date(); // 返回的是当前时间总的毫秒数
// var times = inputTime - nowTime; times是剩余时间总的毫秒数
var times = (inputTime - nowTime) / 1000; // times是剩余时间总的秒数
var h = parseInt(times / 60 / 60 % 24);
h = h < 10 ? '0' + h : 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>
停止setInterval()定时器
window.clearInterval(intervalID);
clearInterval()方法取消了先前通过调用setInterval()建立的定时器
注意:
- window可以省略.
- 里面的参数是定时器的标识符
<button class="begin">开启定时器</button>
<button class="stop">暂停定时器</button>
<script>
var begin = document.querySelector('.begin');
var stop1 = document.querySelector('.stop');
var timer = null;// 全局变量 null 是一个空对象
begin.addEventListener('click', function () {
timer = setInterval(function () {
console.log(666);
}, 1000);
});
stop1.addEventListener('click', function () {
clearInterval(timer);
})
</script>
案例分析:
- 按钮点击之后,会禁用disabled为true
- 同时按钮里面的内容会变化,注意button里面的内容通过innerHTML修改
- 里面的秒数是有变化的,因此需要用到定时器
- 如果变量为0说明到了时间,我们需要停止定时器,并且复原按钮初始状态。
手机号码:<input type="number"><button>发送</button>
<script>
// 1.注册事件
var btn = document.querySelector('button');
var t = 5;
var timer = null;
// 2.绑定事件
btn.addEventListener('click', function () {
timer = setInterval(function () {
if (t == 0) {
// 清除定时器和复原按钮
clearInterval(timer);
btn.disabled = false;
btn.innerHTML = '发送';
t = 5; // 这个5 需要重新开始
} else {
btn.disabled = true;
btn.innerHTML = '剩余时间还剩' + t + '秒';
--t;
}
}, 1000);
})
</script>
this
this的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定this到底指向谁,一般情况下this的最终指向的是那个调用它的对象
<button>点击</button>
<script>
// this 指向问题 一般情况下this的最终指向的是那个调用它的对象
// 1. 全局作用域或者普通函数中this指向全局对象window(注意定时器里面的this指向window)
console.log(this); // window
function fn() {
console.log(this);
}
fn(); // 结果是window ---> 其实是window.fn()
setTimeout(function () {
console.log(this);// window
}, 1000)
// 2. 方法调用中谁调用this指向谁
var o = {
sayHi: function () {
console.log(this); // this 指向的是o这个对象
}
}
o.sayHi();
var btn = document.querySelector('button');
btn.onclick = function () {
console.log(this); // this 指向的是btn这个按钮对象
}
btn.addEventListener('click', function () {
console.log(this);// this 指向的是btn这个按钮对象
})
// 3. 构造函数中this指向构造函数的实例
function Fun() {
console.log(this); // this指向的是fun 实例对象
}
var fun = new Fun();
</script>
一般情况下this的最终指向的是那个调用它的对象
- 全局作用域或者普通函数中this指向全局对象window( 注意定时器里面的this指向window)
- 方法调用中谁调用this指向谁
- 构造函数中this指向构造函数的实例
JS执行机制
JS是单线程
JavaScript语言的一大特点就是单线程,也就是说,同一时间只能做一件事。这是因为JavaScript这门脚本语言诞生的使命所致——JavaScript是为处理页面中用户的交互,以及操作DOM而诞生的。比如我们对某个DOM元素进行添加和删除操作,不能同时进行。应该先进行添加,之后再删除。
单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。这样所导致的问题是:如果JS执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞的感觉。
同步和异步
为了解决这个问题,利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程。于是,JS中出现了同步和异步。
一个问题:
以下代码执行的结果是什么?
console.log(1);
setTimeou(function(){
console.log(3);
},1000);
console.log(2);
其实是1,2,3,因为有异步的存在
同步和异步的本质区别:
流水线上各个流程的执行顺序不同
将原来代码中的等待时间从1000改为0,会有区别吗?
console.log(1);
setTimeou(function(){
console.log(3);
},1000);
console.log(2);
答案依旧是123,验证了上述本质区别
同步任务
同步任务都在主线程上执行,形成主线程执行栈
异步任务
JS的异步是通过回调函数实现的。
一般而言,异步任务有以下三种类型:
- 普通事件,如click、resize等
- 资源加载,如load、error等
- 定时器,包括setInterval、setTimeout等
异步任务相关回调函数添加到任务队列中(任务队列也称为消息队列)
JS执行机制
onclick里的函数会被放在异步进程处理程序中,只要点击了则会被放在异步任务里一次。所以会出现两种情况:click在2前和click在2后
事件循环(event loop):
由于主线程不断的重复获得任务、执行任务、再获取任务、再执行,所以这种机制被称为事件循环(event loop)
location对象
什么是location对象
window对象给我们提供了location属性用于获取或设置窗体的URL,并且可以用于解析URL。因为这个属性返回的是一个对象,所以我们将这个属性也称为location对象。
URL
统一资源定位符(Uniform Resource Locator,URL)是互联网上标准资源的地址。互联网上的每个文件都有一个唯一的URL,它包含的信息指出文件的位置以及浏览器应该怎么处理它。
URL的一般语法格式:
protocol://host[:port]/path/[?query]#fragment
https://www.baidu.com/index.html?user=pdd&sex=male#link
参数(query),以问号开头,以键值对的形式通过&符号分隔开来
location对象的属性
一个案例:
案例分析:
- 利用定时器做倒计时效果
- 时间到了,就跳转页面,利用location.href
<div></div>
<script>
var div = document.querySelector('div');
var t = 6;
function daojishi() {
t--;
div.innerHTML = '您还有' + t + '秒回到主页';
if (t == 0) {
location.href = 'https://www.baidu.com';
}
}
daojishi(); // 可有可无
setInterval(daojishi, 1000);
</script>
案例分析:
- 第一个登录页面,里面有提交表单,action提交到了index.html页面
- 第二个页面,可以使用第一个页面的参数,这样实现了一个数据不同页面之间的传递效果
- 第二个页面之所以可以使用第一个页面的数据,是利用URL里面的location.search参数
- 在第二个页面中,需要把这个参数提取。
- 第一步去掉问号(?)利用substr
- 第二步去掉(=)号分割键和值 利用spilt(’=’)
在login.html中:
<form action="index.html">
用户名:<input type="text" name="uname" id="">
<input type="submit" value="提交">
</form>
在index.html中:
<style>
div {
display: inline;
}
</style>
欢迎您 <div></div>
<script>
// console.log(location.search); // ?uname=andy
// 1.先去掉问号 substr('起始的位置',截取几个字符);
var params = location.search.substr(1); // 从第二个字符开始截取,后面的省略代表截取到最后一个字符 结果为:uname=andy
// console.log(params);
// 2.利用=把字符串分割为数组 split('=');
var arr = params.split('=');
// console.log(arr[1]);
// 3.把数据写入div中
var div = document.querySelector('div');
div.innerHTML = arr[1];
</script>
location 对象的方法
<button>点击</button>
<script>
var btn = document.querySelector('button');
btn.addEventListener('click', function () {
// 可以记录页面 回退到原来访问的页面
// location.assign('https://www.baidu.com');
// 不记录浏览历史,所以不可以实现后退功能
// location.replace('https://www.baidu.com');
// 相当于刷新页面,加true为强制刷新页面
location.reload(true);
})
</script>
navigator 对象
navigator 对象包含有关浏览器的信息,它有很多属性,我们最通常用的是userAgent,该属性可以返回由客户机发送服务器的user-agent头部的值。
下面前端代码可以判断用户哪个终端打开页面,实现跳转
两个引号之间写入要跳转的网页
history对象
写两个list.html和index.html 在里面操作
这个在OA系统里使用
PC端网页特效
元素偏移量offset 系列
offset 概述
offset翻译过来就是偏移量,我们使用offset系列相关属性可以动态的得到该元素的位置(偏移)、大小等。
- 获得元素距离带有定位父元素的位置
- 获得元素自身的大小(宽度高度)
- 注意:返回的数值都不带单位
<style>
* {
margin: 0;
padding: 0;
}
.father {
position: relative;
width: 200px;
height: 200px;
background-color: pink;
margin: 150px;
}
.son {
width: 100px;
height: 100px;
background-color: purple;
margin-left: 45px;
}
.w {
width: 200px;
height: 200px;
background-color: skyblue;
margin: 0 auto 200px;
padding: 10px;
border: 15px solid blanchedalmond;
}
</style>
<div class="father">
<div class="son"></div>
</div>
<div class="w"></div>
<script>
// offset 系列
var father = document.querySelector('.father');
var son = document.querySelector('.son');
// 可以得到元素的偏移,位置,返回的不带单位的数值
console.log(father.offsetTop); // 150 考虑margin值
console.log(father.offsetLeft); // 150 考虑margin值
// 它以带有定位的父亲为准
console.log(son.offsetLeft); // 没写定位的话,是以body为准
// 45 返回元素带有定位父元素左边边框的偏移,因为margin-left值是45px并且又带有定位,所以值是45
var w = document.querySelector('.w');
// 2.可以得到元素的大小 宽度和高度 是包含padding + border + width
// 如果将宽度值删除,则肉眼无法看出的宽度可以通过返回值得到
console.log(w.offsetWidth); // 200(width)+10*2(padding)+15*2(border) =250
console.log(w.offsetHeight); // 200(width)+10*2(padding)+15*2(border) =250
// 3.返回带有定位的父亲,否则返回的是body
console.log(son.offsetParent);// 必须有定位 <div class="father">...</div>
console.log(son.parentNode); // 返回父亲 是最近一级的父亲 亲爸爸 不管父亲有没有定位 <div class="father">...</div>
</script>
offset与style的区别
<div class="box"></div>
<script>
// offset 与style的区别
var box = document.querySelector('.box');
console.log(box.offsetWidth + 'px'); // 结果正常显示为200;不带单位;包含padding和border;只能读不能写
console.log(box.style.width); // 结果为空 不能正常显示,因为只能显示行内样式,不能显示内嵌样式表里的;带单位(若是行内样式);不包含padding和border;即可读又可写
</script>
offset
- offset 可以得到任意样式表中的样式值
- offset 系列获得的数值是没有单位的
- offsetWidth 包含padding+border+width
- offsetWidth 等属性是只读属性,只能获取不能赋值
- 所以,想要获取元素大小位置,使用offset更合适
style
- style 只能得到行内样式表中的样式值
- style.width 获得的是带有单位的字符串
- style.width 获得不包含padding和border的值
- style.width 是可读写属性,可以获取也可以赋值
- 所以,想要给元素更改值,需要用style改变
一案例:
案例分析:
- 在盒子内点击,想得到鼠标距离盒子左右的距离
- 首先得到鼠标在页面中的坐标(e.pageX,e.pageY)
- 其次得到盒子在页面中的距离(box.offsetLeft,box.offsetTop)
- 用鼠标距离页面的坐标减去盒子在页面中的距离,得到鼠标在盒子内的坐标
- 如果想要移动一下鼠标,就要获取最新的坐标,使用鼠标移动时间mousemove
<style>
.pdd {
position: absolute;
left: 100px;
top: 100px;
width: 200px;
height: 200px;
background-color: pink;
}
</style>
<div class="pdd"></div>
<script>
var pdd = document.querySelector('.pdd');
pdd.addEventListener('mousemove', function (e) {
// console.log(e.pageX);
// console.log(e.pageY);
// console.log(this.offsetTop);
// console.log(this.offsetLeft);
var left = e.pageX - pdd.offsetLeft;
var top = e.pageY - pdd.offsetTop;
// e.pageX 和 e.pageY是鼠标离浏览器顶端和左端的距离;pdd.offsetLeft 和 pdd.offsetTop是盒子离浏览器顶端和左端的距离
console.log('鼠标距离粉色盒子左侧距离为' + left + 'px');
console.log('鼠标距离粉色盒子上边距离为' + top + 'px');
})
</script>
pageX为鼠标距离浏览器左侧的距离,offsetLeft为盒子距离浏览器左侧的距离,相减即为鼠标距离盒子左侧的距离
又又一案例:
案例分析:
- 点击弹出层,模态框和遮罩层就会显示出来display:blcok;
- 点击关闭按钮,模态框和遮罩层会隐藏起来display:none;
先制作前两步的效果
<script>
// 1. 获取元素
var login = document.querySelector('.login');
var mask = document.querySelector('.login-bg');
var link = document.querySelector('#link');
var closeBtn = document.querySelector('#closeBtn');
// 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';
})
</script>
制作后面的过程:
- 在页面中拖拽的原理:鼠标按下并且移动,松开鼠标则停止移动
- 触发事件时鼠标按下mousedown、鼠标移动mousemove和鼠标松开mouseup
- 拖拽过程:鼠标移动过程中,获得最新的值赋给模态框的left和top值,这样模态框就可以跟着鼠标走了
- 鼠标按下触发的事件源是最上面一行,id为title
- 鼠标的坐标减去鼠标在盒子内的坐标,才是模态框真正的位置。
- 鼠标按下,就可以得到鼠标在盒子内的坐标。
- 鼠标移动,就让模态框的坐标设置为:鼠标坐标减去盒子坐标即可,注意移动事件写到按下事件里面
// 4.开始拖拽
// (1)当鼠标按下,就获得鼠标在盒子里的坐标
var title = document.querySelector('#title');
// 点击title的div才可以移动模态框
title.addEventListener('mousedown', function (e) {
var left = e.pageX - login.offsetLeft;
var top = e.pageY - login.offsetTop;
// (2)鼠标移动的时候,把鼠标在页面中的坐标,减去鼠标在盒子内的坐标就是模态框的left和top值
document.addEventListener('mousemove', function (e) {
login.style.left = e.pageX - left + 'px';
login.style.top = e.pageY - top + 'px';
})
})
- 鼠标松开,就停止拖拽,可以让鼠标移动事件解除
// (3) 鼠标弹起,就让鼠标移动事件移除
document.addEventListener('mouseup', function (e) {
document.removeEventListener('mousemove', move);
})
记得将mousemove里的方法更改为自定义函数move()
一案例:
在原先的网页里书写这个案例,符合企业级标准;放在pre_view里两个class分别为mask和big,并在css中书写对应样式,并且先隐藏起来
<style>
.preview_img {
position: relative;
height: 398px;
border: 1px solid #ccc;
}
.mask {
display: none;
position: absolute;
top: 0;
left: 0;
width: 300px;
height: 300px;
background-color: #fede4f;
/* .5代表半透明 */
opacity: .5;
border: 1px solid #ccc;
cursor: move;
}
.big {
display: none;
position: absolute;
left: 410px;
top: 0;
width: 500px;
height: 500px;
/* background-color: pink; */
z-index: 999;
// 后增加:
// overflow: hidden;
}
.big img {
width: 100%;
// 后修改为:
// position: absolute;
// top: 0;
// left: 0;
}
</style>
<div class="preview_img">
<img src="angel.jpg" alt="">
<div class="mask"></div>
<div class="big">
<img src="angel.jpg" alt="" class="bigImg">
</div>
</div>
案例分析1:
- 整个案例可以分为三个功能模块
- 鼠标经过小盒子,黄色遮罩层和大图片盒子显示,离开隐藏2个盒子功能
- 黄色的遮罩层跟随鼠标功能
- 移动黄色遮罩层,大图片跟随移动功能
第一个功能:
- 鼠标经过小图片盒子,黄色的遮罩层和大图片盒子显示,离开则隐藏这两个盒子功能
var previw_img = document.querySelector('.preview_img');
var mask = document.querySelector('.mask');
var big = document.querySelector('.big');
//1.当我们鼠标经过previw_img就显示和隐藏mask遮罩层和big 大盒子
previw_img.addEventListener('mouseover', function () {
mask.style.display = 'block';
big.style.display = 'block';
})
previw_img.addEventListener('mouseout', function () {
mask.style.display = 'none';
big.style.display = 'none';
})
案例分析2:
- 黄色的遮挡层跟随鼠标功能
- 把鼠标坐标给遮挡层不合适,因为遮挡层坐标以父盒子为准
- 首先是获得鼠标在盒子的坐标
- 之后把数值给遮挡层做left和top值
- 此时用到鼠标移动事件,但是还是在小图片内移动。
- 发现遮挡层位置不对,需要再减去盒子自身高度和宽度的一半
// 2.鼠标移动的时候,让黄色的盒子跟着鼠标来走
preview_img.addEventListener('mousemove', function (e) {
//(1)先计算出鼠标在盒子内的坐标
var left = e.pageX - this.offsetLeft;
var top = e.pageY - this.offsetTop;
// console.log(left, top);
//(2)减去盒子高度300 的一半是150 就是我们mask的最终left和top值
mask.style.left = left - mask.offsetWidth / 2 + 'px';
mask.style.top = top - mask.offsetHeight / 2 + 'px';
})
在小盒子内移动的效果:加判断条件
- 遮挡层不能超出小图片盒子范围
- 如果小于0,就把坐标设置为0
- 如果大于遮挡层最大移动的距离,就把坐标设置为最大的移动距离
- 遮挡层的最大移动距离:小图片盒子宽度减去遮挡层盒子宽度
// (3)我们mask移动的距离设定两个变量:
var maskX = left - mask.offsetWidth / 2;
var maskY = top - mask.offsetHeight / 2;
//加个判断:
// (4) 如果x/y坐标小于了0 就让他停在0的位置
var maskMax = preview_img.offsetWidth - mask.offsetWidth;
if (maskX <= 0) {
maskX = 0;
} else if (maskX >= preview_img.offsetWidth - mask.offsetWidth) {
maskX = preview_img.offsetWidth - mask.offsetWidth;
}
if (maskY <= 0) {
maskY = 0;
} else if (maskY >= preview_img.offsetHeight - mask.offsetHeight) {
maskY = preview_img.offsetHeight - mask.offsetHeight;
}
mask.style.left = maskX + 'px';
mask.style.top = maskY + 'px';
按照比例进行移动
// 大图片的移动距离 = 遮罩层移动距离 * 大图片最大移动距离/遮罩层的最大移动距离
// 大图
var bigImg = document.querySelector('.bigImg');
// 大图片最大移动距离,图片是800×800 而显示大图的大小为502×502
var bigMax = bigImg.offsetWidth - big.offsetWidth;
var bigX = bigMax * maskX / maskMax;
var bigY = bigMax * maskY / maskMax;
bigImg.style.left = -bigX + 'px';
bigImg.style.top = -bigY + 'px';
元素可视区client系列
client翻译过来就是客户端,我们使用client系列的相关属性来获取元素可视区的相关信息。通过client系列的相关属性可以动态的得到该元素的边框大小、元素大小等。
<style>
div {
width: 200px;
height: 200px;
background-color: pink;
border: 10px solid red;
}
</style>
<div></div>
<script>
// client 宽度 和 offsetWidth最大的区别 就是不包含border
var div = document.querySelector('div');
console.log(div.clientWidth); // 不包含边框的
</script>
立即执行函数:
(function(){})() 主要作用:创建一个独立的作用域,避免了命名冲突的问题
<script>
// 1.立即执行函数:不需要调用,立马能够自己执行的函数
function fn() {
console.log(1);
}
fn();
// 2. 写法 也可以传递参数进来
// (function () { })() 或者(function () { }())
(function (a, b) {
console.log(a + b);
})(1, 2);// 第二个连续的小括号可以看做是调用函数
(function (a, b) {
console.log(a + b);
var num = 10; // 局部变量
}(2, 3));
// 3. 立即执行函数最大的作用就是 独立创建了一个作用域,里面所有的变量都是局部变量 不会有命名冲突
</script>
元素滚动scroll系列
元素scroll系列属性
scroll 翻译过来就是滚动的,我们使用scroll系列的相关属性可以动态的得到该元素的大小、滚动距离等。
页面被卷去的头部
如果浏览器的高(或宽)度不足以显示整个页面时,会自动出现滚动条。当滚动条向下滚动时,页面上面被隐藏掉的高度,我们就称为页面被卷去的头部(scrollTop)。滚动条在滚动时会触发onscroll事件。
<script>
// scroll 系列
var div = document.querySelector('div');
console.log(div.scrollHeight); // 原始盒子大小加上超出盒子大小的文字部分
console.log(div.clientHeight); // 只是原始盒子大小
// scroll 滚动事件:当我们滚动条发生变化时会触发的事件
div.addEventListener('scroll', function () {
console.log(div.scrollTop); //0就是最上边,52就是最下边
})
</script>
<style>
div {
width: 200px;
height: 200px;
background-color: pink;
border: 1px solid red;
/* padding: 10px; 添加则会改变scrollHeight和clientHeight的值*/
/* 会显示滚动条 */
overflow: auto;
}
</style>
<div>
很多内容很多很多内容很多内容很多很多内容很多内容很多很多内容很多内容很多很多内容很多内容很多很多内容很多内容很多很多内容很多内容很多很多内容很多内容很多很多内容很多内容很多很多内容很多内容很多很多内容很多内容很多很多内容很多内容很多很多内容很多内容很多很多内容很多内容很多很多内容很多内容很多很多内容很多内容很多很多内容很多内容很多很多内容
</div>
<script>
// scroll 系列
var div = document.querySelector('div');
console.log(div.scrollHeight); // 原始盒子大小加上超出盒子大小的文字部分 200+115=315
console.log(div.clientHeight); // 只是原始盒子大小 200
// scroll 滚动事件:当我们滚动条发生变化时会触发的事件
// 当添加overflow:auto在div中,才会显示滚动条,才会进一步显示被卷曲部分的高度也就是scrollTop
div.addEventListener('scroll', function () {
console.log(div.scrollTop); //0就是最上边,136就是最下边
})
</script>
一案例:
案例分析:
- 需要用到页面滚动事件scroll因为是页面滚动,所以事件源是document
- 滚动到某个位置,就是判断页面被卷去的上部分值
- 页面被卷去的头部:可以通过window.pageYOffset 如果是被卷去的左侧 window,pageXOffset
- 注意,元素被卷去的头部是element.scrollTop,如果是页面被卷去的头部则是window.pageYOffset
<script>
// 1.获取元素
var slider_bar = document.querySelector('.slider-bar');
var goBack = document.querySelector('.goBack');
// 2.页面滚动事件 scroll
document.addEventListener('scroll', function () {
// console.log(window.pageYOffset);
// window.pageYOffset 页面被卷去的头部
// 3.当我们的页面被卷去的头部大于等于了172 此时 侧边栏就要改变为固定定位
if (window.pageYOffset >= 172) {
goBack.style.display = 'inline';
// slider_bar.style.top = window.pageYOffset + 'px';
slider_bar.style.position = 'fixed';
} else {
goBack.style.display = 'none';
// slider_bar.style.top = 300 + 'px';
slider_bar.style.position = 'absolute';
}
})
</script>
再进一步修改数值
总结
它们的主要用法:
- offset系列经常用于获得元素位置 offsetLeft offsetTop
- client经常用于获取元素大小 clientWidth clientHeight
- scroll经常用于获取滚动距离 scrollTop scrollLeft
- 注意页面滚动距离通过window.pageXOffset 获取
mouseenter和mouseover的区别
mouseenter 鼠标事件
前者经过自身盒子触发,后者经过自身和子盒子都会触发
- 当鼠标移动到元素上时就会触发mouseenter事件
- mouseover鼠标经过自身盒子会触发,经过子盒子还会触发。mouseenter只会经过自身盒子触发
- 原因:mouseenter不会冒泡
- 因此跟mouseenter搭配鼠标离开mouseleave同样不会冒泡
由父亲移动到孩子的过程中不会触发,因为不会冒泡
动画函数封装
动画实现原理
核心原理:通过定时器setInterval()不断移动盒子位置
实现步骤:
- 获得盒子当前位置
- 让盒子在当前位置加上1个移动距离
- 利用定时器不断重复这个操作
- 加一个结束定时器的条件
- 注意此元素需要添加定位,才能使用element.style.left
<div></div>
<script>
// 1.动画原理
var div = document.querySelector('div');
var timer = setInterval(function () {
if (div.offsetLeft >= 400) {
clearInterval(timer);
} else {
div.style.left = div.offsetLeft + 5 + 'px';
}
}, 20)
</script>
动画函数简单封装
<div></div>
<span></span>
<script>
var div = document.querySelector('div');
var span = document.querySelector('span')
// 简单动画函数封装 obj是目标对象 target目标位置
function animate(obj, target) {
var timer = setInterval(function () {
if (obj.offsetLeft >= target) {
clearInterval(timer);
} else {
obj.style.left = obj.offsetLeft + 5 + 'px';
}
}, 20)
}
// 记得加定位!!!
animate(div, 400);
animate(span, 1000);
对封装的函数进行性能优化
每一次调用都会产生新的内存空间并且有多个timer会引起歧义
因此将var timer改为obj.timer(避免了var声明变量产生新的内存空间;并且便于阅读,每一个调用的对象都会有自己的定时器)
<button>点击才走</button>
<div></div>
<span></span>
<script>
// var obj = {};
// obj.name = 'andy';
// 给不同的元素指定了不同的定时器 ——————》obj.timer
var div = document.querySelector('div');
var span = document.querySelector('span')
// 简单动画函数封装 obj是目标对象 target目标位置
function animate(obj, target) {
// 当我们不断的点击按钮,这个元素的速度会越来越快,因为开启了太多的定时器
// 解决方案就是 让我们元素只有一个定时器执行
// 因此再点击一次时就会清楚上一个定时器,然后重新给他一个新的定时器,这样就不会触发多个定时器
clearInterval(obj.timer);
obj.timer = setInterval(function () {
if (obj.offsetLeft >= target) {
clearInterval(obj.timer);
} else {
obj.style.left = obj.offsetLeft + 5 + 'px';
}
}, 20)
}
// 记得加定位!!!
// animate(div, 400);
// animate(span, 1000);
// 当我们不断的点击按钮,这个元素的速度会越来越快,因为开启了太多的定时器
var btn = document.querySelector('button');
btn.addEventListener('click', function () {
animate(div, 400);
animate(span, 1000);
// btn.disabled = true; 自己的方法
})
</script>
缓动效果原理
缓动动画就是让元素运动速度有所变化,最常见的是让速度缓缓慢下来,直到停下来
思路:
- 让盒子每次移动的距离慢慢变小,速度就会慢慢落下来
- 核心算法:(目标值-现在的位置)/10 作为每次移动的距离或者叫步长
- 停止的条件是:让当前盒子位置等于目标位置就停止定时器
// 步长值写在定时器的里面
var step = (target - obj.offsetLeft) / 10;
// 把每次加5 这个步长值改为一个慢慢变小的值 步长公式:(目标值-现在的位置)/10 (100-0)/10=10 (100-10)/10 =9 (100-19)/10 =8.1
obj.style.left = obj.offsetLeft + step + 'px';
// 匀速动画 就是盒子是当前的位置+ 固定的值10
// 缓动动画就是 盒子当前的位置+变化的值(目标值-现在的位置)/10
动画函数在多个目标值之间移动
可以让动画函数从800到500
当我们点击按钮时候,判断步长是正值还是负值
如果是正值还是负值
- 如果是正值,则步长往大了取整
- 如果是负值,则步长向上了取整
因为有除法的出现所以会有小数除不断的情况
把我们步长值改为整数,不要出现小数的问题,所以使用取整函数,将得到的结果向大的取
Math.ceil()函数
将
var step = (target - obj.offsetLeft) / 10;
改为
var step = Math.ceil((target - obj.offsetLeft) / 10);
这样就会让最终的left值变为整数且等于target值,不会出现接近target值并且无限循环的情况
这时候再写一个button,并且将前一个修改为走到500,后一个修改为走到800,反复点则会发现盒子可以倒退了,原因是step变为了负值。与此同时发现了返回时left值只能到509px,因为算法为向上取整,而负值向上取整为更大的负值
所以写一个三元表达式用来判断正负然后根据正负来向上或者向下取整
将
var step = Math.ceil((target - obj.offsetLeft) / 10);
改为
var step = (target - obj.offsetLeft) / 10;
step = step > 0 ? Math.ceil(step) : Math.floor(step);
这样就可以正确的返回500和去向800了
动画函数添加回调函数
假如想给盒子在移动到left=800时变更背景颜色,则给animate函数加一个形参
将
function animate(obj, target) {
animate(span, 800);
改为
function animate(obj, target,callback) {}
animate(span, 800, function () {
span.style.backgroundColor = 'skyblue';
});
将callback的实参设定为一个函数,把函数当做参数传递给callback
回调函数写到定时器结束里面,因为想要在结束时才改变背景颜色
在停止计时器后写:
// 回调函数写到定时器结束里面
if (callback) {
// 调用函数
callback();
}
回调函数原理:函数可以作为一个参数。将这个函数作为参数传到另一个函数里面,当那个函数执行完之后,再执行传进去的这个函数,这个过程就叫做回调。
回调函数写的位置:定时器结束的位置。
动画函数封装到单独js文件里面
<div class="sliderbar">
<span>←</span>
<div class="con">问题反馈</div>
</div>
<script>
// 当我们鼠标经过sliderbar 就会让 con 这个盒子滑动到左侧
//1.获取元素
var sliderbar = document.querySelector('.sliderbar');
var con = document.querySelector('.con');
// var span = document.querySelector('span');
sliderbar.addEventListener('mouseenter', function () {
animate(con, -160, function () {
// span.innerHTML = '→';
sliderbar.children[0].innerHTML = '→';
});
// span.style.display = 'none';
})
// 当我们鼠标离开sliderbar 就会让 con 这个盒子滑动到右侧
sliderbar.addEventListener('mouseleave', function () {
animate(con, 0, function () {
// span.innerHTML = '←';
sliderbar.children[0].innerHTML = '←';
});
})
</script>
内部js
// 简单动画函数封装 obj是目标对象 target目标位置
function animate(obj, target, callback) {
// 当我们不断的点击按钮,这个元素的速度会越来越快,因为开启了太多的定时器
// 解决方案就是 让我们元素只有一个定时器执行
clearInterval(obj.timer);
obj.timer = setInterval(function () {
// 步长值写在定时器的里面
// 把我们步长值改为整数,不要出现小数的问题,所以使用取整函数,将得到的结果向大的取
var step = (target - obj.offsetLeft) / 10;
// var step = Math.ceil((target - obj.offsetLeft) / 10);
// console.log(step);
step = step > 0 ? Math.ceil(step) : Math.floor(step);
if (obj.offsetLeft == target) {
// 停止动画 本质就是停止计时器
clearInterval(obj.timer);
// 回调函数写到定时器结束里面
if (callback) {
// 调用函数
callback();
}
}
// 把每次加5 这个步长值改为一个慢慢变小的值 步长公式:(目标值-现在的位置)/10 (100-0)/10=10 (100-10)/10 =9 (100-19)/10 =8.1
obj.style.left = obj.offsetLeft + step + 'px';
}, 15);
}
css:
<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>
一案例:
分为四个部分:大盒子,图片、左右按钮和小圆圈
HTML:
<!-- 左右按钮 -->
<!-- 左侧按钮 -->
<a href="javascript:;" class="arrow-l">
<
</a>
<!-- 右侧按钮 -->
<a href="javascript:;" class="arrow-r">
>
</a>
<!-- 图片 -->
<!-- 核心的滚动区域 -->
<ul>
<li>
<a href="#"><img src="upload/focus.jpg" alt=""></a>
</li>
<li>
<a href="#"><img src="upload/focus1.jpg" alt=""></a>
</li>
<li>
<a href="#"><img src="upload/focus2.jpg" alt=""></a>
</li>
<li>
<a href="#"><img src="upload/focus3.jpg" alt=""></a>
</li>
</ul>
<!-- 小圆圈 -->
<ol class="circle">
写好之后发现所有图片竖着在原始那张图片下面排列,因为写在li中,因此要将所有图片浮动起来,图片需要水平滚动
(.focus为最外层盒子)
CSS:
.focus {
position: relative;
width: 721px;
height: 455px;
background-color: purple;
/* 新加,以隐藏多余的 */
overflow: hidden;
}
.focus ul {
position: absolute;
top: 0;
left: 0;
/* 增加父盒子宽度,让父盒子能容下儿子 */
width: 600%;
}
.focus ul li {
/* 只写这个不会浮动起来,因为父盒子太小了容不下 */
float: left;
}
轮播图也称为焦点图,是网页中比较常见的网页特效
效果1:
当鼠标移入轮播图时,左右箭头显示出来,离开隐藏左右按钮
效果2:
点击轮播图左右按钮就会有图片改变的效果
效果3:
图片和下面的小圆点会对应同步变化
效果4:
点击小圆点就会弹出相应的图片
效果5:
鼠标不经过轮播图,则图片会自动滚动播放
效果6:
鼠标经过轮播图模块,自动播放停止
案例分析1:
- 因为JS较多,我们需要单独建立JS文件夹,再新建js文件,引入页面中
- 此时需要添加load事件
- 鼠标经过轮播图模块,左右按钮显示,离开隐藏左右按钮,
window.addEventListener('load', function () {
// this.alert(1);
// 1.获取元素
var arrow_l = document.querySelector('.arrow-l');
var arrow_r = document.querySelector('.arrow-r');
var focus = document.querySelector('.focus');
// 2.鼠标经过focus 就显示左右按钮
focus.addEventListener('mouseenter', function () {
arrow_l.style.display = 'block';
arrow_r.style.display = 'block';
})
focus.addEventListener('mouseleave', function () {
arrow_l.style.display = 'none';
arrow_r.style.display = 'none';
})
})
案例分析2:
- 动态生成小圆圈
- 核心思路:小圆圈的个数要跟图片张数要跟图片张数一致
- 所以首先先得到ul里面图片的张数(图片放入li里面,所以就是li 的个数)
- 利用循环动态生成小圆圈(这个小圆圈要放入ol里面)
- 创建createElement(‘li’)
- 插入节点ol.appendChild(‘li’)
- 第一个小圆圈需要添加current类
创建节点的语法为:var element = document.createElement(’ ')
// 3. 动态生成小圆圈, 有几张图片,就生成几个小圆圈
var ul = focus.querySelector('ul');
console.log(ul.children.length); // 4个
var ol = focus.querySelector('.circle');
for (var i = 0; i < ul.children.length; i++) {
// 创建一个小li
var li = document.createElement('li');
// 把小li插入到ol里面
ol.appendChild(li);
}
// 把ol里面的第一个小li设置类名为 current
ol.children[0].className = 'current';
案例分析3:
排他思想:干掉所有人,留下我自己
排他思想放在for循环里,生成了之后就可以直接进行操作
效果:点击某个小按钮则变成白色,其他全部变成透明的
// 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';
})
案例分析4:
- 点击小圆圈实现滑动的效果
- 此时要用到animate动画函数,记得一定要将引入animate的js文件引入在index.js文件之前(因为有调用的效果)
- 使用动画函数的前提,该元素必须要有定位
- 图片动是ul动,而不是li动
- 滚动图片的核心算法:点击某个小圆圈,就让图片滚动小圆圈的索引号乘以图片的宽度作为ul移动距离
- 此时需要知道小圆圈的索引号,可以在生成小圆圈的时候,给它设置一个自定义属性,点击的时候获取这个自定义属性即可。
写在循环外:
// 记录当前小圆圈的索引号,通过自定义属性来做
li.setAttribute('index', i);
循环内:
// 5.点击小圆圈,移动图片 当然移动的是 ul
// ul移动的距离:小圆圈的索引号 * 图片的宽度 注意是负值
// 获取图片的宽度
var focusWidth = focus.offsetWidth;
// 当我们点击了某个 小li ,就拿到当前小li的索引号
var index = this.getAttribute('index'); // 0 1 2 3 得到的是自定义属性index的具体的值
var target = -index * focusWidth;
animate(ul, target);
案例分析5:
- 点击右侧按钮一次,就让图片滚动一张
- 声明一个变量num、点击一次就自增1,让这个变量乘以图片宽度,就是ul的滚动效果
- 图片无缝滚动原理:把ul的第一个复制一份,放到ul的最后面;当图片滚动到克隆的最后一张图片时,让ul快速的、不做动画的跳到最左侧:left为0
- 同时num赋值为0,就可以重新开始滚动图片了
在HTML中,首先将第一个li复制一份放到最后一个li的后面
var num = 0;
arrow_r.addEventListener('click', function () {
// 如果走到了最后复制的一张图片,此时我们的ul要快速复原 left改为0
if (num == ol.children.length - 1) {
ul.style.left = 0;
num = 0;
}
num++;
animate(ul, -num * focus.offsetWidth);
})
arrow_l.addEventListener('click', function () {
num--;
animate(ul, num * focus.offsetWidth);
})
出现问题1:当多加了一个li后,小圆点会多加一个
出现问题2:小圆点的个数不确定
案例分析6:
- 克隆第一张图片
- 克隆ul的第一个li,使用深克隆,cloneNode(true)
先把最后一个li删除
这种方法的原理是:把克隆节点写在生成小圆圈的后面,所以不会生成小圆圈
//7.克隆第一张图片 放到ul最后面
var first = ul.children[0].cloneNode(true);
ul.appendChild(first);
案例分析7:
- 点击右侧按钮,小圆圈跟随变化
- 最简单的办法就是再声明一个变量circle,每次点击就自增1,注意,左侧按钮也需要这个变量,因此要声明全局变量
- 但是图片有五张,小圆圈只有4个少一个,必须加一个判断条件:如果circle == 4 就重新复原为0
- 判断circle的方法要紧跟着circle定义写
// 8. 点击右侧按钮,小圆圈跟随一起变化,可以再声明一个变量circle控制小圆圈的播放
circle++;
if (circle == ol.children.length) {
circle = 0;
}
// 先清除其余小圆圈的current类名
for (var i = 0; i < ol.children.length; i++) {
ol.children[i].className = '';
}
// 留下当前小圆圈的current类名
ol.children[circle].className = 'current';
案例分析8:
把点击小圆圈和点击右按钮结合起来
也就是比如说当我们点击第三个小圆圈时,index=2,此时num也得=2
分别在
li.addEventListener('click', function (){})
中添加
// 当我们点击了某个小li 就要把这个li 的索引号给 num
num = index;
和
arrow_r.addEventListener('click', function () {})
中添加
// 也可以在上一个监听函数里写
circle = num;
案例分析9:
制作左侧按钮:复制右侧的,再相应修改
因为修改小圆圈的current类名的部分左右相同,所以封装到一个函数中:circleChange()
// 9.左侧按钮做法
arrow_l.addEventListener('click', function () {
// 如果走到了最后复制的一张图片,此时我们的ul要快速复原 left改为0;前面的记得加个li
if (num == 0) {
num = ul.children.length - 1;
ul.style.left = -focus.offsetWidth * num + 'px';
}
num--;
animate(ul, -num * focus.offsetWidth);
// 点击左侧按钮,小圆圈跟随一起变化,通过变量circle控制小圆圈的播放
circle--;
// 如果circle <0 说明是第一张图片,则小圆圈要改为第4个小圆圈(3)
if (circle < 0) {
circle = ol.children.length - 1;
}
circleChange();
});
function circleChange() {
// 先清除其余小圆圈的current类名
for (var i = 0; i < ol.children.length; i++) {
ol.children[i].className = '';
}
// 留下当前小圆圈的current类名
ol.children[circle].className = 'current';
}
案例分析10:
- 自动播放功能
- 添加一个定时器
- 自动播放轮播图,实际就类似于隔一段时间点击了右侧按钮
- 使用手动调用右侧按钮点击事件 arrow_r.click()
- 鼠标经过focus就停止定时器
- 鼠标离开focus就开启定时器
// 10.自动播放轮播图
var timer = this.setInterval(function () {
arrow_r.click();
}, 2000)
节流阀
防止轮播图按钮连续点击造成播放过快
- 节流阀目的:当上一个函数动画内容执行完毕,再去执行下一个函数动画,让事件无法连续触发。
- 核心实现思路:利用回调函数,添加一个变量来控制,锁住函数和解锁函数
- 开始设置一个变量 var flag = true;
- if(flag){flag = false;do something} 关闭水龙头
- 利用回调函数,当动画执行完毕,flag=true 打开水龙头
在右侧箭头点击的监听函数写入flag变量,设置为false。在animate函数中写入function回调函数打开节流阀(因为执行函数时动画已经结束了)
又一案例:
案例分析:
- 带有动画的返回顶部
- 此时可以继续使用封装的动画函数
- 只需要把所有的left相关的值改为跟页面垂直滚动距离相关的就可以了
- 页面滚动了多少,可以通过window.pageYOffset得到
- 最后是页面滚动,使用window.scroll(x,y)
写入:
// 3. 当我们点击了返回顶部模块,就让窗口滚动到页面的最上方
goBack.addEventListener('click', function () {
// 里面的x和y 不跟单位 直接写数字即可
// window.scroll(0, 0);
// 因为是窗口滚动,所以对象是window
animate(window, 0);
});
并修改动画函数:
将所有的obj.offsetLeft更换为window.pageYOffset
并在最后将
obj.style.left = obj.offsetLeft+ step + 'px';
修改为:
window.scroll(0, window.pageYOffset + step);
又又一案例:
(需要一个gif图片演示效果)
案例分析:
- 利用动画函数做动画效果
- 原先筋斗云的起始位置是0
- 鼠标经过某个小li,把当前小li的offsetLeft位置作为目标值即可
- 鼠标离开某个小li,就把目标值设为0
- 如果点击了某个小li,就把当前的位置存储起来,作为筋斗云的起始位置
在for循环中,lis[i]在调用时,必须使用this
设定一个current变量,记录当前位置
<script>
window.addEventListener('load', function () {
//1.获取元素
var cloud = document.querySelector('.cloud');
var ul = document.querySelector('ul');
var lis = ul.querySelectorAll('li');
// var flag = true;
// 这个current值作为筋斗云的起始位置
var current = 0;
for (var i = 0; i < lis.length; i++) {
// (1)鼠标经过把当前li的位置作为目标值
lis[i].addEventListener('mouseenter', function () {
animate(cloud, this.offsetLeft);
});
// if (flag) {
// //(3) 当我们鼠标点击,就把当前位置作为目标值
// lis[i].addEventListener('click', function () {
// cloud.style.left = this.offsetLeft;
// flag = false;
// })
// } else if (!flag) {
// // (2)鼠标离开就复原为0
// lis[i].addEventListener('mouseleave', function () {
// animate(cloud, 0);
// })
// }
// (2)鼠标离开就复原为0
lis[i].addEventListener('mouseleave', function () {
animate(cloud, current);
});
//(3) 当我们鼠标点击,就把当前位置作为目标值
lis[i].addEventListener('click', function () {
current = this.offsetLeft;
});
}
})
</script>