一、前言
项目中经常会遇到文件列表相关的需求,其中框选文件是比较常见的需求,因此提取了一个小组件,如大家有更好的实现方式,可以评论区交流一下。
demo链接:code.juejin.cn/api/raw/731...
二、如何实现
1.容器
第一步,创建多个块,使用数组记录并动态设置active以高亮边框。
js
<div
class="box"
:class="{
'active': activeList.includes(index)
}"
v-for="(item, index) in 50"
>{{ index }}</div>
第二步,使用getBoundingClientRect计算盒子信息,用于后续位置判定。
js
const calcBox = (arr, dom, baseScrollTop) => {
const length = dom.children.length;
for(let i = 0; i < length; i++) {
let child = dom.children[i];
let domData = child.getBoundingClientRect();
let obj = {
index: i,
dom: child,
position: [
domData.x,
domData.y + baseScrollTop,
domData.width,
domData.height,
]
}
arr.push(obj);
}
}
2.计算选择框是否包含块
根据选择框的left,top,width,height,以及前期收集的盒子数据数组判断是否处于框内。
从x轴方向分析,有三种情况需要考虑,y轴同理。
根据框的四维和盒子的思维做判断
js
const calcSelect = (left, top, width, height, boxList) => {
const arr = new Set()
for (let i = 0; i < boxList.length; i++) {
const p = boxList[i].position
if (
(left <= p[0] && left + width > p[0]) ||
(left > p[0] && left < p[0] + p[2] && width > 0)
) {
if (
(top <= p[1] && top + height > p[1]) ||
(top > p[1] && top < p[1] + p[3] && height > 0)
) {
arr.add(boxList[i].index)
} else {
arr.delete(boxList[i].index)
}
} else {
arr.delete(boxList[i].index)
}
}
return Array.from(arr)
}
3.鼠标事件中记录需要的值
使用mouseevent中的clientX和clientY记录鼠标位置,同时需要考虑页面原本的滚动高度。
移动中需考虑整体方向是向左还是向右,会影响到选择框的思维绘制。
js
const mousedown = (e) => {
let baseL = e.clientX,
baseT = e.clientY,
baseScrollTop = document.children[0].scrollTop, // html根节点,需根据实际场景替换
boxList = []
calcBox(boxList, dragSelectDom.value, baseScrollTop);
activeList.value = []
document.onmousemove = (ev) => {
showDrag.value = true
const xOffset = ev.clientX - baseL
const yOffset = ev.clientY - baseT
const width = Math.abs(xOffset)
const height = Math.abs(yOffset)
const left = xOffset < 0 ? ev.clientX : baseL
const top = yOffset < 0 ? ev.clientY : baseT
const scrollTop = document.children[0].scrollTop
updateRect(left, top + scrollTop, width, height); // 这个函数用于修改选择框css属性
activeList.value = []
activeList.value = calcSelect(left, top + scrollTop, width, height, boxList)
}
document.onmouseup = () => {
showDrag.value = false
document.onmousemove = null
document.onmouseup = null
}
}
最后将mousedown事件挂载到根节点上即可
三、后续事项
1.首先是判断算法优化,目前是每次移动都需要判断全部块是否有相交,比较耗性能,块越多越卡。
2.还需考虑拖拽触底时候的滚动,可以用requestAnimationFrame模拟滚动,在相应的判断中要增加滚动差的判断。
3.横向滚动
4.针对实际应用,会有无限滚动加载的应用场景,需及时刷新容器数据。