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
相关产品推荐
相关产品推荐

