前言
JavaScript = ECMAScript(JS基础) + DOM +BOM
本文旨在介绍DOM (Document Object Model) 和 BOM (Browser Object Model) 的基本概念和相关操作,以及如何利用它们来实现动态交互效果和特效开发。
在DOM部分中,我们将详细介绍节点操作,包括如何访问元素节点、节点之间的关系、如何改变元素节点的内容,以及节点的创建、移除和克隆等操作。我们还将介绍如何使用事件监听来处理各种事件,并探讨事件传播和事件对象的相关知识。
另外,我们将深入讨论定时器和延时器的用法,以及如何利用它们来实现动画效果。我们还会介绍一些常用的BOM对象,如window对象、Navigator对象、History对象和Location对象,以及如何利用它们来实现一些特效开发。
通过阅读本文,你将掌握DOM和BOM的基本概念和操作方法,了解如何利用它们来制作动态交互和特效效果,提升网页的用户体验和交互性。
请你继续阅读,深入了解DOM和BOM的精彩世界。
DOM
1.DOM基本概念
-
DOM(Document Object Model,文档对象模型)是JavaScript操作HTML文档的接口,使文档操作变得非常优雅、简便。
-
DOM最大的特点就是将文档表示为节点树
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<h1>DOM基本概念</h1>
<div>
<h2>程序员的梦工厂</h2>
<img src="logo.png">
<div class="box">
DOM节点树
</div>
</div>
</body>
</html>
- nodeType常用属性值:
- 延迟运行
- 在测试DOM代码时,通常代码一定要写到HTML节点的后面,否则无法找到相应HTML节点
- 可以使用window.οnlοad=function(}事件,使页面加载完毕后,再执行指定的代码
2.节点操作
1) 访问元素节点
- 所谓“访问”元素节点,就是指”得到”、“获取”页面上的元素节点
- 对节点进行操作,第一步就是要得到它
- 访问元素节点主要依靠document对象
什么是document对象 ?
- document对象是DOM中最重要的东西,几乎所有DOM的功能都封装在了document对象中
- document对象也表示整个HTML文档,它是DOM节点树的根
- document对象的nodeType.属性值是9
- 访问元素节点的常用方法:
① getElementById()
<div id="box">我是一个盒子</div>
var oBox = document.getElementById('box'); //参数就是元素节点的id,注意不要写#号
- 如果页面上有相同id的元素,则只能得到第一个
- 不管元素藏的位置有多深,都能通过id把它找到
② getElementsByTagName()
<p> 我是段落1 </p>
<p> 我是段落2 </p>
<p> 我是段落3 </p>
var ps = document.getElementsByTagName('p'); //得到p标签的数组
var p2 = ps.getElementsByTagName('p')[1]; //得到第二个p标签
- 数组方便遍历,从而可以批量操控元素节点
- 即使页面上只有一个指定标签名的节点,也将得到长度为1的数组
- 任何一个节点元素也可以调用getElementsByTagName()方法,从而得到其内部的某种类的元素节点
③ getElementsByClassName()
<div class="spec">
<p class="para"> 我是段落 </p>
<p> 我是段落 </p>
</div>
var spec_divs = document.getElementsByClassName('spec');//注意不要写.号
var p1 = spec.getElementsByClassName('para');
- 某个节点元素也可以调用getElementsByClassName()方法,从而得到其内部的某类名的元素节点
④ querySelector()
<div id="box">
<p>我是段落</p>
<p class="spec para">我是段落</p>
</div>
var the_p = document.querySelector('#box .spec');
- querySelector()方法只能得到页面上一个元素,如果有多个元素符合条件,则只能得到第一个元素
- querySelector()方法从IE8开始兼容,但从IE9开始支持CSS3的选择器,如:nth-child0、:[src='dog]等CSS3选择器形式都支持良好
⑤ querySelectorAll()
<ul id="list1">
<li>我是li</li>
<li>我是li</li>
<li>我是li</li>
<li>我是li</li>
<li>我是li</li>
</ul>
<ul id="list2">
<li>我是li</li>
<li>我是li</li>
<li>我是li</li>
<li>我是li</li>
<li>我是li</li>
</ul>
var list1 = document.querySelectorAll('#list1 li'); //得到list1中li的内容
- querySelectorAll()方法的功能是通过选择器得到元素数组
- 即使页面上只有一个符合选择器的节点,也将得到长度为1的数组
2) 节点的关系
- DOM中,文本节点也属于节点,在使用节点的关系时一定要注意
- 在标准的W3C规范中,空白文本节点也应该算作节点,但是在E8及以前的浏览器中会有一定的兼容问题,它们不把空文本节点当做节点
- 从E9开始支持一些“只考虑元素节点”的属性(排除文本节点干扰)
- 封装常见的节点关系函数
<div id="box">
<p>我是段落A</p>
<p>我是段落a</p>
<p id="para">我是段落B
<span>1</span>
<span id="sp">2</span>
<span>3</span>
</p>
<p>我是段落b</p>
<p>我是段落C</p>
<p>我是段落c</p>
</div>
var box = document.getElementById('box');
var para = document.getElementById('para');
// 1.封装第一个函数,这个函数可以返回元素的所有子元素节点(兼容到IE6)
function getChildren(node){
// 结果数组
var children = [];
// 遍历node这个节点的所有子节点,判断每一个子节点的nodeType的值
// 如果是1,就推入数组
for( var i =0; i < node.childNodes.length; i++){
if(node.childNodes[i].nodeType == 1){
children.unshift(node.childNodes[i]);
}
}
return children;
}
console.log(getChildren(box));
console.log(getChildren(para));
// 2.封装第二个函数,这个函数可以返回元素的前一个元素兄弟节点(兼容到IE6),类似previousElementSibling的功能
function getElementPrevSibling(node){
var o = node;
//使用while语句
while(o.previousSibling != null){
if(o.previousSibling.nodeType == 1){
//结束循环,找到了
return o.previousSibling;
}
// 让o成为它的前一个节点
o = o.previousSibling;
}
return null;
}
console.log(getElementPrevSibling(para));
console.log(getElementPrevSibling(sp));
// 3.封装第三个函数,这个函数可以返回元素的所有元素兄弟节点
function getAllElementSilbling(node){
// 前面的元素兄弟节点
var prevs = [];
// 后面的元素兄弟节点
var nexts = [];
var o = node;
//遍历node前面的节点
while(o.previousSibling != null){
if(o.previousSibling.nodeType == 1){
prevs.push(o.previousSibling);
}
o = o.previousSibling;
}
//遍历node后面的节点
o = node;
while (o.nextSibling != null){
if(o.nextSibling.nodeType == 1){
nexts.push(o.nextSibling);
}
o = o.nextSibling
}
return prevs.concat(nexts);
}
console.log(getAllElementSilbling(para));
3) 改变元素节点的内容
<div id="box"></div>
① innerHTML
- innerHTML属性能以HTML语法设置节点中的内容
oBox.innerHTML = 'innerHTML学习'; //输出innerHTML学习
oBox.innerHTML = '<ul><li>牛奶</li><li>咖啡</li></ul>'; //输出·牛奶 ·咖啡
② innerText
- innerText属性只能以纯文本的形式设置节点中的内容
oBox.innerText = 'innerText学习' ;//输出 innerText学习
oBox.innerText = '<ul><li>牛奶</li><li>咖啡</li></ul>' ; //输出<ul><li>牛奶</li><li>咖啡</li></ul>
4) 节点的创建、移除和克隆
① 创建节点
-
document.createElement0方法用于创建一个指定tagname的HTML元素
-
新创建出的节点是“孤儿节点”,这意味着它并没有被挂载到DOM树上,我们无法看见它
-
必须继续使用appendChild()或insertBefore()方法将孤儿节点插入到DOM树上
-
appendChild():
- 将孤儿节点挂载到它的内部,成为它的最后一个子节点
父节点.appendChild(孤儿节点);
-
insertBefore():
- 将孤儿节点挂载到它的内部,成为它的\“标杆子节点”之前的节点
父节点.insertBefore(孤儿节点,标杆节点);
-
-
示例:
<div id="box"> <p>我是原本的段落0</p> <p>我是原本的段落1</p> <p>我是原本的段落2</p> </div>
//访问需要添加的节点(父节点) var oBox = document.getElementById('box'); // 第一步: 创建孤儿节点 var oP = document.createElement('p'); //设置内部文字 oP.innerText = '我是新来的'; // 第二步: 上树 appendChild()方法 oBox.appendChild(oP); // 第二步: 上树 insertBefore()方法 var oPs = oBox.getElementsByTagName('p'); oBox.insertBefore(oP.oPs[0]);
② 移动节点
-
如果将已经挂载到DOM树上的节点成为appendChild()或者insertBefore()的参数,这个节点将会被移动
新父节点.appendChild(已经有父亲的节点);
新父节点.insertBefore(已经有父亲的节点,标杆子节点);
-
这意味着一个节点不能同时位于DOM树的两个位置
-
示例:
<div id="box1"> <p id="para">我是段落</p> </div> <div id="box2"> <p>我是box2的原有p1标签</p> <p>我是box2的原有p2标签</p> </div>
var box2 = document.getElementById('box2'); var para = document.getElementById('para'); var ps_box2 = box2.getElementsByTagName('p'); // box2.appendChild(para); box2.insertBefore(para,ps_box2[1]);//将para插到box2的p2之前
③ 删除节点
-
removeChild()方法从DOM中删除一个子节点
-
节点不能主动删除自己,必须由父节点删除它
- 语法:
父节点.removeChild(要删除的节点);
- 示例:
box1.removeChild(para);
- 语法:
④ 克隆节点
- cloneNode()方法可以克隆节点,克隆出来的节点是"孤儿节点"
var 孤儿节点 = 老节点.cloneNode();
//相当于falsevar 孤儿节点 = 老节点.cloneNode(true);
- 参数是一个布尔值,表示是否采用深克隆:
- 如果为true,则节点的所有后代节点也都会被克隆,
- 如果为false,则只克隆该节点本身
- 参数是一个布尔值,表示是否采用深克隆:
5) 其他操作
① 改变节点的CSS样式
- 同时更改多个样式:
oBox.style.cssText = "background-color: red; font-size: 32px;";
② 改变元素节点的HTML属性
-
标准W3C属性,如src、href等,只需要打点进行更改即可
oImg.src = 'imgages/pic.jpg';
-
不符合W3C标准的属性,要使用setAttribute()和getAttribute()来设置、读取
bBox.setAttribute('data-n',10); //设置 (更改) var n = oBox.getAttribute('data-n'); //读取 alert(n); //弹出10
3.DOM事件
1) 事件监听
-
事件 : 用户与网页的交互动作
- 当用户点击元素时
- 当鼠标移动到元素上时
- 当文本框内容被改变时
- 当键盘在文本框中被按下时
- 当网页已经加载完毕时
-
监听 : 为了随时能够发现这个事件发生了,而执行预先编写的一些程序
- 设置监听的方法主要有onxxx和addEventListener()
-
最简单的设置事件监听的方法——设置它们的onxxx属性:
oBox.onclick = function(){ // 点击盒子时,将执行这里的语句 }
-
addEventListener()方法
addEventListener(type,listener,useCapture)
- type: 表示事件类型,比如"click"、“mouseover”、"keydown"等
- listener: 表示事件监听器,也就是处理特定事件发生时执行的函数。可以传入一个已定义的函数作为监听器,也可以直接使用匿名函数。
- useCapture(可选): 一个布尔值,默认为false。表示事件是否在捕获阶段进行处理。如果设置为true,则监听器将在事件捕获阶段被调用;如果为false,则在冒泡阶段被调用。
oBox.addEventListener('click',function(){ // 点击盒子时,将执行这里的语句 },true);
-
- 设置监听的方法主要有onxxx和addEventListener()
① 鼠标事件监听
② 键盘事件监听
- 注意: onkeypress事件处理程序已弃用。
③ 表单事件监听
④ 页面事件监听
- onload : 当页面或图像被完全加载
- onunload : 用户退出页面
2) 事件传播
-
研究: 当盒子嵌套时事件监听的执行顺序 ? (鼠标点击中间盒子)
- 结果1: 执行顺序是 box3 -> box2 -> box1 (×)
-
事件的传播顺序 : 先从外到内,再从内到外
-
事件捕获:当一个事件触发后,从window对象触发,不断经过下级的节点,直到目标节点,在事件到达目标节点之前的过程就是捕获过程,所有经过的节点,都会触发对应的事件.
-
事件冒泡:当事件到达目标节点后,会沿着捕获阶段的路线原路返回,同样所有经过的节点都会触发对应的事件.
-
结果2: 先捕获(box1 -> box2 -> box3) -> 再冒泡(box3 -> box2 -> box1) (√)
-
onxxx这样的写法只能监听冒泡阶段,捕获阶段需要用 addEventListener() 方法
-
-
注意事项:
- 最内部元素不区分捕获和冒泡阶段,会先执行写在前面的事件监听,然后执行后写的
- 如果给元素设置相同的两个或多个同名事件,则DOM0级写法后面写的会覆盖先写的;而DOM2级会按顺序执行
3) 事件对象
- 事件处理函数提供一个形式参数,它是一个对象,封装了本次事件的细节
- 这个参数通常用单词event或字母e来表示
- 示例:
oBox.onmousemove = function(e){}; //对象e就是这次事件的"事件对象"
①鼠标位置的相关属性
- 使用方法举例:
e.clientX
- 注意: offsetX/offsetY指的是最内部盒子里的坐标
② preventDefault()方法
- 阻止事件产生的"默认动作"
- 比如鼠标滚动时页面滚动条的滚动
e.preventDefault();
③ stopPropagation()方法
- 阻止事件继续传播
- 在一些场合,非常有必要切断事件继续传播,否则会造成页面特效显出bug
e.stopPropagation();
4) 事件委托
-
优点
- 减少DOM操作,减少浏览器的重绘(repaint) 和 重排 (reflow) 提高性能
- 减少内存空间的占用率,因为每一个函数就是一个对象,对象越多,内存占用率就越大,自然性能就越差,使用事件委托,只需要在父元素定义一个事件即可.
- 动态添加和修改元素,不需要因为元素的改动而修改事件绑定
-
原理
- 运用事件冒泡机制,将后代元素事件委托给祖先元素
- 运用事件冒泡机制,将后代元素事件委托给祖先元素
-
事件委托实现
-
e.target属性: 事件源元素,返回事件的目标节点
oBox.addEventListener('click',function(e){ e.target.style.backgroundColor = 'red'; //点击盒子时,盒子将变红 })
-
标准浏览器用 ev.target, IE浏览器用event.srcElement
-
e.currentTarget: 事件处理程序附加到的元素
-
-
使用场景
- 当有大量类似元素需要批量添加事件监听时,使用事件委托可以减少内存开销
- 当有动态元素节点上树时,使用事件委托可以让新上树的元素具有事件监听
-
适用事件委托的事件
- click/mousedown/mouseup/keydown/keyup/keypress
-
注意事项
- 不能委托不冒泡的事件给祖先元素
- mouseenter、mouseleave 不冒泡
- mouseover 冒泡
- 最内层的元素不能再有额外的内层元素了
- 不能委托不冒泡的事件给祖先元素
4.定时器和延时器
1)定时器
① setInterval()
-
setInterval()函数可以重复调用一个函数,在每次调用之间有固定的时间间隔
-
函数的参数
setInterval(function (a,b){ // 形式参数a的值是88,形式参数b的值是66 },2000,88,66);
-
具名函数也可以传入setInterval()
var a = 0 function fun(){ console.log(++a); } setInterval(fun,2000);//具名函数当中第一个参数,注意这里没有()
② clearInterval()
-
clearInterval()函数可以清除一个定时器
var a = 0; //设置定时器,并且用变量timer接受这个定时器 var timer = setInterval(function(){ clearInterval(timer); //设表先关 console.log(++a); },2000); //点击按钮时,清除定时器 oBtn.onclick = function(){ clearInterval(timer); //清除定时器时,要传入定时器变量 }
-
设表先关 : 在多次点击按钮时会导致定时器叠加,数字会快速增加, 为了防止定时器叠加,我们应该在设置定时器时,也要清除之前的定时器
2) 延时器
① setTimeout()
- 当指定事件到了之后,会执行一次函数,不再重复执行
var timer = setTimeout(function(){ //这个函数会再两秒后执行一次 },2000);
② clearTimeout()
- 清除延时器(相当于取消预约),和clearInterval()非常类似
clearInterval(timer);
3) 异步
-
setInterval() 和 setTimeout() 是两个异步语句
-
异步(asynchronous): 不会阻塞CPU继续执行其他语句,当异步完成时,会执行"回调函数"(callback)
5.实现动画
-
使用定时器实现动画较为不便:
- 不方便根据动画总时间计算步长
- 运动方向要设置正负
- 多种运动进行叠加较为困难(比如一个方形一边移动一边变为圆形)
- 可用示例 : 无缝连续滚动特效
- 多复制一遍所有的li
oList.innerHTML += oList.innerHTML;
- 设置全局变量,表示当前list的left值
var left = 0;
- 判断是不是最后一张,如果是,回到第一张
left = 0
,
- 多复制一遍所有的li
-
JS+CSS3结合实现动画
- 示例: 轮播图
- 走马灯轮播图
- 呼吸轮播图
- 用法注意:
<a href="javascript:;" ></a>
点击a时不会刷新页面- 克隆第一个图片当缓冲图片
- 点击左按钮时,先判断是不是第1张(可以设置一个全局变量标记当前显示的是第几张图片
var idx = 0
,每触发一次点击事件,让idx++或者idx–),如果是,就要瞬间跳转到缓冲图片上,然后再过渡到真正要显示的下一张图片 - 点击右按钮时,要判断是不是最后一张图片,如果是,要先过渡到缓冲图片上,再瞬间跳转到第一张图片
- 点击左按钮时,先判断是不是第1张(可以设置一个全局变量标记当前显示的是第几张图片
- 瞬间跳转时要清除动画效果
oList.style.transition = 'none';
- 设置一个延时器,可以将图片瞬间拉到目标位置
- 注意:这个延时器的延时时间可以是0ms,虽然是0ms,但是可以让我们的过渡先是瞬间取消,然后加上
- 示例: 轮播图
-
函数节流:一个函数执行一次后,只有大于设定的执行周期后才允许执行第二次,需要借助setTimeout()延时器
BOM
-
BOM(Browser Object Model,浏览器对象模型)是JS与浏览器窗口交互的接口
-
一些与浏览器改变尺寸、滚动条滚动相关的特效,都要借助BOM技术
1.BOM常用对象
1) window 对象
-
window对象是当前JS脚本运行所处的窗口,而这个窗口中包含DOM结构,window.document属性就是document对象
-
在有标签页功能的浏览器中,每个标签都拥有自己的window对象;也就是说,同一个窗口的标签页之间不会共享一个window对象
-
全局变量会成为window对象的属性
-
内置函数普遍是window的方法
- 比如setInterval()、alert()、prompt()
-
多个js文件之间是共享全局作用域的,即JS文件没有作用域隔离功能
-
窗口尺寸相关属性
- 获得不包含滚动条的窗口宽度:
document.documentElement.clientWidth
- 获得不包含滚动条的窗口宽度:
-
resize事件
-
在窗口大小改变之后,就会触发resize事件,可以用window.onresize或者window.addEventLinstener(‘resize’)来绑定事件处理函数
window.onresize = function (){ console.log('窗口宽度事件被触发了'+ window.innerWidth); //窗口宽度事件被触发了1613 }
-
-
已卷动高度
-
window.scrollY属性表示在垂直方向已滚动的像素值 (只读)
-
document.documentElement.scrollTop属性也表示窗口卷动高度(不是只读的)
var scrollTop = window.scrollY || document.documentElement.scrollTop; //提高网页兼容性的一种方式
-
-
scroll事件
-
在窗口被卷动之后,就会触发scroll事件,可以使用window.onscroll或者window.addEventListener(‘scroll’)来绑定事件处理函数
window.onscroll = function(){ console.log('窗口卷动事件被触发了'+window.scrollY); //窗口事件被触发了888 }
-
2) Navigator 对象
-
window.navigator属性可以检索navigator对象,它内部含有用户此次活动的浏览器的相关属性和表标识
appName: 浏览器官方名称appVersion : 浏览器版本- userAgent : 浏览器的用户代理(含有内核信息和封装壳信息)
- platform : 用户操作系统
console.log('浏览器官方名称:' + navigator.appName);//浏览器官方名称:Netscape console.log('浏览器版本:' + navigator.appVersion);//浏览器版本:5.0 console.log('用户代理:' + navigator.userAgent); //用户代理:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36 console.log('用户操作系统:' + navigator.platform);//用户操作系统:Win32
3) History 对象
- window.history对象提供了操作浏览器会话历史的接口
- 常用的操作 : 模拟浏览器回退按钮
-
history.back() 同于点击浏览器的回退按钮
<a href="javascript:history.back();">回退</a>
-
history.go(-1) 等同于history.back();
btn.onclick = function (){ history.go(-1); }
-
4) Location 对象
- window.location标识当前所在网址,可以通过这个属性赋值命令浏览器进行页面跳转
window.location = 'https://www.csdn.net/';
- reload方法 : 重新加载当前页面,参数true表示强制从服务器强制加载
window.location.reload(true);
- window.location.search属性即为当前浏览器的GET请求查询参数
2.BOM特效开发
1) 返回顶部按钮制作
- 原理:改变document.documentElement. scrollTop属性,通过定时器逐步改变此值,则将用动画形式返回顶部
- 当document.documentElement. scrollTop =0 时在网页顶部
2) 楼层导航小效果
- DOM元素都有offsetTop属性,表示此元素到定位祖先元素的垂直距离
- 定位祖先元素: 在祖先中,离自己最近的且拥有定位属性的元素
- 没有定位祖先元素时,offsetTop属性值是此元素到页面顶部的净top值
- 示例:
*{
margin: 0;
padding: 0;
}
.content-part{
width: 1200px;
margin: 0 auto;
margin-bottom: 30px;
background-color: #aeaeae;
font-size: 50px;
padding: 20px;
}
.floornav{
position: fixed;
right: 40px;
top: 50%;
margin-top: -120px;
height: 240px;
width:80px;
background-color: greenyellow;
}
.floornav ul {
list-style: none;
}
.floornav ul li{
width: 80px;
height: 40px;
font-size: 26px;
line-height: 40px;
text-align: center;
cursor:pointer;
}
.floornav ul li.current{
background-color: pink;
color:white;;
}
<nav class="floornav" id="floornav">
<ul id="list">
<li data-n = '体育' class="current">体育</li>
<li data-n = '科技'>科技</li>
<li data-n = '视频'>视频</li>
<li data-n = '娱乐'>娱乐</li>
<li data-n = '新闻'>新闻</li>
<li data-n = '美食'>美食</li>
</ul>
</nav>
<section class="content-part" style="height: 400px" data-n = '体育'>体育栏目</section>
<section class="content-part" style="height: 200px" data-n = '科技'>科技栏目</section>
<section class="content-part" style="height: 300px" data-n = '视频'>视频栏目</section>
<section class="content-part" style="height: 500px" data-n = '娱乐'>娱乐栏目</section>
<section class="content-part" style="height: 600px" data-n = '新闻'>新闻栏目</section>
<section class="content-part" style="height: 400px" data-n = '美食'>美食栏目</section>
// 使用事件委托给li添加监听
let list = document.getElementById('list');
let current = document.querySelector('.current');
let contenParts = document.querySelectorAll('.content-part');
let lis = document.querySelectorAll('#list li');
list.onclick = function (e){
if(e.target.tagName.toLowerCase() === 'li'){
// getAttribute表示得到标签身上的某个属性值
var n = e.target.getAttribute('data-n');
// 可以用属性选择器(就是方括号选择器)来寻找带有相同data-n的content-part
let contentPart = document.querySelector('.content-part[data-n='+n+']');
//让页面的卷动自动成为这个盒子的offsetTop值
document.documentElement.scrollTop = contentPart.offsetTop;
}
}
// 在页面加载好之后,将所有的content-part盒子的offsetTop值推入数组
var offsetTopArr = [];
// 遍历所有的contentPart,将他们的净位置推入数组
for( var i= 0;i < contenParts.length; i++){
offsetTopArr.push(contenParts[i].offsetTop);
}
console.log(offsetTopArr);
// 为了最后一项可以方便比较,我们可以推入一个无穷大
offsetTopArr.push(Infinity);
//当前楼层
var nowfloor =-1;
// 窗口的卷动
window.onscroll = function (){
// 遍历offsetTopArr数组,看看当前的scrollTop值在哪两个楼层之间
for(var i = 0;i<offsetTopArr.length;i++){
var scrolltop = document.documentElement.scrollTop;
if(scrolltop >= offsetTopArr[i] && scrolltop < offsetTopArr[i+1]){
break;
}
}
// 退出循环的时候,i是几,就表示当前楼层是几
console.log(i);
// 如果当前所在楼层不是i,表示环楼了(也是一种节流机制)
if(nowfloor !== i){
// 让全局变量改变为这个楼层号
nowfloor = i;
//设置下标为i的项有current
for (var j=0;j<lis.length;j++){
if(j===i){
lis[j].className = 'current';
}else{
lis[j].className = '';
}
}
}
};