【练习】tab切换

17 阅读9分钟

tab页面结构

页面结构:
└── 容器div container
    ├── 标签栏div tab_box
    │   ├── 按钮 tab_btn (激活) - “首页”
    │   ├── 按钮 tab_btn - “关于”
    │   ├── 按钮 tab_btn - “绿豆沙”
    │   ├── 按钮 tab_btn - “我的”
    │   ├── 按钮 tab_btn - “其他”
    │   └── 下划线 line
    └── 内容区div content_box
        ├── 内容div content (active)
        │   ├── 图片 bg.jpg
        │   ├── 标题 h2 - “首页”
        │   └── 段落 p - 诗句内容
        ├── 内容div content
        │   ├── 图片 bg.jpg
        │   ├── 标题 h2 - “关于”
        │   └── 段落 p - 诗句内容
  

  html, body {
    height: 100%;
    margin: 0;
  }
  /*设置内容居中*/

  body{
    background-color: #deeeff;
    display: flex;
    justify-content: center;
    align-items: center;
  }  

为什么这样写才行 不能一起写在body里

这样也可以
  body{
    background-color: #deeeff;
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100vh;
  }  

height: 100% 是“继承父元素高度”

  • body 的高度如果是 100%,它的意思是:我要占满我的父元素 html 的高度
  • 但如果你没有写 html { height: 100%; },那 html 的高度就是默认的“被内容撑开的高度”,比如几十像素。
  • 所以 body { height: 100%; } 没有“基准”,就不起作用。
写法结果
html, body { height: 100%; }body 才能真正占满全屏
❌ 只写 body { height: 100%; }body 高度 = 内容高度,无法居中

改写jQue版本

  $(document).ready(function (){
    const $tabs = $('.tab_btn');
    const $contents = $('.content');
    const $line = $('.line');
    $tabs.each(function(index) {
      $(this).on('click', function(e) {
        // 移除所有 tab 的 active 类
        $tabs.removeClass('active');
        console.log($(this))

        // 当前 tab 添加 active 类
        $(this).addClass('active');

        // 设置滑块 line 的宽度和位置
        $line.css({
          width: $(this).outerWidth() + 'px',
          left: $(this).position().left + 'px'
        });

        // 隐藏所有内容
        $contents.removeClass('active');

        // 当前索引对应的内容添加 active 类
        $contents.eq(index).addClass('active');
      });
    });

  })
原生 JSjQuery 对应写法
document.querySelectorAll()$('.class')
element.addEventListener()$(element).on('click', fn)
classList.add/removeaddClass() / removeClass()
offsetWidth$(element).outerWidth()
offsetLeft$(element).position().left
forEach()each() 或使用 .on() 自动遍历
all_content[index]$contents.eq(index)

outerWidth() 是 jQuery 获取宽度的方法,包含 padding 和 border(相当于原生的 offsetWidth)。

position().left 获取的是元素相对父元素的左边距,对应 offsetLeft

.eq(index) 是 jQuery 中选取第 index 个元素的方法。

  const tabs = document.querySelectorAll('.tab_btn');
  const all_content = document.querySelectorAll('.content');

  // 遍历所有的tab元素
  tabs.forEach((tab, index)=>{
    tab.addEventListener('click',(e)=>{
      console.log('你点击了按钮:', e.target);         // 被点击的元素
      console.log('事件类型:', e.type);               // click
      console.log('点击位置:', e.clientX, e.clientY); // 鼠标点击的位置
      // 删除所有的active元素
      tabs.forEach(tab => {
        tab.classList.remove('active')
      });
      // 给当前的tab增加active元素
      tab.classList.add('active')
      // 获取line元素 计算当前元素距离左边的距离
      var line = document.querySelector('.line');
      line.style.width = e.target.offsetWidth + "px";
      line.style.left = e.target.offsetLeft + "px";
      console.log(e.target.offsetWidth)
      console.log(e.target.offsetLeft)

      all_content.forEach(content => {
        content.classList.remove('active')
      });
      console.log(all_content)
      all_content[index].classList.add('active');

    })
  })

📌使用 .forEach()

let tabs = ['首页', '关于', '我的'];
tabs.forEach((tab, index) => {
  console.log(index, tab);
});

let tabs = ['首页', '关于', '我的'];
for (let i = 0; i < tabs.length; i++) {
  console.log(i, tabs[i]);
}

遍历所有元素不能用 break continue

.addEventListener 是 JavaScript 中用于 注册事件监听器 的方法,它让你可以对某个 HTML 元素添加“监听”,当发生某种事件(如点击、悬停、键盘输入等)时执行指定的函数。

📌 基本语法:

element.addEventListener(event, function, useCapture);
参数说明:
  1. event(字符串):你要监听的事件类型,比如 "click""mouseover""keydown" 等。
  2. function(函数):当事件发生时执行的回调函数。
  3. useCapture(可选,布尔值):默认为 false,一般不用管,涉及事件捕获阶段(进阶话题)。

📌 e常用的属性方法

属性/方法说明
e.target触发事件的元素(比如被点击的按钮)
e.currentTarget当前监听器所在的元素(一般等于 tab
e.type事件类型,例如 "click"
e.clientX/Y鼠标点击的坐标(相对于窗口)
e.pageX/Y鼠标点击的坐标(相对于页面)
e.preventDefault()阻止默认行为(如链接跳转)
e.stopPropagation()阻止事件冒泡

📌e.target.offsetWidth && e.target.offsetLeft

offsetWidth 表示一个元素的 “实际宽度”,包含了:

  • 元素的 content(内容区)
  • padding(内边距)
  • border(边框)

不包含:

  • margin(外边距)
.box {
  width: 100px;
  padding: 10px;
  border: 2px solid black;
  margin: 20px;
}

<div class="box">Hello</div>

const box = document.querySelector('.box');
console.log(box.offsetWidth); // 👉 124
  • width = 100
  • padding 左 + 右 = 10 + 10 = 20
  • border 左 + 右 = 2 + 2 = 4
  • ✅ 所以 offsetWidth = 100 + 20 + 4 = 124

margin 不包含在内!

其他属性

属性含义
offsetWidth元素的 总宽度(含padding和border)
clientWidth内容 + padding,不含 border
scrollWidth实际滚动区域宽度(内容超出时会更大)
style.width你在 CSS 里写的 width 值(可能是空)

offsetLeft 表示:这个元素距离“父元素”的左边有多远。

就像你问一个盒子:“你从你爸左边数第几像素开始站的?”

<div class="parent" style="position: relative;">
  <div class="child" style="position: absolute; left: 50px;"></div>
</div>

const child = document.querySelector('.child');
console.log(child.offsetLeft); // 👉 50
[parent]
|-----------------------------------------|
|                                         |
|   <-- offsetLeft --> [child]            |
|                                         |
|-----------------------------------------|

其他属性

属性名含义
offsetLeft元素相对最近的定位父元素左边的距离
clientLeft元素左边框的宽度(很少用)
scrollLeft元素被滚动出去的水平像素值(用于滚动位置)
getBoundingClientRect().left元素相对于视口的左边距离(常用于精确定位)

📌.classList 就像是元素身上的「衣服列表」,你可以:

  • 👕 add():穿上一件新衣服
  • remove():脱掉一件衣服
  • 🔄 toggle():穿上或脱掉(根据当前状态)
  • 🔍 contains():检查是否穿了某件衣服
// 查看当前有哪些类
console.log(box.classList); // DOMTokenList ['box', 'active']

// 添加类
box.classList.add('highlight'); // <div class="box active highlight">

// 移除类
box.classList.remove('active'); // <div class="box highlight">

// 切换类(如果有就移除,没有就添加)
box.classList.toggle('hidden');

// 判断是否有某个类
console.log(box.classList.contains('box')); // true

.classList 常用方法总结:

方法名功能说明
add('class')添加类名
remove('class')移除类名
toggle('class')切换类名(有就移除,无就添加)
contains('class')判断是否包含某个类名
replace('old', 'new')替换一个类名
[...element.classList]把类名转为数组(方便遍历)

📌拓展

event事件有哪些

一、鼠标事件(Mouse Events)

事件名说明
click鼠标点击(按下+释放)
dblclick双击
mousedown鼠标按下
mouseup鼠标释放
mousemove鼠标移动
mouseenter鼠标首次进入元素,不冒泡
mouseleave鼠标离开元素,不冒泡
mouseover鼠标进入元素,会冒泡
mouseout鼠标离开元素,会冒泡
contextmenu鼠标右键

二、键盘事件(Keyboard Events)

事件名说明
keydown按键按下
keyup按键松开
keypress ⚠️已废弃,用 keydown 代替(旧版用于输入字符)

三、表单事件(Form Events)

事件名说明
submit表单提交
reset表单重置
change值发生改变(如 <select>、复选框)
input输入框内容变化(比 change 更即时)
focus获取焦点(如点击输入框)
blur失去焦点
focusin类似 focus,但会冒泡
focusout类似 blur,但会冒泡

四、剪贴板事件(Clipboard Events)

事件名说明
copy拷贝
cut剪切
paste粘贴

五、拖放事件(Drag & Drop Events)

事件名说明
drag拖动进行中
dragstart拖动开始
dragend拖动结束
dragenter拖动元素进入目标区域
dragleave拖动元素离开目标区域
dragover拖动元素在目标区域上
drop拖动元素放下

六、窗口事件(Window / Document Events)

事件名说明
load页面加载完成(包括图片)
DOMContentLoadedDOM 加载完成(不等图片)
resize窗口大小变化
scroll页面滚动
unload页面卸载(几乎已废弃)
beforeunload页面即将卸载,常用于弹窗提醒
error出现错误(可用于图片、JS 错误)

七、触摸事件(移动端 Touch Events)

事件名说明
touchstart手指触摸屏幕
touchmove手指滑动
touchend手指离开
touchcancel系统取消触摸事件

八、媒体事件(用于 <audio><video>

事件名说明
play开始播放
pause暂停
ended播放结束
timeupdate播放时间更新
volumechange音量改变
loadeddata媒体加载完成
error媒体加载错误

九、自定义事件(Custom Events)

  • 你可以自己创建事件:
javascript复制编辑const event = new CustomEvent("myevent", { detail: { key: "value" } });
element.dispatchEvent(event);

十、动画/过渡事件(CSS 动画相关)

事件名说明
animationstart动画开始
animationend动画结束
animationiteration动画重复
transitionstart过渡开始
transitionend过渡结束

源代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
  <style>
      *{
        margin: 0;
        padding: 0;
        box-sizing: border-box;
        font-family: 'Poppins', sans-serif;
      }
      html, body {
        height: 100%;
        margin: 0;
      }
      /*设置内容居中*/
      body{
        background-color: #deeeff;
        display: flex;
        justify-content: center;
        align-items: center;

      }

      .tab_box{
        width: 100%;
        display: flex;
        justify-content: space-around;
        align-items: center;
        border-bottom: 2px solid rgb(209, 209, 209);
        position: relative;
      }

      .line{
        position: absolute;
        top: 54px;
        width: 62px;
        left: 23px;
        height: 5px;
        background-color: #7360ff;
        border-radius: 10px;
        transition: all .3s ease-in-out;
      }


      .container{
        width: 600px;
        background-color: white;
        padding: 20px;
        box-shadow: 0 2px 16px rgba(0, 0, 0, .1);
        border-radius: 20px;
      }

      .tab_box .tab_btn{
        font-size: 16px;
        font-weight: 600;
        background: none; /*清除button样式*/
        border: none;
        padding: 15px;
        cursor: pointer;
      }

      .content_box {
        padding: 20px;
      }

      .content img{
        width: 100%;
        height: 250px;
      }
      .content_box .content h2{
        margin-bottom: 10px;
      }

      .content_box .content {
        display: none;
        animation: moving .5s ease;
      }

      @keyframes moving {
        from {
          transform: translateX(50px);
          opacity: 0;
        }

        to {
          transform: translateX(0px);
          opacity: 1;
        }
      }

      .content_box .content.active {
        display: block;
      }

      .tab_box .tab_btn.active {
        color: #7360ff;
      }



  </style>
</head>
<body>
<div class="container">
  <div class="tab_box">
    <button class="tab_btn active">首页</button>
    <button class="tab_btn">关于</button>
    <button class="tab_btn">绿豆沙</button>
    <button class="tab_btn">我的</button>
    <button class="tab_btn">其他</button>
    <div class="line"></div>
  </div>
  <div class="content_box">
    <div class="content active">
      <img src="../images/bg.jpg" alt="">
      <h2>首页</h2>
      <p>
        北国风光,千里冰封,万里雪飘。望长城内外,惟余莽莽;大河上下,顿失滔滔。山舞银蛇,原驰蜡象,欲与天公试比高。须晴日,看红装素裹,分外妖娆。江山如此多娇,引无数英雄竞折腰。惜秦皇汉武,略输文采;唐宗宋祖,稍逊风骚。一代天骄,成吉思汗,只识弯弓射大雕。俱往矣,数风流人物,还看今朝。
      </p>
    </div>
    <div class="content">
      <img src="../images/bg.jpg" alt="">
      <h2>关于</h2>
      <p>
        北国风光,千里冰封,万里雪飘。望长城内外,惟余莽莽;大河上下,顿失滔滔。山舞银蛇,原驰蜡象,欲与天公试比高。须晴日,看红装素裹,分外妖娆。江山如此多娇,引无数英雄竞折腰。惜秦皇汉武,略输文采;唐宗宋祖,稍逊风骚。一代天骄,成吉思汗,只识弯弓射大雕。俱往矣,数风流人物,还看今朝。
      </p>
    </div>
    <div class="content">
      <img src="../images/bg.jpg" alt="">
      <h2>绿豆沙</h2>
      <p>
        北国风光,千里冰封,万里雪飘。望长城内外,惟余莽莽;大河上下,顿失滔滔。山舞银蛇,原驰蜡象,欲与天公试比高。须晴日,看红装素裹,分外妖娆。江山如此多娇,引无数英雄竞折腰。惜秦皇汉武,略输文采;唐宗宋祖,稍逊风骚。一代天骄,成吉思汗,只识弯弓射大雕。俱往矣,数风流人物,还看今朝。
      </p>
    </div>
    <div class="content">
      <img src="../images/bg.jpg" alt="">
      <h2>我的</h2>
      <p>
        北国风光,千里冰封,万里雪飘。望长城内外,惟余莽莽;大河上下,顿失滔滔。山舞银蛇,原驰蜡象,欲与天公试比高。须晴日,看红装素裹,分外妖娆。江山如此多娇,引无数英雄竞折腰。惜秦皇汉武,略输文采;唐宗宋祖,稍逊风骚。一代天骄,成吉思汗,只识弯弓射大雕。俱往矣,数风流人物,还看今朝。
      </p>
    </div>
    <div class="content">
      <img src="../images/bg.jpg" alt="">
      <h2>其他</h2>
      <p>
        北国风光,千里冰封,万里雪飘。望长城内外,惟余莽莽;大河上下,顿失滔滔。山舞银蛇,原驰蜡象,欲与天公试比高。须晴日,看红装素裹,分外妖娆。江山如此多娇,引无数英雄竞折腰。惜秦皇汉武,略输文采;唐宗宋祖,稍逊风骚。一代天骄,成吉思汗,只识弯弓射大雕。俱往矣,数风流人物,还看今朝。
      </p>
    </div>
  </div>
</div>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script>

  $(document).ready(function (){
    const $tabs = $('.tab_btn');
    const $contents = $('.content');
    const $line = $('.line');


    $tabs.each(function(index) {
      $(this).on('click', function(e) {
        // 移除所有 tab 的 active 类
        $tabs.removeClass('active');
        console.log($(this))

        // 当前 tab 添加 active 类
        $(this).addClass('active');

        // 设置滑块 line 的宽度和位置
        $line.css({
          width: $(this).outerWidth() + 'px',
          left: $(this).position().left + 'px'
        });

        // 隐藏所有内容
        $contents.removeClass('active');

        // 当前索引对应的内容添加 active 类
        $contents.eq(index).addClass('active');
      });
    });

  })

</script>
</body>
</html>