|
做后台管理系统,全选列表页几乎绕不开全选和全不选。和全我见过不少项目,不选这块代码写得又长又乱——给每个复选框绑事件,代码或者用循环一个个改 checked。全选其实原生 JS 处理这件事,和全核心就两个点:一个控制全选的不选复选框,加一组结构相同的代码子复选框。 先把 HTML 骨架搭出来,全选别搞复杂: <input type="checkbox" id="checkAll"> 全选 <div id="list"> <input type="checkbox" class="item"> 选项一 <input type="checkbox" class="item"> 选项二 <input type="checkbox" class="item"> 选项三 </div> 关键在 JS。和全全选按钮被点击时,不选把它的代码 checked 状态同步给所有子项。用 querySelectorAll 拿到 NodeList,全选再用 forEach 遍历。和全代码短到离谱: const checkAll = document.getElementById('checkAll'); const items = document.querySelectorAll('.item'); checkAll.addEventListener('change',不选 function () { items.forEach(item => item.checked = this.checked); }); 注意这里用了 this.checked,而不是 checkAll.checked。在普通函数里 this 指向触发事件的元素,用 this 更稳。如果你用箭头函数,this 会指向外层作用域,那就得老老实实写 checkAll.checked。 反过来,用户手动点某个子项时,全选框的状态也得跟着变。逻辑是:如果所有子项都被选中,全选框就勾上;只要有一个没选,全选框就取消。我一般用 every 来判断: items.forEach(item => { item.addEventListener('change', function () { checkAll.checked = [...items].every(i => i.checked); }); }); 这里有个细节,items 是 NodeList,不是真数组,所以得用扩展运算符转一下才能调 every。当然你也可以用 Array.from(items).every(...),看个人习惯。 实际项目里,列表数据往往是动态渲染的。比如用 fetch 拿到 JSON 后拼 HTML,或者用框架的列表渲染。这时候事件绑定不能写死在页面加载时,得用事件委托。把 change 监听挂到父容器上,通过 event.target 判断点的是谁: document.getElementById('list').addEventListener('change', function (e) { if (e.target.classList.contains('item')) { checkAll.checked = [...items].every(i => i.checked); } }); 但注意,items 如果是动态生成的,每次重新渲染后得重新获取。要么把 querySelectorAll 放进事件处理函数里,要么在渲染完成后更新变量。我吃过这个亏——列表翻页后全选失效,查了半天发现 items 还是旧的 NodeList。 还有一种常见需求:全选后取消其中一个,全选框自动取消;再重新勾上,全选框又自动勾上。上面 every 那行代码已经覆盖了。但如果你有“半选”状态要展示,就得额外处理 indeterminate 属性。比如选中了一部分,全选框显示横杠而不是勾。代码大概长这样: const checkedCount = [...items].filter(i => i.checked).length; checkAll.indeterminate = checkedCount > 0 && checkedCount < items.length; checkAll.checked = checkedCount === items.length; 半选状态在视觉上是个小横杠,浏览器原生支持,不用自己画。但要注意,indeterminate 只能通过 JS 设置,用户点击全选框时它会自动清除,所以每次 change 事件里都得重新算。 最后提一个容易踩的坑。如果你在表单里用 <label> 包裹复选框,点击文字也会触发 change,逻辑上没问题。但有些老项目用 onclick 绑在 input 上,点击 label 时可能触发两次——一次是 label 转发,一次是 input 自身。换成 addEventListener('change') 就没这个问题。 代码不复杂,但细节决定它能不能在真实项目里稳定跑。我自己的习惯是,全选逻辑单独抽一个函数,叫 syncCheckAll,每次子项变化或列表更新后调一次。这样不管后面加搜索、加分页,逻辑都不会散得到处都是。 |