9. 三大系列
本篇一开始我们已经学了三大系列中的offset系列
,三大系列分别是offset
系列、scroll
系列、client
系列。学习这些有什么用呢?在后面的特效案例中,会大量的使用到获取元素的宽度、获取元素内部的宽度、获取元素距离顶部的距离等。这时候就需要用到三大系列,下面为大家一一讲解三大系列的用法。
9.1 offset 系列
第一章已经讲过了,详见第一章。
9.2 scroll 系列
scroll
是用来获取盒子内容的大小和位置。scroll
家族有:scrollWidth
、scrollHeight
、scrollLeft
、scrollTop
。
1、onscroll 事件
前面DOM的时候,我们知道了触发事件,这里讲下onscroll事件。
对于有滚动条的盒子,可以使用onscroll
注册滚动事件,每滚动一像素,就会触发该事件。
示例代码: [31-scroll系列-onscroll事件.html]
<!-- 样式部分 -->
<style>
#box {
width: 300px;
height: 300px;
border: 2px solid salmon;
margin: 100px auto;
/* 当内容超出盒子大小的时候 自动生成滚动条 */
overflow: auto;
}
</style>
<!-- html 部分 -->
<div id="box">
我是内容我是内容我是内容我是内容我是内容我是内容我是内容
...
...
...
我是内容我是内容我是内容我是内容我是内容我是内容我是内容
</div>
<!-- js 部分 -->
<script>
var box = document.getElementById('box');
box.onscroll = function() {
console.log("滚了!滚了");
}
</script>
效果图:
2、scrollWidth 和 scrollHeight
scrollWidth
与scrollHeight
是盒子内容的真实的宽度和高度。与和盒子大小无关,仅仅与盒子的内容有关系,不包括border
、margin
,包括padding
。
scrollWidth = padding + width;
// 如果盒子里面的内容超出盒子高度的时候,这里的scrollHeight获取的就是内容的高度了
scrollHeight = padding + height;
示例代码: [32-scroll系列-scrollWidth&scrollHeight.html]
<!-- 样式部分 -->
<style>
#box {
width: 100px;
height: 100px;
border: 10px solid salmon;
margin: 50px;
padding: 10px;
}
</style>
<!-- html 部分 -->
<div id="box">
杨柳青青江水平,闻郎江上踏歌声。东边日出西边雨,道是无晴却有晴。
杨柳青青江水平,闻郎江上踏歌声。东边日出西边雨,道是无晴却有晴。
</div>
<!-- js 部分 -->
<script>
var box = document.getElementById('box');
console.log(box.scrollWidth); // 120
console.log(box.scrollHeight); // 241 获取的是内容的高度
</script>
效果图:
如果盒子里面的内容超出盒子高度的时候,这里的scrollHeight
获取的就是内容的高度了
注意:
- 在现代高版本浏览器中,
scrollHeight
,在内容没有超度盒子的情况下,获取到的高度是height+padding
- 但是在
IE8
以下的时候,即使内容没有超出盒子,获取到的高度也是内容的高度。这里就不演示了,scrollHeight
很少用到。
3、scrollTop 和 scrollLeft
scrollTop
是盒子内容被滚动条卷去的头部的高度。scrollLeft
是盒子内容被滚动条卷去的左侧的宽度。通常来说,scroll
系列用的最多的地方就是用来获取页面被卷去的宽度和高度,非常的常用。
scrollTop
和 scrollLeft
存在兼容性
示例代码: [33-scroll系列-scrollTop&scrollLeft.html]
<!-- 样式部分 -->
<style>
body {
height: 5000px;
}
</style>
<!-- js 部分 -->
<script>
// 给页面注册滚动事件
window.onscroll = function() {
// 滚动条滚动一次,浏览器就会获取一次被卷去的头部的高度
// 将高度赋值给title
// 获取scrollTop的时候是有兼容性的:现代浏览器用的是 window.pageYOffset
// IE678 用的是 document.documentElement.scrollTop
document.title = window.pageYOffset || document.documentElement.scrollTop;
}
</script>
效果图:
完整版封装函数: [34-scroll系列-scrollTop&scrollLeft兼容性封装.html]
function getScroll() {
// 返回的是一个对象,调用的时候 getScroll().top 获取页面被卷去的头部的距离
return {
left: window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft || 0,
top: window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0
}
}
返回值是一个对象,需要获得卷去头部的距离,只需要调用getScroll.top
即可。
scroll 系列图解:
示例代码:固定导航栏 [ 35-scroll系列-固定导航栏.html ]
- 通过
offsetHeight
获取导航栏上部元素自身的高度,判断scrollTop
的高度大于等于上部元素高度的时候,说明scrollTop
到导航栏的位置了; - 到达导航栏位置,给导航栏绝对定位在页面顶部,同时因为固定定位,导航栏脱标,所以要获得导航栏下部的元素,将其
margin-top
设置为导航栏的高度,将位置空出来; - 当
scrollTop
小于上部元素高度的时候,导航栏去掉固定定位,同时将下部元素的margin-top
设置为0
。
为什么一开始不直接拿导航栏到顶部的距离跟 scrollTop
比较呢?,因为导航栏固定定位之后位置就变了,恢复原来位置时的判断就不生效了
<!-- 样式部分 -->
<style>
* {
margin: 0;
padding: 0;
list-style: none;
}
html,
body {
width: 100%;
}
.header {
height: 130px;
background: #FBFBFB;
font: 700 28px/130px serif;
color: #666;
text-align: center;
}
.nav {
height: 60px;
width: 100%;
background: #B9E1DC;
font: 700 24px/60px serif;
color: #52524E;
text-align: center;
}
ul {
display: inline-block;
}
li {
float: left;
margin-left: 60px;
}
.content1,
.content2,
.content3 {
height: 800px;
background: #DFFCB5;
font: 700 60px/800px serif;
color: #52524E;
text-align: center;
}
.content2 {
background: #FFE1B6;
}
.content3 {
background: #CDE3EB;
}
.fixed {
position: fixed;
top: 0;
left: 0;
}
</style>
<!-- html 部分 -->
<div class="header" id="header">
顶部广告栏
</div>
<div class="nav" id="nav">
<ul>
<li>HOME</li>
<li>ABOUT</li>
<li>SERVICES</li>
<li>TEAM</li>
<li>CONTACT</li>
</ul>
</div>
<div class="content1" id="con">
内容1
</div>
<div class="content2">
内容2
</div>
<div class="content3">
内容3
</div>
<!-- js 部分 -->
<script>
var header = document.getElementById('header');
var nav = document.getElementById('nav');
var content = document.getElementById('con');
// 封装一个scrollTop兼容性函数
function getScrollTop() {
return window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
}
// 给页面注册滚动事件
window.onscroll = function() {
// 判断广告栏header 与 滚动的scrollTop的值
// 当scrollTop > header高度的时候 让导航栏 nav 固定定位
var scrollTop = getScrollTop();
if (scrollTop >= header.offsetHeight) {
// 样式中有的类名这里一定不要忘了加上去,否则就会被替换掉
nav.className = "nav fixed";
// 一旦标题栏设置了固定定位之后,就脱离标准流了,下面的内容就会顶上来,
// 所以要手动给下面的内容添加一个margin-top,将导航栏的位置留下来
content.style.marginTop = nav.offsetHeight + "px";
} else {
// 当scrollTop < header高度的时候 让导航栏 nav 恢复到原来的位置
// nav 取消固定定位,恢复到原来的位置,所以下面内容的margin-top也要去掉
nav.className = "nav"; // 去掉固定定位的样式,保留之前的样式
content.style.marginTop = 0;
}
};
</script>
效果图:
示例代码:两侧跟随小广告 [ 36-offset系列-两侧跟随小广告.html ]
- 需求:屏幕滚动多少,两侧广告缓动等距离
- 将两张图片绝对定位在屏幕两侧的中间
- 通过滚动事件,实时获取
scrollTop
的值 - 将获取到的
scrollTop
的值,通过缓动动画设置给两侧图片(需要将之前top
的高度加上去)
<!-- html 部分 -->
<img src="../image/两侧固定小广告/advert.jpg" alt="" id="img1">
<img src="../image/两侧固定小广告/advert.jpg" alt="" id="img2">
<div>
内 容
. . .
. . .
</div>
<!-- js 部分-->
<script>
window.onload = function() {
var imgs = document.getElementsByTagName('img');
window.onscroll = function() {
// 获取滚动条滚动距顶部的距离距离
var scrollTop = getScrollTop();
// 缓动跟随
animate(imgs[0], scrollTop + 300);
animate(imgs[1], scrollTop + 300);
};
// scrollTop兼容性处理
function getScrollTop() {
return window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
}
// 缓动动画
function animate(element, target) {
clearInterval(element.timer);
element.timer = setInterval(function() {
var leader = element.offsetTop;
var step = (target - leader) / 20;
step = step > 0 ? Math.ceil(step) : Math.floor(step);
leader += step;
element.style.top = leader + 'px';
if (Math.abs(target - leader) < Math.abs(step)) {
element.style.top = target + "px";
clearInterval(element.timer);
}
}, 15);
}
}
</script>
效果图:
示例代码:返回顶部 [ 37-offset系列-返回顶部.html ]
window.scrollTo(0, 0);
让滚动条回到(0,0)
位置。这是回到顶部的主要原理
- 注册滚动条滚动事件,实时获取
scrollTop
的位置,判断当它距离大于等于800
的时候,让回到顶部的按钮,缓动的显示出来。当scrollTop
位置小于800
的时候,让回到顶部的按钮,缓动的隐藏起来。 - 给回到顶部按钮注册点击事件,当点击的时候,页面缓动的回到顶部。这里就要用到刚刚提到的知识点:
window.scrollTo()
- 重新创建一个缓动动画框架,目标位置
target
就是scrollTo(0,target)
,所以,target
的值为0
; - 实现原理与最基本的缓动框架基本一样,只是将设置的值改为:
window.scrollTo(0,leader)
;此时的leader
还是一个未知数,leader
其实就是当前滚动条的位置,所以,在滚动事件里,只要将leader
实时获取滚动条位置即可。
<!-- 样式部分 -->
<style>
body {
background: #FDFCE0
}
div {
margin: 200px auto;
text-align: center;
}
img {
width: 50px;
height: 50px;
background: url(../image/返回顶部/top.png);
cursor: pointer;
position: fixed;
right: 50px;
opacity: 0;
bottom: 50px;
}
</style>
<!-- html 部分 -->
<div>
...
内容
...
</div>
<img src="../image/返回顶部/top.png" alt="" id="top">
<!-- js 部分 -->
<script src="../js/slow-animate-styles.js"></script>
<script>
var img = document.getElementsByTagName('img')[0];
var scrollTop;
window.onscroll = function() {
scrollTop = getScrollTop();
// 当滚动条位置大于等于800 的时候,让回到顶部图标缓动的显示出来
if (scrollTop >= 800) {
slowAnimateStyles(img, {
opacity: 100,
});
img.style.display = "block";
} else {
// 当位置小于800 的时候,回到顶部图标缓动的隐藏起来
slowAnimateStyles(img, {
opacity: 0,
}, function() {
img.style.display = "none";
});
// 在这里获取leader的位置
leader = getScrollTop();
}
};
// 点击img的时候,让滚动条回到顶部,这里有个知识点:window.scrollTo(0,0); 滚动条回到顶部
// 需要单独创建一个缓动动画
var timer = null;
var target = 0; // 目标位置为0 即:window.scrollTo(0,target)
var leader = 0; // 初始化leader
img.onclick = function() {
clearInterval(timer);
timer = setInterval(function() {
var step = (target - leader) / 10;
step = step > 0 ? Math.ceil(step) : Math.floor(step);
leader += step;
// 此时的leader还是一个未知数,我们需要获取到当前滚动条的位置,然后赋值给leader,
// 并且这个位置应该是实时变化的,我们只需要在上面的滚动事件里设置下leader即可
window.scrollTo(0, leader);
if (leader === 0) {
clearInterval(timer);
}
}, 15);
}
// scrollTop兼容性处理
function getScrollTop() {
return window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
}
</script>
效果图:
示例代码:楼层跳跃 [ 38-offset系列-楼层跳跃.html ]
- 其实这里的案例跟上面的返回顶部很类似,同样的运用到的是
window.scrollTo()
- 页面布局,背景继承
body
,和html
的100%
,将背景的索引与左边导航栏绑定 - 创建一个缓动动画框架,目标距离
target
,就是当前索引背景距离顶部的距离,leader
就是滚动条此时的位置
<!-- html 部分 -->
<ul>
<li>鞋子区域</li>
<li>袜子区域</li>
<li>裤子区域</li>
<li>裙子区域</li>
<li>帽子区域</li>
</ul>
<ol>
<li>鞋子</li>
<li>袜子</li>
<li>裤子</li>
<li>裙子</li>
<li>帽子</li>
</ol>
<!-- js 部分 -->
<script>
var colorArr = ["#B7F5DE", "#FFE9E3", "#CBF078", "#7CDFFF", "#F59292"];
var ul = document.getElementsByTagName('ul')[0];
var ol = document.getElementsByTagName('ol')[0];
var ulLis = ul.getElementsByTagName('li');
var olLis = ol.getElementsByTagName('li');
var target = 0,
leader = 0,
timer = null;
for (var i = 0; i < colorArr.length; i++) {
// 动态设置背景颜色
ulLis[i].style.background = colorArr[i];
olLis[i].style.background = colorArr[i];
// 将olLis属性绑定索引值
olLis[i].index = i;
olLis[i].onclick = function() {
// 点击索引,获取ul下的当前li距离顶部的距离
target = ulLis[this.index].offsetTop;
clearInterval(timer);
timer = setInterval(function() {
var step = (target - leader) / 10;
step = step > 0 ? Math.ceil(step) : Math.floor(step);
leader += step;
// 滚动条y方向到达leader位置
window.scrollTo(0, leader);
// 判断,清除定时器
if (Math.abs(target - leader) <= Math.abs(step)) {
window.scrollTo(0, target);
clearInterval(timer);
}
}, 15);
}
}
// 滚动事件,实时获取滚动条的位置
window.onscroll = function() {
// 实时获取leader的值
leader = getScrollTop();
}
// scrollTop兼容性处理
function getScrollTop() {
return window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
}
</script>
效果图:
9.3 client 系列
client
家族用于获取盒子可视区的大小。client
家族有clientWidth
、clientHeight
、clientLeft
、clientTop
。
1、clientWidth 和 clientHeight
-
clientWidth
:获取网页可视区域宽度 ;clientHeight
:获取网页可视区域高度; -
调用者不同,意义不同:
- 盒子调用,指盒子本身;
-
html/body
调用:可视区域大小
- 不包括
border
和margin
图解clientWidth和clientHeight:
clientWidth 和 clientHeight 兼容性封装:
function getClient() {
return {
width: window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth || 0,
height: window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight || 0
};
}
onresize事件:
onresize
事件会在窗口被调整大小的时候发生。
window.onresize = function(){
//事件处理程序
}
示例代码:模仿响应式布局 [ 39-client系列-模拟响应式.html ]
// 页面一进来的时候就执行一次,确定浏览器可视区域的宽度
responsive();
// 浏览器窗口调整触发事件
window.onresize = function() {
responsive();
};
// 获取浏览器宽度
function responsive() {
var pageWidth = getClientWidth();
if (pageWidth >= 960) {
//说明是pc
document.body.style.backgroundColor = "#B7F5DE";
} else if (pageWidth >= 640) {
//说明是平板
document.body.style.backgroundColor = "#CBF078";
} else {
// 说明是手机
document.body.style.backgroundColor = "#F59292";
}
}
// clientWidth 兼容性处理
function getClientWidth() {
return window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth || 0;
}
效果图:
2、clientX 和 clientY
-
clientX
:鼠标距离可视区域左侧距离(event
调用)event
:事件对象,下面会讲 -
clientY
:鼠标距离可视区域上侧距离(event
调用)
事件对象的时候,单独讲解
3、clientTop 和 clientLeft
-
clientTop
:盒子的上部border
的宽度 -
clientleft
:盒子的左部border
的宽度
用的很少很少,基本不会用到
9.4 screen 系列
clientWidth
获取的其实是浏览器窗口的宽度,想要获取用户显示的分辨率怎么办呢?
获取用户显示器分辨率有专门的方法:window.screen.width
和window.screen.Height
示例代码:获取显示器分辨率 [ 40-screen系列-获取显示器分辨率.html ]
document.write("屏幕分辨率为:" + window.screen.width + "*" + window.screen.height);
效果图:
1280*720 分辨率的情况下:
1920*1080 分辨率的情况下:
9.5 三大系列的区别
图解三大系列区别:
width 和 height:
-
clientWidth
/clientHeight
:- clientWidth =
width
+padding
; - clientHeight =
height
+padding
;
- clientWidth =
-
offsetWidth
/offsetHeight
:- offsetWidth =
width
+padding
+border
; - offsetHeight =
heigth
+padding
+border
;
- offsetWidth =
-
scrollWidth
/scrollHeight
:- scrollWidth = 内容宽度(不包含
border
); - scrollHeight = 内容高度(不包含
border
);
- scrollWidth = 内容宽度(不包含
top 和 left:
-
offsetTop
/offsetLeft
:- 调用者:任意元素。(盒子为主)
- 作用 :获取距离父系盒子中带有定位的距离。
-
scrollTop
/scrollLeft
:(盒子也可以调用,必须有滚动条)- 调用者:
document.body.scrollTop/.....(window)
- 作用:浏览器无法显示的部分(被卷去的部分)。
- 调用者:
-
clientY
/clientX
:(clientTop
/clientLeft
值是border
)- 调用者:
event.clientX(event)
- 作用:鼠标距离浏览器可视区域的距离(左、上)。
- 调用者:
10. 事件对象
10.1 事件对象的概述
在触发某个事件的时候,都会产生一个事件对象Event
,这个对象中包含所有与事件相关的一些信息,包括触发事件的元素,事件的类型以及其他与事件相关的信息。
比如:
- 鼠标事件触发时,事件对象中会包含鼠标的位置信息。
- 键盘事件触发时,事件对象中会包含按下的键相关的信息。
10.2 获取事件对象
既然事件对象中存储了这么多的信息,我们首先需要做的就是获取到这个事件对象。获取事件对象的时候,存在浏览器的兼容问题。
现代浏览器:
获取事件对象非常的简单,只需要在注册事件的时候,指定一个形参即可。这个形参就是我们想要获取到的事件对象。
btn.onclick = function(event){
// event就是事件对象,里面包含了事件触发时的一些信息。
// 触发事件的时候,事件是由浏览器调用,生成一个事件对象,里面包含了一些信息,当成实参传递进来了。
console.log(event);
}
IE678:
获取事件对象则是另一种方式,在事件里面,通过window.event
来获取事件对象
btn.onclick = function(){
// IE678通过window.event获取事件对象
// IE678浏览器在触发的事件的时候,生成一个事件对象,但是呢,并没有当成实参传过来。会给window.event这个属性。
var event = window.event;
console.log(event);
}
兼容性封装: [ 41-事件对象Event兼容性.html ]
btn.onclick = function(event){
//只要用到了事件对象,就要记得处理浏览器兼容性
event = event || window.event;
}
10.3 事件对象的常用属性
事件对象中有很多很多的属性,但是很多属性并不常用。我们经常用到的是鼠标位置信息 和键盘码 相关的信息。
打印event对象我们可以看到如下信息:
我们可以看到一个鼠标按下的时候,它的事件对象里面有这么多属性,但是最常用的也就是鼠标位置信息和键盘码相关的信息。
记录了鼠标位置信息的相关属性:
-
screenX
与screenY
:光标相对于屏幕左上角的水平位置与垂直位置。 -
clientX
与clientY
:光标相对于可视区左上角的水平位置和垂直位置。 -
pageX
与pageY
:光标相对于网页(文档document
)左上角的水平位置与垂直位置(推荐使用)
[ 42-事件对象-鼠标三种获取位置的属性.html ]
document.onclick = function(e) {
var e = e || window.event;
//获取鼠标的位置,相对的是可视区最左上角的点。(忽略滚动的距离)
console.log("client(" + e.clientX + "," + e.clientY + ")");
//获取鼠标的位置,相对的页面最左上角的位置 (计算滚动的距离)
console.log("page(" + e.pageX + "," + e.pageY + ")");
//获取鼠标的位置,相对的是屏幕最左上角的那个点
console.log("screen(" + e.screenX + "," + e.screenY + ")");
}
图解:
记录了键盘码的属性:
-
event.keyCode
:键盘按下的那个键的键盘码
10.4 pageX与pageY的兼容性
在鼠标事件中,记录鼠标位置信息的属性有很多,使用最多的还是pageX
与pageY
这两个属性,但是pageX
和pageY
存在浏览器兼容性问题。
在现代浏览器中: 直接通过事件对象就可以获得pageX
与pageY
document.onclick = function (event) {
event = event || window.event;
console.log(event.pageX+","+event.pageY);
}
在IE678中: 并没有pageX
与pageY
,但是我们可以通过scrollTop + clientY
的方式进行计算来获得pageY
。
document.onclick = function (event) {
event = event || window.event;
// 在IE678中使用document.documentElement.scrollTop就可以获取到scrollTop的值
alert(event.clientY + document.documentElement.scrollTop);
}
pageX与pageY的兼容性封装:
function getPage(event) {
return {
//在IE678中使用document.documentElement.scrollLeft就可以获取到scrollLeft的值
x:event.pageX || event.clientX + document.documentElement.scrollLeft,
y:event.pageY || event.clientY + document.documentElement.scrollTop
}
}
调用时:
getPage(event).x;
getPage(event).y;
示例代码:兼容性封装测试 [ 43-事件对象-pageX&PageY兼容性处理.html ]
<!-- 样式部分 -->
<style>
body {
height: 5000px;
}
</style>
<!-- js 部分 -->
<script>
document.onclick = function(event) {
event = event || window.event;
alert("当前坐标为(" + getPage(event).x + "," + getPage(event).y + ")");
}
function getPage(e) {
return {
x: e.pageX || e.clientX + document.documentElement.scrollLeft,
y: e.pageY || e.clientY + document.documentElement.scrollTop
}
}
</script>
10.5 案例:鼠标跟随
- 鼠标跟随,指的就是,鼠标后面有一张图片,会在页面中一直跟随鼠标
- 通过事件对象的属性,我们知道了有三种方法获取鼠标的位置信息,我们只要把鼠标的位置,赋值给后面跟随图片的位置,就可以实现图片一直跟随鼠标移动了
- 我们知道
clientX/Y
、screenX/Y
、pageX/Y
,都可以获取鼠标的位置,但是各有优劣,我们先使用pageX/Y
获取,上面我们已经处理pageX/Y
的兼容性了,所以这里直接使用 - 我们只需将获得的鼠标位置,赋值给定位后的图片,将图片绝对定位,再给页面注册鼠标移动事件,图片就会一直跟着鼠标移动。
[ 44-事件对象-跟随鼠标移动.html ]
<!-- 样式部分 -->
<style>
* {
margin: 0;
padding: 0;
}
body {
height: 5000px;
}
#follow {
position: absolute;
width: 160px;
}
</style>
<!-- html 部分 -->
<img src="../image/鼠标跟随/2.gif" alt="" id="follow">
<!-- js 部分 -->
<script>
var follow = document.getElementById("follow");
//给document注册一个鼠标移动事件
document.onmousemove = function(e) {
e = e || window.event;
console.log(e.clientX + " " + e.clientY);
follow.style.left = getPage(e).x + "px";
follow.style.top = getPage(e).y + "px";
}
function getPage(e) {
return {
x: e.pageX || e.clientX + document.documentElement.scrollLeft,
y: e.pageY || e.clientY + document.documentElement.scrollTop,
}
}
效果图:
通过效果图我们可以发现,绝对定位时,当鼠标移到最右边的时候,图片会撑大浏览器自动生成滚动条,那怎么办呢?
鼠标跟随优化版 [ 45-事件对象-跟随鼠标移动优化版.html ]
只要将图片固定定位,然后通过clientX/Y
,获取可视区的位置,将它的值赋值给图片就行了
<!-- 样式部分 -->
<style>
* {
margin: 0;
padding: 0;
}
body {
height: 5000px;
}
#follow {
position: fixed;
width: 160px;
}
</style>
<!-- html 部分 -->
<img src="../image/鼠标跟随/2.gif" alt="" id="follow">
<!-- js 部分 -->
<script>
var follow = document.getElementById("follow");
//给document注册一个鼠标移动事件
document.onmousemove = function(e) {
e = e || window.event;
console.log(e.clientX + " " + e.clientY);
follow.style.left = e.clientX + "px";
follow.style.top = e.clientY + "px";
}
</script>
效果图:
10.6 案例:拖拽效果
1、获取鼠标在盒子中的位置
当在盒子里面点击鼠标的时候,怎么获得这个鼠标在盒子中的位置呢?
没有直接的方法能够获取,但是我们可以通过:
获取鼠标的位置 - 盒子距离顶部和左边的距离 = 鼠标在盒子里面的距离
[ 46-事件对象-获取鼠标在盒子中的位置.html ]
<!-- 样式部分 -->
<style>
* {
margin: 0;
padding: 0;
}
#box {
width: 150px;
height: 150px;
background: #b7f5de;
margin: 200px;
}
</style>
<!-- html 部分 -->
<div id="box"></div>
<!-- js 部分 -->
<script>
var box = document.getElementById('box');
box.onclick = function(e) {
var e = e || window.event;
// 没有直接的方法能够获取,但是我们可以通过:
// 获取鼠标的位置 - 盒子距离顶部和左边的距离 = 鼠标在盒子里面的距离
var x = getPage(e).x - box.offsetLeft;
var y = getPage(e).y - box.offsetTop;
console.log("当前位置坐标:(" + x + "," + y + ")");
}
function getPage(e) {
return {
x: e.pageX || e.clientX + document.documentElement.scrollLeft,
y: e.pageY || e.clientY + document.documentElement.scrollTop
}
}
</script>
效果图:
2、拖拽效果
拖拽效果在网页很常见,比如一个注册框,弹出来的时候,你可以拖动它的位置。
新事件:
-
onmousedown
:当鼠标按下的时候触发 -
onmouseup
:当鼠标弹起的时候触发
实现思路:
- 给盒子注册按下鼠标(
onmousedown
)事件,获取鼠标在盒子里的位置; - 然后在里面注册页面移动鼠标(
onmousemove
)事件,鼠标移动时,将此时的鼠标距浏览器的距离减去鼠标在盒子中的距离后,赋值给盒子的top/left
- 给页面注册松开鼠标(
onmouseup
)事件,盒子应该停在那个位置,所以清除移动事件。
[ 47-事件对象-拖拽效果.html ]
<!-- 样式部分 -->
<style>
* {
margin: 0;
padding: 0;
}
body {
height: 4000px;
}
#box {
width: 150px;
height: 150px;
background: #B7F5DE;
position: absolute;
}
</style>
<!-- html 部分 -->
<div id="box"></div>
<!-- js 部分 -->
<script>
var box = document.getElementById('box');
// 鼠标按下
box.onmousedown = function(event) {
event = event || window.event;
// 记录按下的鼠标的位置
var x = getPage(event).x - box.offsetLeft;
var y = getPage(event).y - box.offsetTop;
// 按下的时候才触发鼠标移动事件
document.onmousemove = function(e) {
// 鼠标点击的时候应该减去鼠标按下时在盒子中的位置
box.style.left = getPage(e).x - x + "px";
box.style.top = getPage(e).y - y + "px";
}
}
// 鼠标松开
// 这里为什么不给盒子注册鼠标松开事件呢? 因为一旦有延迟,鼠标不在盒子上松开的时候,move事件就清除不掉了
// 所以直接给页面注册鼠标松开事件,只要鼠标松开,就清除move事件
document.onmouseup = function() {
// 上面注册的移动事件会一直触发,所以在鼠标松开的时候,我们应该将移动事件移除掉
document.onmousemove = null;
}
// 获取事件对象里的pageX/Y属性
function getPage(e) {
return {
x: e.pageX || e.clientX + document.documentElement.scrollLeft,
y: e.pageY || e.clientY + document.documentElement.scrollTop
}
}
</script>
效果图:
注意:
- 拖拽的时候,可能里面会有文字,当移动的时候,不小心获取文字焦点的时候,就不能清除鼠标移动事件了。
解决方法:
清除选中的文字
window.getSelection ? window.getSelection().removeAllRanges() : document.selection.empty();
10.7 案例:放大镜
放大镜在开发中是一个很常见的特效,但是所有的放大镜的实现效果都是一样。
图解放大镜原理:
实现思路:
- 当鼠标经过
smallBox
的时候,显示mask
和bigBox
; - 当鼠标离开
smallBox
的时候,隐藏mask
和bigBox
; - 获取鼠标在
smallBox
里面的位置; - 获得鼠标在
smallBox
里面的位置后,要减去 mask 一半的宽高,否则鼠标不在mask
中间显示; -
判断x的值限定
mask
的位置 :-
mask
在小盒子里面能够移动最大的宽度和高度0
; -
mask
在小盒子里面能够移动最大的宽度 =smallBox
的宽度 -mask
的宽度
-
- 设定 mask 的位置;
-
让大图片等比例的跟着动 :
-
bigImg
能够移动的距离 /mask
能移动的距离 = 大图片移动的距离 /mask
移动的距离
-
思路图解:
示例代码: [ 48-事件对象-放大镜效果.html ]
<!-- 样式部分 -->
<style>
* {
margin: 0;
padding: 0;
}
#box {
width: 350px;
height: 350px;
margin: 100px;
border: 1px solid #ccc;
position: relative;
}
#bigBox {
width: 400px;
height: 400px;
position: absolute;
top: 0;
left: 360px;
border: 1px solid #ccc;
overflow: hidden;
display: none;
}
.mask {
width: 175px;
height: 175px;
background-image: url(../image/放大镜/1.png);
position: absolute;
top: 1px;
left: 1px;
cursor: move;
display: none;
}
#smallBox {
position: relative;
}
#box img {
vertical-align: top;
}
#bigBox img {
position: absolute;
}
</style>
<!-- html 部分 -->
<div id="box">
<div id="smallBox">
<img src="../image/放大镜/img.jpg" width="350" alt="">
<div id="mask" class="mask"></div>
</div>
<div id="bigBox">
<img src="../image/放大镜/img.jpg" width="800" id="bigImg" alt="" />
</div>
</div>
<!-- js 部分 -->
<script>
var box = document.getElementById('box');
var smallBox = document.getElementById('smallBox');
var bigBox = document.getElementById('bigBox');
var mask = document.getElementById('mask');
var bigImg = document.getElementById('bigImg');
// 当鼠标 经过smallBox的时候,显示 mask 和 bigBox
smallBox.onmouseover = function() {
mask.style.display = "block";
bigBox.style.display = "block";
};
// 当鼠标 离开smallBox的时候,隐藏 mask 和 bigBox
smallBox.onmouseout = function() {
mask.style.display = "none";
bigBox.style.display = "none";
};
// 鼠标在smallBox里面移动,移动 mask 和 bigImg
smallBox.onmousemove = function(e) {
var e = e || window.event;
// mask 跟着鼠标移动
// 1- 获取鼠标在smallBox里面的位置
var spaceX = getPage(e).x - box.offsetLeft;
var spaceY = getPage(e).y - box.offsetTop;
// 2- 获得鼠标在smallBox里面的位置后,要减去mask一半的宽高,否则鼠标不在mask中间显示
var x = spaceX - mask.offsetWidth / 2;
var y = spaceY - mask.offsetHeight / 2;
// 3- 判断x的值 限定 mask的位置
// mask 在小盒子里面能够移动最大的宽度和高度 0
if (x <= 0) {
x = 0;
}
if (y <= 0) {
y = 0;
}
// mask 在小盒子里面能够移动最大的宽度 = smallBox的宽度 - mask的宽度
var maskMaxX = smallBox.offsetWidth - mask.offsetWidth;
var maskMaxY = smallBox.offsetHeight - mask.offsetHeight;
if (x >= maskMaxX) {
x = maskMaxX;
}
if (y >= maskMaxY) {
y = maskMaxY;
}
// 4- 设定mask的位置
mask.style.left = x + "px";
mask.style.top = y + "px";
// 5- 让大图片等比例的跟着动
// bigImg 能够移动的距离 / mask 能移动的距离 = 大图片移动的距离 / mask移动的距离
// rate :比例
var xRate = (bigImg.offsetWidth - bigBox.offsetWidth) / maskMaxX;
bigImg.style.left = -xRate * x + "px";
var yRate = (bigImg.offsetHeight - bigBox.offsetHeight) / maskMaxY;
bigImg.style.top = -yRate * y + "px";
};
// pageX/Y 兼容性处理
function getPage(e) {
return {
x: e.pageX || e.clientX + document.documentElement.scrollLeft,
y: e.pageY || e.clientY + document.documentElement.scrollTop
}
}
</script>
效果图:
11. 注册事件
前面我们已经知道了许多触发事件的名称,但是我们只知道了一种注册事件的方式,就是"on
+ 事件名称",下面会为大家再介绍一种注册事件的方式:addEventListener
。
11.1 on + 事件名称 方式
onclick
、onmouseover
这种on
+ 事件名称的方式注册事件几乎所有的浏览器都支持。
注册事件:
box.onclick = function(){
//事件处理程序
}
移除事件:
box.onclick = null;
on + 事件名称注册事件的缺点:
同一个元素同一类型的事件,只能注册一个,如果注册了多个,会出现覆盖问题。
document.onclick = function(){
console.log("呵呵");
}
document.onclick = function(){
console.log("哈哈"); // 最后打印的是 "哈哈",呵呵会被覆盖掉
}
11.2 addEventListener 方式
现代浏览器支持的注册事件的新方式,这种方式注册的事件不会出现覆盖问题。以后在手机端就用这种注册事件的方式。
1、addEventListener 注册事件的语法:
// add:添加 Event:事件 Listener:监听器
// 三个参数:
// 1. type:事件类型 "click","mouseover"... 不要再加 on了
// 2. 函数:事件触发的时候要执行的程序
// 3. useCapture(是否使用事件捕获) :true & false 默认是false
document.addEventListener(type,function(){
// 事件处理程序
},useCapture);
之前我们说过window.onload
,只能注册一个就是这个原因,因为"on +
"注册方式会覆盖。所以如果真的需要执行两个window.onload
事件的时候,我们就可以使用addEventListener
注册:
window.addEventListener("load",function(){
// 预加载函数 1
});
window.addEventListener("load",function(){
// 预加载函数 2
});
示例代码: [ 49-注册事件-addEventListener.html ]
// 给页面注册点击事件后,会同时打印 "呵呵呵","哈哈哈"
document.addEventListener("click", function() {
console.log("呵呵呵");
});
document.addEventListener("click", function() {
console.log("哈哈哈");
});
2、removeEventListener 移除事件的语法:
// remove:移除 Event:事件 Listener:监听器
// 三个参数:
// 1. type:事件类型 "click","mouseover"
// 2. 函数名:要移除的那个函数
// 3. useCapture(是否使用事件捕获) :true & false 默认是false
document.addEventListener(type,fn,useCapture);
注意:
要想一个事件能够被移除,在它注册事件的时候,执行函数必须要有函数名,不能是匿名函数。因为移除事件的时候,就是移除的这个函数名。
示例代码: [ 50-移除事件-removeEventListener.html ]
// 第二个点击事件就被移除了
document.addEventListener("click", fn1);
document.addEventListener("click", fn2);
function fn1() {
console.log("呵呵呵");
};
function fn2() {
console.log("哈哈哈");
}
// 移除第二个点击事件
document.removeEventListener("click", fn2);
3、IE678兼容性问题:
IE678
不支持addEventListener
与removeEventListen
两个方法,但是支持attachEvent
与detachEvnet
。
attachEvent注册事件的语法:
// attach :附上;系上;贴上
// 参数:
// 1. type:事件类型 需要加上on "onclick","onmouseenter"...
// 2. 函数fn:需要执行的那个事件函数
attachEvent(type, function(){
// 事件处理程序
});
attach注册时间的时候,事件类型要加上on
,没有为什么,IE就这样
detachEvent的用法:
// detach :脱离
// 参数:
// 1. type:事件类型 需要加上on "onclick","onmouseenter"...
// 2. 函数名: 需要执行的那个事件函数名
detachEvent(type, fn);
示例代码: [ 51-注册事件-IE678方法.html ]
// IE678 下运行
document.attachEvent("onclick", fn1);
document.attachEvent("onclick", fn2);
function fn1() {
alert("123");
};
function fn2() {
alert("456");
};
// 移除第一个注册事件
document.detachEvent("onclick", fn1);
4、兼容性处理:
注册事件的新方式的解决了事件覆盖的问题,但是存在浏览器兼容性问题,因此可以进行兼容性封装。
// 添加事件兼容性封装
function addEvent(element, type, fn) {
// 能力检测
if (element.addEventListener) {
element.addEventListener(type, fn);
} else if (element.attachEvent) {
element.attachEvent("on" + type, fn);
} else {
//如果都不行,那就用on方式
element["on" + type] = fn;
}
};
//移除事件兼容性封装
function removeEvent(element, type, fn) {
if (element.removeEventListener) {
element.removeEventListener(type, fn);
} else if (element.detachEvent) {
element.detachEvent("on" + type, fn);
} else {
element["on" + type] = null;
}
}
示例代码: [ 52-注册事件-封装兼容性.html ]
// 添加事件兼容性封装
function addEvent(element, type, fn) {
// 能力检测
if (element.addEventListener) {
element.addEventListener(type, fn);
} else if (element.attachEvent) {
element.attachEvent("on" + type, fn);
} else {
//如果都不行,那就用on方式
element["on" + type] = fn;
}
};
//移除事件兼容性封装
function removeEvent(element, type, fn) {
if (element.removeEventListener) {
element.removeEventListener(type, fn);
} else if (element.detachEvent) {
element.detachEvent("on" + type, fn);
} else {
element["on" + type] = null;
}
}
function fn1() {
alert("呵呵");
}
function fn2() {
alert("哈哈");
}
addEvent(document, "click", fn1);
addEvent(document, "click", fn2);
removeEvent(document, "click", fn1);
12. 事件冒泡和事件捕获
事件冒泡和事件捕获其实可以理解成一样东西,就是当父级元素和子元素都具有点击事件的时候,点击触发子级元素的时候父级元素也会被触发。事件冒泡是IE678
在处理事件间机制的一种说法,它执行的顺序是由内向外的,就是从子元素一直到window
。 事件捕获是火狐在处理机制时的一种说法,它执行的顺序是由外向内的,就是window
一直到子元素。
图解事件冒泡和事件捕获:
12.1 事件冒泡
当一个元素的事件被触发时,同样的事件将会在该元素的所有祖先元素中依次被触发。这一过程被称为事件冒泡。说白了就是:当父元素和子元素都设置了点击事件的时候,触发子盒子点击事件的时候,父盒子的点击事件也会被执行。
示例代码: [ 53-事件冒泡.html ]
<!-- 样式部分 -->
<style>
#big-box {
width: 500px;
height: 500px;
margin: 100px auto;
vertical-align: middle;
text-align: center;
border: 1px solid transparent;
background-color: aquamarine;
}
#box {
width: 300px;
height: 300px;
margin-top: 100px;
display: inline-block;
background-color: darkorange;
}
</style>
<!-- html 部分 -->
<div id="big-box">
<div id="box"></div>
</div>
<!-- js 部分 -->
<script>
var bigBox = document.getElementById('big-box');
var box = document.getElementById('box');
document.onclick = function() {
document.body.style.background = "#000";
}
bigBox.onclick = function() {
bigBox.style.background = "fuchsia";
}
box.onclick = function() {
// 当我们点击box的时候,bigBox、body的点击事件 也会被触发
box.style.background = "lightgreen";
}
</script>
效果图:
我们会发现,当点击中间小盒子的时候,他的父级元素,只要有点击事件的,都被触发了,这就是事件冒泡。
12.2 阻止事件冒泡
正常情况下,我们肯定不想,点击子元素触发事件的时候,父元素事件也跟着触发,所以我们就要知道一个知识点:阻止事件冒泡。在阻止事件冒泡中是存在兼容性的:
正常浏览器:
前面我们知道了事件触发的时候,会有一个事件对象,我们只要给事件对象加上:stopPropagation
方法即可。stopPropagation
方法不仅可以阻止事件冒泡,还可以阻止事件委托
element.onclick = function (e) {
e = event || window.event;
//stop :停止 propagation:传播
e.stopPropagation();
}
IE678浏览器:
ie是给事件对象的属性cancelBubble
赋值
element.onclick = function (e) {
e = event || window.event;
e.cancelBubble = true;
}
兼容性处理:
// 能力检测
element.onclick = function (e) {
e = event || window.event;
if(e.stopPropagation){
e.stopPropagation();
}else {
e.cancelBubble = true;
}
}
12.3 事件捕获
事件捕获(capture
)是火狐浏览器提出来的,IE678
不支持事件捕获(基本上,我们都是用事件冒泡)。事件的处理将从DOM
层次的根开始,而不是从触发事件的目标元素开始,事件被从目标元素的所有祖先元素依次往下传递。
当addEventListener
第三个参数为true时,表示事件捕获:
element.addEventListener("click", function () {
console.log("哈哈哈");
},true);
12.4 事件流
事件流就是事件的三个阶段,首先发生的是捕获阶段,然后是目标阶段,最后才是冒泡阶段,对于捕获和冒泡,我们只能干预其中的一个,通常来说,我们可能会干预事件冒泡阶段,而不去干预事件捕获阶段。
- 事件的捕获阶段
- 事件的目标阶段
- 事件的冒泡阶段
注意:
其实这三个阶段在执行是都会发生,但是冒泡和捕获只能执行一个,所以通过usecaptrue = false
可以让捕获阶段执行但是不触发。
12.5 键盘事件
对于鼠标事件,事件对象中有一系列的XY
记录了鼠标的位置信息。而键盘事件中,事件对象有一个event.keyCode
属性,记录了按下去的键的键盘码。[ 54-键盘事件-键盘码.html ]
document.onkeydown = function (e) {
// 键盘按下的时候触发的事件对象
console.log(e);
// keyCode: 键盘码
console.log(e.keyCode);
}
键盘码对应值:
常见的键盘事件:
-
onkeydown
:键盘按下时触发 -
onkeyup
:键盘弹起时触发
示例代码: [ 55-键盘事件-ESC键关闭遮罩层弹出框.html ]
// 点击登陆按钮
var btn = document.getElementById('btn');
// 登陆框
var login = document.getElementById('login');
// 遮罩层
var bg = document.getElementById('bg');
btn.addEventListener("click", function() {
login.style.display = "block";
bg.style.display = "block";
});
document.addEventListener("keyup", function(e) {
e = e || window.event;
// ESC 键的键盘码是27
if (e.keyCode == 27) {
login.style.display = "none";
bg.style.display = "none";
}
});
效果图:
12.6 案例:弹幕效果
我们都看过直播,都知道弹幕的效果,下面我们就模拟直播中的弹幕做个小案例。
实现步骤:
- 获取输入框的的
value
值;并生成span
标签 - 将
span
标签添加到 页面中,随机颜色 随机高度span
动画从右向左 - 到达最左边的时候删除
span
标签(不删除会随着输入的内容越来越多影响性能)
示例代码:
<style>
html,
body {
margin: 0px;
padding: 0px;
width: 100%;
height: 100%;
font-family: "微软雅黑";
font-size: 62.5%;
background: #ccc;
}
#page {
width: 100%;
height: 100%;
position: relative;
overflow: hidden;
}
#import {
width: 100%;
height: 60px;
background: #666;
position: fixed;
bottom: 0px;
}
#content {
display: inline-block;
width: 430px;
height: 40px;
position: absolute;
left: 0px;
right: 0px;
top: 0px;
bottom: 0px;
margin: auto;
}
.title {
display: inline;
font-size: 25px;
vertical-align: bottom;
color: #fff;
}
#text {
border: none;
width: 300px;
height: 30px;
border-radius: 5px;
font-size: 15px;
padding-left: 10px;
}
#btn {
width: 60px;
height: 30px;
background: #f90000;
border: none;
color: #fff;
font-size: 15px;
}
span {
width: 300px;
height: 40px;
position: absolute;
overflow: hidden;
color: #000;
font-size: 25px;
line-height: 37.5px;
cursor: pointer;
white-space: nowrap;
}
</style>
<!-- html 部分-->
<div id="page">
<div id="import">
<div id="content">
<p class="title">吐槽</p>
<input type="text" name="" id="text" placeholder="发送弹幕,与小伙伴一起互动!">
<button id="btn">发射</button>
</div>
</div>
</div>
<!-- js 部分 -->
<script src="../js/animate-callback.js"></script>
<script>
var page = document.getElementById('page');
var text = document.getElementById('text');
var btn = document.getElementById('btn');
// 定义一个颜色数组
var colorArr = ['#FF895D', '#78BBE6', '#FF4273', '#00BBF0', '#7C73E6', '#EE2B47', '#F60C86', '#9870FC', '#F96D00', '#303481'];
btn.onclick = function() {
// 点击发射按钮的时候,要做的事情:
// 1- 获取 input 的 value 值;并生成 span 标签
// 2- 将 span 标签添加到 page中,随机颜色 随机高度 span动画从右向左
// 3- 到达最左边的时候删除 span 标签(不删除会随着输入的内容越来越多影响性能)
// a. 获取input的值,并清空
var content = text.value;
// b. 生成span标签 添加到 page中
if (content != "" && content.trim()) {
text.value = '';
var span = document.createElement('span');
page.appendChild(span);
span.innerText = content;
// c. 随机颜色
var randomColor = parseInt(Math.random() * colorArr.length);
span.style.color = colorArr[randomColor];
// d. 随机高度 随机位置
var randomHeight = parseInt(Math.random() * 201);
span.style.top = randomHeight + "px";
span.style.left = getClient().width + "px";
// e. 动画效果
animate(span, -300, function() {
// f. 动画执行完成之后,回调函数中移除执行完的 span
page.removeChild(span);
});
}
};
// text 注册键盘按下事件 当为回车按键的时候,执行发射操作
text.onkeydown = function(e) {
e = e || window.event;
if (e.keyCode == 13) {
btn.click();
}
}
// 获取可视区域宽高
function getClient() {
return {
width: window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth || 0,
height: window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight || 0
}
}
</script>
效果图:
13. 瀑布流
瀑布流,又称瀑布流式布局。是比较流行的一种网站页面布局,视觉表现为参差不齐的多栏布局,随着页面滚动条向下滚动,这种布局还会不断加载数据块并附加至当前尾部。
1、首先瀑布流所有的图片应该保持宽度一致,高度是由内容决定。
左浮动的话,我们可以看到第6
个盒子直接就在第4
个盒子旁边停下了,因为第4
个高度最高,挡住了它左浮动的去路。第6
个盒子是第2
行的最后一个,所以第7
个盒子只能在第3
行排列了。当排到第12
个盒子的时候,盒子会以第11
个盒子的位置为基础左浮动(这就是第12
个盒子为什么没有‘跳到’第9
个盒子下面的原因),碰到第8
个盒子后又被挡住了。
通过定位的方式是我们实现瀑布流的最基本的原理,只要我们动态的设置它的top
值、left
值,就能让它排列。
2、定位后确定浏览器显示区域内,一行能放多少列图片盒子。
- 获取页面的宽度
- 获取图片盒子的宽度
- 显示的列数 = 页面宽度/图片盒子宽度
column = pageWidth / itemWidth
3、为了美观我们可以加上一个空隙
- 显示的列数 = 页面宽度/(图片盒子宽度+间隙);
column = pageWidth / (itemWidth + gap);
4、 确定列数之后,排列第一行
- 下面还有很多图片盒子,我们先要排列第
1
行,所以在for
循环里就要判断一下,当i
(所有图片盒子的索引) <column
(显示列数)的时候,说明在第1
行; - 知道在第
1
行之后,动态设置每个图片盒子的left
值就能排好第1
行。 left = i * ( itemWidth + gap );
5、第1行排列好之后,获取第1行所有图片盒子的高度
- 需要定义一个数组
arr
,将获取到的高度存在数组中,因为第2
行排列的时候需要考虑top
值,此时只能根据第1
行图片盒子的高度来设置; - 获取图片高度的时候要注意,程序必须写在入口函数
onload
里面,因为图片的加载特性是:等页面都加载完之后才去请求加载,所以不写在入口函数里可能会出现高度获取不到的情况。
6、排列第2行
- 获取到刚刚数组中,高度最小的那一列,将第
2
行的第1
个图片盒子放置在它的下方; - 此时的
left
值就是高度最小列的offsetLeft
;top
值就是:第1
行高度最小列的高度(为了布局美观可以加上上下间隙gap
)。 - 记录下高度最小列的索引
index
,后面计算会用到; - 设置完成之后,会发现后面所有的图片都叠在这个高度最小列的下面,原因就是此时的最小列高度没有改变,应该加上下面图片的高度,得出一个新高度。
7、改变最小列当前高度
- 此时的这一列高度其实已经发生改变了,所以需要将新高度赋值给数组
- 当前高度最小列的高度 = 当前高度最小列的高度 + 间隙 + 下面图片盒子的高度
8、触发resize事件
- 将整个设置样式的部分封装成一个函数,在
onload
里面注册一个resize
事件,只要页面一发生改变,就触发样式部分的代码。 - 实时改变
pageWidth
的宽度,这样瀑布流就会是一个响应式的效果了
9、懒加载效果
- 目前我们用的是
30
张图片,假如一个页面中有几百张图片的时候,我们不可能等到它都加载完再显示,所有这里引入一个懒加载的概念,我们规定第30
张为显示的最后一张图片,当滚动条滚动到30
张的时候,应该加载下一批图片。 - 即页面可视区高度+滚动条卷去的高度 = 第
30
图片的offsetTop
;的时候加载下面的图片。
完整代码:
<!-- 样式部分 -->
<style>
* {
margin: 0;
padding: 0;
position: relative;
}
img {
width: 220px;
display: block;
}
.item {
box-shadow: 2px 2px 2px #999;
position: absolute;
}
</style>
<!-- html 部分 -->
<div id="box">
<div class="item"><img src="../image/瀑布流/001.jpg" alt=""></div>
.
.
.
<div class="item"><img src="../image/瀑布流/030.jpg" alt=""></div>
</div>
<!-- js 部分 -->
<script>
var box = document.getElementById('box');
var items = box.children;
// 定义每一列之间的间隙 为10像素
var gap = 10;
window.onload = function() {
// 一进来就调用一次
waterFall();
// 封装成一个函数
function waterFall() {
// 1- 确定列数 = 页面的宽度 / 图片的宽度
var pageWidth = getClient().width;
var itemWidth = items[0].offsetWidth;
var columns = parseInt(pageWidth / (itemWidth + gap));
var arr = [];
for (var i = 0; i < items.length; i++) {
if (i < columns) {
// 2- 确定第一行
items[i].style.top = 0;
items[i].style.left = (itemWidth + gap) * i + 'px';
arr.push(items[i].offsetHeight);
} else {
// 其他行
// 3- 找到数组中最小高度 和 它的索引
var minHeight = arr[0];
var index = 0;
for (var j = 0; j < arr.length; j++) {
if (minHeight > arr[j]) {
minHeight = arr[j];
index = j;
}
}
// 4- 设置下一行的第一个盒子位置
// top值就是最小列的高度 + gap
items[i].style.top = arr[index] + gap + 'px';
// left值就是最小列距离左边的距离
items[i].style.left = items[index].offsetLeft + 'px';
// 5- 修改最小列的高度
// 最小列的高度 = 当前自己的高度 + 拼接过来的高度 + 间隙的高度
arr[index] = arr[index] + items[i].offsetHeight + gap;
}
}
}
// 页面尺寸改变时实时触发
window.onresize = function() {
waterFall();
};
// 当加载到第30张的时候
window.onscroll = function() {
if (getClient().height + getScrollTop() >= items[items.length - 1].offsetTop) {
// 模拟 ajax 获取数据
var datas = [
"../image/瀑布流/001.jpg",
...
"../image/瀑布流/030.jpg"
];
for (var i = 0; i < datas.length; i++) {
var div = document.createElement("div");
div.className = "item";
div.innerHTML = '<img src="' + datas[i] + '" alt="">';
box.appendChild(div);
}
waterFall();
}
};
};
// clientWidth 处理兼容性
function getClient() {
return {
width: window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth,
height: window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
}
}
// scrollTop兼容性处理
function getScrollTop() {
return window.pageYOffset || document.documentElement.scrollTop;
}
</script>
效果图:
14. 正则表达式
正则表达式:用于匹配规律规则的表达式,正则表达式最初是科学家对人类神经系统的工作原理的早起研究,现在在编程语言中有广泛的应用,经常用于表单校验,高级搜索等。
14.1 创建正则表达式
js中的正则表达式用RegExp对象表示,可以通过RegExp()构造函数来创建RegExp对象,不过更多的是通过字面量语法来创建。
/.../
正则表达式必须要有斜杠,它表示的是正则构成
构造函数的方式:
var regExp = new RegExp(/abc/); // 判断是否包含字符abc
正则字面量:/.../
var regExp = /abc/;
正则的使用:
正则表达式有一个方法:test();
有一个返回值,是布尔类型。决定参数是否符合正则表达式
console.log(/abc/.test("abc")); // true
示例代码: [ 57-正则表达式-创建正则表达式.html ]
var reg = new RegExp(/abc/);
console.log(reg.test("abc")); // ture
console.log(reg.test("efg")); // false
console.log(reg.test("abcd")); // true 只要包含abc就正确 后面会细讲
console.log(/123/.test(123)); // true
14.2 元字符
正则表达式由一些普通字符
和元字符
组成,普通字符包括大小写字母、数字等,而元字符则具有特殊的意义。元字符:^
...
14.3 正则内部类
1、预定义类
正则表达式中具有特殊意义的字符。
预定义类 | 正则形式 | 释义 |
---|---|---|
. |
[^\n\r] |
除了换行和回车之外的任意字符 |
\d |
[0-9] |
数字字符 |
\D |
[^0-9] |
非数字字符 |
\w |
[a-zA-Z0-9_] |
单词字符(所有的字母数字和'_') |
\W |
[^a-zA-Z0-9_] |
非单词字符 |
\s |
[\f\r\n\t\v] |
不可见字符,包含空格 |
\S |
[^\f\r\n\t\v] |
可见字符 |
示例代码: [ 59-正则表达式-预定义类.html ]
console.log("----------------'.'---------------");
console.log(/./.test('\n')); // false
console.log(/./.test('2s#2')) // true
console.log("----------------'\\d'---------------");
console.log(/\d/.test(123)); // true
console.log(/\d/.test('123abc')); // true
console.log(/\d/.test('abc')); // false
console.log("----------------'\\D'---------------");
console.log(/\D/.test(123)); // false
console.log(/\D/.test('123abc')); // true
console.log(/\D/.test('abc')); // true
console.log("----------------'\\w'---------------");
console.log(/\w/.test(123)); // true
console.log(/\w/.test('123abc_')); // true
console.log(/\w/.test(' ')); // false
console.log("----------------'\\W'---------------");
console.log(/\W/.test(123)); // false
console.log(/\W/.test('123abc_')); // false
console.log(/\W/.test(' ')); // true
console.log("----------------'\\s'---------------");
console.log(/\s/.test(123)); // false
console.log(/\s/.test('123abc_')); // false
console.log(/\s/.test(' ')); // true
console.log("----------------'\\S'---------------");
console.log(/\S/.test(123)); // true
console.log(/\S/.test('123abc_')); // true
console.log(/\S/.test(' ')); // false
2、简单类 [ 60-正则表达式-简单类.html ]
/ /
中什么特殊符号也不写,就是简单类
直接字符: 必须是完整的包含正则选项,只能多不能少
console.log(/levi/.test('levi')); // true
console.log(/levi/.test('le')); // false
console.log(/levi/.test('levi_lxh')); // true
只要完整的包含了“levi”即可(有他就行)
加上[]
: 只要包含里面任何一个即可 比如/[abcd]/
=> a,b,c,d
只要匹配项的里面有任意一项符合就返回true
console.log(/[levi]/.test("le")); // true
console.log(/[levi]/.test("less")); // true
console.log(/[levi]/.test("kill")); // true
console.log(/[levi]/.test("ss")); // false
3、负向类
元字符^
必须出现在中括号内,表示非、取反的意思[^]
。
示例代码: [ 61-正则表达式-负向类.html ]
console.log(/[^levi]/.test("l")); // false
console.log(/[^levi]/.test("le")); // false
console.log(/[^levi]/.test("ec")); // true
console.log(/[^levi]/.test("levi")); // false
console.log(/[^levi]/.test("levi-lxh")); // true
console.log(/[^levi]/.test("lxh")); // true
条件项[^levi]
,表示不能有l,e,v, i任意组合,当匹配项小于等于条件项并且包含条件项的时候,返回false
,当返回项不完全包含条件项的时候,返回true
4、范围类:
有时候匹配的东西过多,而且类型又相同,全部输入太麻烦,我们可以在中间加个横线-
。
示例代码: [ 62-正则表达式-范围类.html ]
console.log(/[a-d]/.test("a")); // true
console.log(/[a-d]/.test("ac123")); // true
console.log(/[a-d]/.test("efg")) // false
console.log(/[a-d]/.test("123")) // false
5、组合类
用中括号匹配不同类型的单个字符串
示例代码: [ 63-正则表达式-组合类.html ]
console.log(/[a-f1-6]/.test('abs')); // true
console.log(/[a-f1-6]/.test('12')); // true
console.log(/[a-f1-6]/.test('sg8')); // false
14.4 正则边界
我们前面学习的正则只要有满足的条件的就会返回true,并不能做到精确的匹配。正则边界就是以什么开始,以什么结束,进行精确匹配。
1、以什么开始:
^
元字符在//
里面的时候,表示的是必须以...开始
,^
在中括号[]
内才表示取反、非的意思。
console.log(/^levi/.test('lxhlevi')); // false
console.log(/^levi/.test('levilxh')); // true
console.log(/^levi/.test('lxhlevilxh')); // false
console.log(/^levi/.test('levilevi')); // true
2、以什么结尾:
$ 元字符表示的是必须以...结尾
console.log(/levi$/.test('lxhlevi')); // true
console.log(/levi$/.test('levilxh')); // false
console.log(/levi$/.test('lxhlevilxh')); // false
console.log(/levi$/.test('levilevi')); // true
3、精确匹配:
^...$
表示的是精确匹配,匹配项必须是^、$
之间的内容
console.log(/^levi$/.test('lxhlevi')); // false
console.log(/^levi$/.test('levilxh')); // false
console.log(/^levi$/.test('lxhlevilxh')); // false
console.log(/^levi$/.test('levilevi')); // false
console.log(/^levi$/.test('levi')); // true
console.log(/^\d$/.test('111')); // false \d表示的是0-9当中的一位数
console.log(/^\d$/.test('1')); // true
[ 64-正则表达式-正则边界.html ]
14.5 量词
量词用来控制出现的次数,一般来说量词和边界会一起使用
1、量词 *
:
表示能够出现0
次,或者跟多的次数,x >= 0
。
// 可以出现0次或者多次 要么不出现 要么只能出现 a
console.log(/^a*$/.test('abc')); // false
console.log(/^a*$/.test('bbb')); // false
console.log(/^a*$/.test('aab')); // false
console.log(/^a*$/.test('aaa')); // true
console.log(/^a*$/.test('a')); // true
console.log(/^a*$/.test('')); // true
2、量词 +
:
表示能够出现1
次或者多次,x >= 1
。
// +表示 可以出现1次或者1次以上
console.log(/^a+$/.test("a")); //true
console.log(/^a+$/.test("")); //false
console.log(/^a+$/.test("b")); //false
console.log(/^a+$/.test("aa")); //true
console.log(/^a+$/.test("aab")); //false
3、量词 ?
:
表示能够出现0
次或者1
次,x=0
或者x=1
。
// ? 表示可以出现0次或者1次
console.log(/^a?$/.test("a")); //true
console.log(/^a?$/.test("")); //true
console.log(/^a?$/.test("b")); //false
console.log(/^a?$/.test("aa")); //false
console.log(/^a?$/.test("aab")); //false
4、量词 {n}
:
表示能够出现n
次
// * ==> {0,}
console.log(/^a{0,}$/.test('a')); // true
console.log(/^a{0,}$/.test('aa')); // true
console.log(/^a{0,}$/.test('')); // true
console.log(/^a{0,}$/.test('abc')); // fasle
console.log(/^a{0,}$/.test('aaab')); // fasle
5、量词 {n,}
:
表示能够出现n
次或者n
次以上
// + ==> {1,}
console.log(/^a{1,}$/.test('a')); // true
console.log(/^a{1,}$/.test('aa')); // true
console.log(/^a{1,}$/.test('')); // fasle
console.log(/^a{1,}$/.test('abc')); // fasle
console.log(/^a{1,}$/.test('aaab')); // fasle
6、量词 {n,m}
:
表示能够出现n-m次
// ? ==> {0,1}
console.log(/^a{0,1}$/.test("a")); //true
console.log(/^a{0,1}$/.test("")); //true
console.log(/^a{0,1}$/.test("b")); //false
console.log(/^a{0,1}$/.test("aa")); //false
console.log(/^a{0,1}$/.test("aab")); //false
[ 65-正则表达式-量词.html ]
14.6 括号总结
1、{}
大括号限定出现的次数
// 表示的是 n 重复两次
console.log(/chuan{2}/.test("chuanchuan")); // false
console.log(/chuan{2}/.test("chuann")); // true
console.log(/chuan{2}/.test("chuann123123")); // true
2、[]
表示一个字符出现的位置
console.log(/^[fb]oot$/.test("foot")); // true
console.log(/^[fb]oot$/.test("boot")); // true
3、()
用来提升优先级
console.log(/^(chuan){2}$/.test("chuanchuan")); // true
14.7 正则表达式综合案例
1、验证座机号码: [ 67-正则案例-验证座机号码.html ]
- 直辖市座机号码:
021-88888888
; - 普通地区做急哦号码:
0515-12345678
; - 前区可以是三位也可以是四位,第一位必须是
0
,后区必须是8
位;
var reg = /^0\d{2,3}-\d{8}$/;
console.log(reg.test('021-12345678')); // true
console.log(reg.test('0515-88888888')); // true
console.log(reg.test('0515-888880888')); // false
2、验证中文姓名 [ 68-正则案例-验证中文姓名.html ]
- 只能是汉字
- 长度
2-6
位之间 - 汉字范围
[\u4e00-\u9fa5]
-
unicode
编码:万国码,其中\u4e00-\u9fa5
表示的就是包含所有汉字的unicode
编码
var nameReg = /^[\u4e00-\u9fa5]{2,6}$/;
console.log(nameReg.test('莫')); // false
console.log(nameReg.test('小泽玛利亚')); // true
console.log(nameReg.test('柯南')); // true
3、验证邮箱 [ 69-正则案例-验证邮箱.html ]
- 前面是字母或者数字
- 必须有
@
-
@
后面是字母或者数字 - 必须有
.
-
.
后面是字母或者数字
var mailBoxReg = /^\w+@\w+(\.\w+)+$/;
console.log(mailBoxReg.test('18888888@qq.com')); // true
4、验证手机号码 [ 70-正则案例-验证手机号码.html ]
-
11
位数字组成 - 号段
13[0-9]
147
15[0-9]
177[0178]
18[0-9]
var mobileReg = /^(13[0-9]|147|15[0-9]|17[0178]|18[0-9])\d{8}$/;
console.log(mobileReg.test(15812345678)); // true
5、验证QQ [ 71-正则案例-验证qq.html ]
- 只能是数字
- 开头不能是
0
- 长度为
5-11
位
var qqReg = /^[1-9]\d{4,10}$/;
console.log(qqReg.test(18888888)); // true
6、完整版表单验证 [ 72-正则案例-表单验证综合案例.html ]
<!-- 样式部分 -->
<style>
body {
background: #ccc;
}
.container {
margin: 100px auto;
width: 400px;
padding: 50px;
line-height: 40px;
border: 1px solid #999;
background: #efefef;
}
label {
width: 40px;
display: inline-block;
}
span {
color: red;
}
span {
margin-left: 30px;
font-size: 12px;
}
</style>
<!-- html 部分 -->
<div class="container">
<label>Q Q</label><input type="text" id="inp1"><span></span><br/>
<label>手机</label><input type="text" id="inp2"><span></span><br/>
<label>邮箱</label><input type="text" id="inp3"><span></span><br/>
<label>座机</label><input type="text" id="inp4"><span></span><br/>
<label>姓名</label><input type="text" id="inp5"><span></span><br/>
</div>
<!-- js 部分 -->
<script>
function checkReg(element, reg) {
element.onblur = function() {
var content = this.value;
if (reg.test(content)) {
this.nextElementSibling.innerHTML = "合法";
this.nextElementSibling.style.color = "green";
} else {
this.nextElementSibling.innerHTML = "不合法";
this.nextElementSibling.style.color = "red";
}
}
}
checkReg(document.getElementById("inp1"), /^[1-9]\d{4,11}$/);
checkReg(document.getElementById("inp2"), /^(13[0-9]|14[57]|15[0-25-9]|17[0137]|18[0-9])\d{8}$/);
checkReg(document.getElementById("inp3"), /^\w+@\w+(\.\w+)+$/);
checkReg(document.getElementById("inp4"), /^0\d{2,3}-\d{7,8}$/);
checkReg(document.getElementById("inp5"), /^[\u4e00-\u9fa5]{2,4}$/);
</script>
效果图:
14.8 正则补充知识点
这里要补充几个正则的小知识点,比如在正则里‘|
’,表示的是或。‘g
’,表示的是global
:全局,全部。‘i
’,表示的是ignore
:忽视大小写。
1、或‘|’ :
var mobileReg = /^(13[0-9]|14[57]|15[0-25-9]|17[0137]|18[0-9])\d{8}$/;
我们可以看到在判断手机号码前三位的时候,我们就用到了或:“|”
2、全部‘g
’:
var str = '123123123123';
// 找到所有的1 替换成3
var newStr = str.replace(/1/g, 3);
console.log(newStr); // 323323323
3、忽略大小写‘i
’:
var str2 = 'abcdAAAA';
var newStr2 = str2.replace(/a/gi, 'e');
console.log(newStr2); // ebcdeeee
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。