JavaScript核心操作DOM与BOM

前言

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(); //相当于false
  • var 孤儿节点 = 老节点.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); 
        

① 鼠标事件监听

在这里插入图片描述

② 键盘事件监听

在这里插入图片描述

  • 注意: 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.实现动画

  • 使用定时器实现动画较为不便:

    • 不方便根据动画总时间计算步长
    • 运动方向要设置正负
    • 多种运动进行叠加较为困难(比如一个方形一边移动一边变为圆形)
    • 可用示例 : 无缝连续滚动特效
      • 多复制一遍所有的lioList.innerHTML += oList.innerHTML;
      • 设置全局变量,表示当前list的left值var left = 0;
      • 判断是不是最后一张,如果是,回到第一张left = 0,
  • JS+CSS3结合实现动画

    • 示例: 轮播图
      • 走马灯轮播图
      • 呼吸轮播图
    • 用法注意:
      • <a href="javascript:;" ></a>点击a时不会刷新页面
      • 克隆第一个图片当缓冲图片
        • 点击左按钮时,先判断是不是第1张(可以设置一个全局变量标记当前显示的是第几张图片var idx = 0,每触发一次点击事件,让idx++或者idx–),如果是,就要瞬间跳转到缓冲图片上,然后再过渡到真正要显示的下一张图片
        • 点击右按钮时,要判断是不是最后一张图片,如果是,要先过渡到缓冲图片上,再瞬间跳转到第一张图片
      • 瞬间跳转时要清除动画效果 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 = '';
                    }
                }
            }


        };

猜你喜欢

转载自blog.csdn.net/weixin_40845165/article/details/131849623