李涛自留地
回到顶部
首页 > jQuery学习 > jquery tab选项卡,我用一个索引变量替掉三个 if

jquery tab选项卡,我用一个索引变量替掉三个 if

文章目录

一个 tab 切换到底要写几个 if?答案取决于两个前提:面板数量固定、按钮与面板一一对应。用 data-index 存索引、把点击交给容器监听,jquery tab选项卡的切换逻辑能压到六行;面板结构各不相同、有的要懒加载有的要保留 DOM 的页面就不适用,索引省下的代码还不够补特判。

一个索引变量,替掉三段重复判断

三个 if 的共同点不在判断,而在每个分支都重复同一组 addClass 和 removeClass,差别只有下标。既然差别只是数字,就把数字抽成变量,判断退化成一次比较。

左侧容器内三枚按钮与右侧三块面板一一对应

DOM 上要提前定两个约定:按钮与面板顺序一致;按钮上用 data-index 显式写出序号,不靠它在父节点里的位置。这两条定好,jquery tab选项卡的切换就只剩下面几行。后一条也是给面板被别的模块插进来时留的余量。

按钮加到第四个,只改 HTML,JS 一行不动;三个 if 的版本不报错,只是第四个按钮点下去没反应。

$(function () {
  var current = 0;
  var $btns = $('.tab-btn');
  var $panels = $('.tab-panel');

  $('.tab-nav').on('click', '.tab-btn', function () {
    var index = parseInt($(this).attr('data-index'), 10);
    if (index === current) { return; }
    $btns.eq(current).removeClass('active');
    $panels.eq(current).removeClass('active');
    $btns.eq(index).addClass('active');
    $panels.eq(index).addClass('active');
    current = index;
  });
});

current 只记上一次亮的是谁。点同一个按钮直接 return,少两次 removeClass 再加回来;不写这个判断,点击当前 tab 时面板会闪一下,带过渡动画的组件上看得很清楚。

序号写进 class(tab-btn-2)也能跑,但按钮顺序一调就得跟着改名,改漏一处就错位。面板这边不用再标号,代码走 $panels.eq(index) 按下标取,代价是按钮的号必须与面板的排列顺序死死对齐,中间插进一块面板就得连号一起改。

从 DOM 到变量的三步取值

取索引是三步:读属性、转数字、按下标取集合。三步里任何一步拿到 undefined,结果都是点击无反应,控制台不报错。这类故障最费时间,因为没有报错可看,只能靠断点一行行回推。

index() 看起来更短,$btns.index($(this)) 一行就出下标,按钮内部没有子元素时它是对的。

按钮里加一层 <span> 放图标之后,$(this) 变成那个 span,index() 在 $btns 里找不到它,返回 -1。eq(-1) 取的是最后一个面板:点第二个 tab,亮的是最后一个,控制台仍然安静。这个现象容易被当成 CSS 优先级问题去查。

四个方框自上而下串起点击到面板,右侧引出一条返回分支

要绕开它,用 $(this).closest('.tab-btn') 把层级抹平再读属性,或者干脆不在按钮里嵌 span,图标走背景图。

另一处是读法。.data('index') 第一次读会把值缓存在 jQuery 内部,之后用 attr('data-index', 2) 改属性,再读 .data('index') 拿到的还是旧值。读写统一走 attr,或者统一走 data,不要一边写一边读。

取值链上会出错的只有两处:层级不对导致 -1,读写方式不一致导致旧值。两种都不报错,现象却完全不同。

事件委托绑在容器上,重复触发才会消失

按钮是服务端渲染的静态节点时,直接 $('.tab-btn').on('click', ...) 也能跑。绑到 .tab-nav 上是为了另一种情况:tab 由 ajax 拉回来,节点在绑定之后才进 DOM,逐个绑定会漏掉后来的那些。

on('click', '.tab-btn', fn) 与直接绑有两处不同。一处是 this:回调里 this 指向命中的按钮,不是容器。写成箭头函数就没有 this,$(this).attr('data-index') 得到 undefined,parseInt 得到 NaN,eq(NaN) 是空集合,点下去什么都不会发生。

另一处是重复绑定。

把这段 on 放进另一个 click 回调,或者放进每次 ajax 成功的回调里,第一次点击正常,第二次点击控制台打出两行 index,面板闪一下又切回去。容器上的监听器只需要绑一次,节点被替换也不会失效。

左右两栏分别标着两种显隐写法的差别

拿 e.target 定位会遇到同一个问题:target 是真正被点的那个节点,可能是 span,也可能是 svg 里的 path。$(e.target).closest('.tab-btn') 才拿得回按钮本身,this 反而更直接。

升级到 jQuery 3.x,jquery tab选项卡会露出两个旧写法

网上多数 tab 教程还写着 .live('click', ...)。jQuery 1.9 已将它移除,3.x 执行到那一行抛 TypeError: $(...).live is not a function。脚本停在那里,后面的初始化都不执行,页面表现为所有 tab 全没反应 —— 不是某个 tab 坏了,是整条链没跑起来。这类整段失效的故障,用jquery tab选项卡时最容易被误判成绑定没生效。

替代写法就是 .on('click', '.tab-btn', fn),参数顺序一致,事件名放前面,剩下的代码不用动。

同一段老代码里还有一处:.hide() 之后 .show(),jQuery 会把内联样式设成 display: block;面板本来是 flex 布局,show 一执行布局就散,看着像样式丢了。父容器宽度、子项间距全乱,但 CSS 文件一个字没改。

把这层交给 active 类更稳:CSS 里写 .tab-panel { display: none } 与 .tab-panel.active { display: flex },JS 只切类名。这条与 jQuery 版本无关,3.x 和 1.x 表现一致。

什么时候不该这么写

面板超过十来个,还要键盘左右方向键、aria-selected、URL hash 同步,手写索引变量要补的东西比切换本身多。这时候用 ARIA 的 tab 模式(button 加 role 与 aria-controls),或者直接上 jQuery UI tabs,代价是它的 DOM 约定固定,ul > li > a 那一层嵌不进现有布局。

站点已经跑 Vue 或 React 的,别在 jQuery 里再存一份 current。两处状态源,DOM 归谁管会打架,改一次样式要查两边。

面板里有 iframe 或播放器,jquery tab选项卡靠 display 切显隐会把 iframe 重新加载,切回来进度归零。要么保留 DOM 用 visibility 加绝对定位,代价是首屏多加载几个面板;要么接受重载。

这类场合硬套索引变量,代价比多写两个 if 高。

若非作者标注皆为李涛自留地原创文章,转载或复制请以超链接形式并注明出处。