You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vanilla JavaScript实现流体菜单Tab激活态弹性滑动下划线效果

导航下划线拉伸过渡效果修复方案

存在的问题

原有实现存在几处核心错误导致效果异常:

  • 状态变量prevItem、currentItem定义在for循环内部,每次遍历菜单项都会被重置,无法正常记录前后选中的菜单项索引,导致非相邻点击失效
  • 拉伸阶段的left值逻辑错误:右滑时直接设置为目标菜单项的左偏移,下划线还没拉伸就已经移动到目标位置,自然看不到正确的过渡效果
  • 导航链接的CSS外边距写法错误,(20px * 2)不是合法的CSS属性值,导致菜单项无横向间距
  • 计算拉伸宽度时混用了导航项和内部链接的尺寸,导致拉伸宽度不符合预期

修复后的完整代码

HTML代码

<nav class="navigation">
  <ul class="navigation__list">
    <li class="navigation__item active">
      <a class="navigation__link" href="#">Lorem ipsum</a>
    </li>
    <li class="navigation__item">
      <a class="navigation__link" href="#">Dolor</a>
    </li>
    <li class="navigation__item">
      <a class="navigation__link" href="#">Consectetur adipiscing</a>
    </li>
    <li class="navigation__item">
      <a class="navigation__link" href="#">Donec ut</a>
    </li>
    <li class="navigation__item">
      <a class="navigation__link" href="#">Placerat dignissim</a>
    </li>
    <div class="navigation__line"></div>
  </ul>
</nav>

注:给首个菜单项默认添加active类,用于初始化下划线的位置和宽度

CSS代码

body {
  color: #444;
  display: flex;
  min-height: 100vh;
  flex-direction: column;
  margin: 0;
}

.navigation {
  display: block;
  position: sticky;
  top: -0.5px;
  background-color: #edece8;
  margin: 60px 0 0;
  text-align: center;
}

.navigation__list {
  list-style: none;
  margin: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 10px 0;
  position: relative;
}

.navigation__link {
  color: inherit;
  line-height: inherit;
  word-wrap: break-word;
  text-decoration: none;
  background-color: transparent;
  display: block;
  text-transform: uppercase;
  letter-spacing: 1.5px;
  font-size: 0.875rem;
  font-weight: bold;
  /* 修复错误的margin写法 */
  margin: 15px 40px 0 40px;
  position: relative;
}

.navigation__line {
  height: 2px;
  position: absolute;
  bottom: 0;
  margin: 10px 0 0 0;
  background: red;
  transition: all 0.35s ease;
}

.navigation__item {
  list-style: none;
  display: flex;
}

JS代码

var navList = document.querySelector(".navigation__list");
var navItems = navList.getElementsByClassName("navigation__item");
var navLine = document.querySelector(".navigation__line");

// 将状态变量移到循环外部,避免每次遍历被重置
var prevItem = 1;
var currentItem = 1;

// 初始化下划线位置宽度
initLine();

for (var i = 0; i < navItems.length; i++) {
  navItems[i].classList.add(`navigation__item--${i + 1}`);
  navItems[i].setAttribute("index", `${i + 1}`);

  navItems[i].addEventListener("click", function() {
    var current = document.getElementsByClassName("active");
    if (current.length > 0) {
      current[0].className = current[0].className.replace(" active", "");
    }
    this.className += " active";

    prevItem = currentItem;
    // 转成数字,避免字符串比较出错
    currentItem = Number(this.getAttribute("index"));

    // 获取前后两个选中链接的尺寸信息
    var prevLink = document.querySelector(`.navigation__item--${prevItem} .navigation__link`);
    var currentLink = this.querySelector(".navigation__link");
    var prevRect = prevLink.getBoundingClientRect();
    var currentRect = currentLink.getBoundingClientRect();
    var listRect = navList.getBoundingClientRect();

    // 相对于父容器的左偏移
    var prevLeft = prevRect.left - listRect.left;
    var currentLeft = currentRect.left - listRect.left;

    let stretchLeft, stretchWidth;
    if (currentItem > prevItem) {
      // 右滑:从之前的位置开始拉伸到当前项的右端
      stretchLeft = prevLeft;
      stretchWidth = (currentRect.left + currentRect.width) - prevRect.left;
    } else {
      // 左滑:从当前项的位置开始拉伸到之前项的右端
      stretchLeft = currentLeft;
      stretchWidth = (prevRect.left + prevRect.width) - currentRect.left;
    }

    // 第一阶段:拉伸
    navLine.style.left = `${stretchLeft}px`;
    navLine.style.width = `${stretchWidth}px`;

    // 第二阶段:收缩到当前项的宽度和位置
    setTimeout(function() {
      navLine.style.left = `${currentLeft}px`;
      navLine.style.width = `${currentRect.width}px`;
    }, 350);
  });
}

function initLine() {
  var activeLink = document.querySelector(".navigation__item.active .navigation__link");
  var activeRect = activeLink.getBoundingClientRect();
  var listRect = navList.getBoundingClientRect();
  navLine.style.left = `${activeRect.left - listRect.left}px`;
  navLine.style.width = `${activeRect.width}px`;
}

内容的提问来源于stack exchange,提问作者ashlward

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.07 15:09:03