|
| 1 | +# 10 JS 实现 Checkboxex 中 Shift 批量多选功能 |
| 2 | + |
| 3 | +> 作者:©[缉熙Soyaine](https://github.com/soyaine) |
| 4 | +> 简介:[JavaScript30](https://javascript30.com) 是 [Wes Bos](https://github.com/wesbos) 推出的一个 30 天挑战。项目免费提供了 30 个视频教程、30 个挑战的起始文档和 30 个挑战解决方案源代码。目的是帮助人们用纯 JavaScript 来写东西,不借助框架和库,也不使用编译器和引用。现在你看到的是这系列指南的第 10 篇。完整指南在 [GitHub](https://github.com/soyaine/JavaScript30),喜欢请 Star 哦♪(^∇^*) |
| 5 | +
|
| 6 | +> 创建时间:2017-01-07 |
| 7 | +最后更新:2017-01-07 |
| 8 | + |
| 9 | +## 实现效果 |
| 10 | + |
| 11 | + |
| 12 | + |
| 13 | +初始文档中提供了一组 `checkbox` 类型的 `input` 元素,选中某个复选框时,其 `<p>` 标签中的文字会显示删除线。最终效果是,提供按下 Shift 键后进行多选操作的功能。 |
| 14 | + |
| 15 | +## 过程指南 |
| 16 | + |
| 17 | +1. 获取所有的 `<input>` 元素,并添加事件监听 |
| 18 | + ```js |
| 19 | + const boxs = document.querySelectorAll('.inbox input[type="checkbox"]'); |
| 20 | + boxs.forEach(box => box.addEventListener('click', handleCheck)); |
| 21 | + ``` |
| 22 | +2. 编写 handleCheck 内部的处理逻辑(细节请看下一部分) |
| 23 | + |
| 24 | +## 解决思路 |
| 25 | + |
| 26 | +在谈具体的代码时,先讲讲思路。首先来复现一下,当你按下 Shift 键进行多选时,发生了什么? |
| 27 | + |
| 28 | +1. 选中 A 项 |
| 29 | +2. 按下 Shift |
| 30 | +3. 再选中 B 项 |
| 31 | +4. A-B 之间的所有项都被选中 |
| 32 | + |
| 33 | +关键点就在于 A、B 划出了一个范围,在这个范围之内的元素状态发生了改变。A 是上一次操作选中的对象,B 是此次操作对象,之后的内容将会用这两个单词来叙述。下面的方案就依据划定范围的方法不同来进行区分。 |
| 34 | + |
| 35 | +### 方法一 |
| 36 | + |
| 37 | +Wes Bos 在文档里提供了一种解决办法:用一个变量,来标记这个范围。 |
| 38 | + |
| 39 | +变量初始值为 `false`,当按下 Shift 键且同时选中了某个元素的时候,遍历所有项,遍历过程中,若遇到 A 或 B,则将标记值取反。同时,将所有标记为 `true` 的项设置为选中。 |
| 40 | + |
| 41 | +```js |
| 42 | +let lastChecked; |
| 43 | + |
| 44 | +// 处理方法一:用变量 inBetween 对需要选中的元素进行标记 |
| 45 | +function handleCheck0(e) { |
| 46 | + let inBetween = false; |
| 47 | + if(e.shiftKey && this.checked){ |
| 48 | + boxs.forEach(input => { |
| 49 | + console.log(input); |
| 50 | + if(input === lastChecked || input ===this) { |
| 51 | + inBetween = !inBetween; |
| 52 | + } |
| 53 | + if(inBetween) { |
| 54 | + console.log("on"); |
| 55 | + input.checked = true; |
| 56 | + } |
| 57 | + }); |
| 58 | + } |
| 59 | + lastChecked = this; |
| 60 | +} |
| 61 | +``` |
| 62 | + |
| 63 | +> 延伸思考 |
| 64 | +
|
| 65 | +上面会出现一个问题,初次加载页面时,按住 Shift 再点击某一项,此项之后的元素都会被选中。此外,对于取消选中,无法批量操作。所以我参照了 Stack Overflow 的一个答案: [How can I shift-select multiple checkboxes like GMail?](http://stackoverflow.com/a/659571/6820726) 改进得到第二种解决方案。 |
| 66 | + |
| 67 | +### 方法二 |
| 68 | + |
| 69 | +方法一中的 `inBetween` 仅仅表示此项是否在被选中的范围中,此处会赋给它更多的意义,用它来表示此项是选中还是未选中,而范围划定则由数组来解决。 |
| 70 | + |
| 71 | +首先将获取到的 `<input>` 组转化为数组,针对每次操作,获取 A 和 B,利用 `indexOf()` 来获得 A 和 B 在数组中的索引值,由此即可确定范围,并能通过 `slice()` 来直接截取 A-B 的所有 DOM 元素,并进行状态改变的操作,而变量 `onOff` 表示 A-B 范围内的状态,`true` 表示选中,`false` 表示取消选中。 |
| 72 | + |
| 73 | +1. 转换 Nodelist 为数组 |
| 74 | + ```js |
| 75 | + const boxs = document.querySelectorAll('.inbox input[type="checkbox"]'); |
| 76 | + const boxArr = Array.from(boxs); |
| 77 | + ``` |
| 78 | +2. 针对按下了 Shift 键的情况,获取 A-B 范围 |
| 79 | + ```js |
| 80 | + let start = boxArr.indexOf(this); |
| 81 | + let end = boxArr.indexOf(lastChecked); |
| 82 | + ``` |
| 83 | +3. 截取该范围内的数组元素,并改变选中状态 |
| 84 | + ```js |
| 85 | + boxArr.slice(Math.min(start, end), Math.max(start, end) + 1) |
| 86 | + .forEach(input => input.checked = onOff); |
| 87 | + ``` |
| 88 | +4. 确定选中 or 取消选中 |
| 89 | + ```js |
| 90 | + onOff = lastChecked.checked ? true : false; |
| 91 | + ``` |
| 92 | +5. 标记 A 值 |
| 93 | + ```js |
| 94 | + if(!lastChecked) lastChecked = this; |
| 95 | + /* ... */ |
| 96 | + lastChecked = this; |
| 97 | + ``` |
| 98 | + |
| 99 | +注意,以上几点是按点抽出的分块代码,整合起来的解决办法如下: |
| 100 | + |
| 101 | +```js |
| 102 | +const boxArr = Array.from(boxs); |
| 103 | +let lastChecked; |
| 104 | +let onOff = false; |
| 105 | + |
| 106 | +// 处理方法二:利用数组索引获取需要选中的范围 |
| 107 | +function handleCheck1(e) { |
| 108 | + if(!lastChecked) lastChecked = this; |
| 109 | + onOff = lastChecked.checked ? true : false; |
| 110 | + if(e.shiftKey) { |
| 111 | + let start = boxArr.indexOf(this); |
| 112 | + let end = boxArr.indexOf(lastChecked); |
| 113 | + boxArr.slice(Math.min(start, end), Math.max(start, end) + 1) |
| 114 | + .forEach(input => input.checked = onOff); |
| 115 | + console.log(start + "+" + end); |
| 116 | + } |
| 117 | + lastChecked = this; |
| 118 | +} |
| 119 | +``` |
| 120 | + |
| 121 | +这样一来,挑战 10 就完!成!啦!恭喜走完了 1/3 的路程\(^o^)/~ |
0 commit comments