100 lines
3.9 KiB
JavaScript
100 lines
3.9 KiB
JavaScript
// 自 https://blog.wy310.cn/2020/12/17/vue-drag-batch-select/ 修改
|
||
Vue.directive('batch-select', {
|
||
// 当被绑定的元素插入到 DOM 中时……
|
||
// inserted: (el, binding) => {}
|
||
componentUpdated: (el, binding) => {
|
||
// 设置被绑定元素el(即上述的box)的position为relative,目的是让蓝色半透明遮罩area相对其定位
|
||
el.style.position = 'relative';
|
||
// 记录el在视窗中的位置elPos
|
||
const { x, y } = el.getBoundingClientRect()
|
||
const elPos = { x, y }
|
||
// 获取该指令调用者传递过来的参数:className / selectArr 是选中的 index 要放的数组
|
||
// v-batch-select="{ className: '.el-checkbox', selectArr: [] }"
|
||
// 表示要使用鼠标框选类名为'.el-checkbox'的元素
|
||
const optionClassName = binding.value.className
|
||
const options = [].slice.call(el.querySelectorAll(optionClassName))
|
||
// 获取被框选对象们的x、y、width、height
|
||
const optionsXYWH = []
|
||
options.forEach(v => {
|
||
const obj = v.getBoundingClientRect()
|
||
optionsXYWH.push({
|
||
x: obj.x - elPos.x,
|
||
y: obj.y - elPos.y,
|
||
w: obj.width,
|
||
h: obj.height
|
||
})
|
||
})
|
||
// 创建一个div作为area区域,注意定位是absolute,visibility初始值是hidden
|
||
const area = document.createElement('div')
|
||
area.style = 'position: absolute; border: 1px solid #409eff; background-color: rgba(64, 158, 255, 0.1); z-index: 10; visibility: hidden;'
|
||
area.className = 'area'
|
||
area.innerHTML = ''
|
||
el.appendChild(area)
|
||
el.onmousedown = (e) => {
|
||
// 获取鼠标按下时相对box的坐标
|
||
const startX = e.clientX - elPos.x
|
||
const startY = e.clientY - elPos.y
|
||
// 判断鼠标按下后是否发生移动的标识
|
||
let hasMove = false
|
||
|
||
|
||
document.onmousemove = (e) => {
|
||
hasMove = true
|
||
binding.value.setSelectStatus(true)
|
||
// 显示area
|
||
area.style.visibility = 'visible'
|
||
// 获取鼠标移动过程中指针的实时位置
|
||
const endX = e.clientX - elPos.x
|
||
const endY = e.clientY - elPos.y
|
||
// 这里使用绝对值是为了兼容鼠标从各个方向框选的情况
|
||
const width = Math.abs(endX - startX)
|
||
const height = Math.abs(endY - startY)
|
||
// 根据初始位置和实时位置计算出area的left、top、width、height
|
||
const left = Math.min(startX, endX)
|
||
const top = Math.min(startY, endY)
|
||
// 实时绘制
|
||
area.style.left = `${left}px`
|
||
area.style.top = `${top}px`
|
||
area.style.width = `${width}px`
|
||
area.style.height = `${height}px`
|
||
|
||
const areaTop = parseInt(top)
|
||
const areaRight = parseInt(left) + parseInt(width)
|
||
const areaBottom = parseInt(top) + parseInt(height)
|
||
const areaLeft = parseInt(left)
|
||
binding.value.selectImageIndex.length = 0
|
||
optionsXYWH.forEach((v, i) => {
|
||
const optionTop = v.y
|
||
const optionRight = v.x + v.w
|
||
const optionBottom = v.y + v.h
|
||
const optionLeft = v.x
|
||
if (!(areaTop > optionBottom || areaRight < optionLeft || areaBottom < optionTop || areaLeft > optionRight)) {
|
||
// 该指令的调用者可以监听到selectIdxs的变化
|
||
binding.value.selectImageIndex.push(i)
|
||
}
|
||
})
|
||
|
||
}
|
||
|
||
document.onmouseup = (e) => {
|
||
document.onmousemove = document.onmouseup = null
|
||
if (hasMove) {
|
||
// 鼠标抬起时,如果之前发生过移动,则执行碰撞检测
|
||
const { left, top, width, height } = area.style
|
||
setTimeout(() => {
|
||
binding.value.setSelectStatus(false)
|
||
}, 100);
|
||
}
|
||
// 恢复以下数据
|
||
hasMove = false
|
||
area.style.visibility = 'hidden'
|
||
area.style.left = 0
|
||
area.style.top = 0
|
||
area.style.width = 0
|
||
area.style.height = 0
|
||
return false
|
||
}
|
||
}
|
||
},
|
||
// update: (el, binding) => {}
|
||
}) |