实现功能
在PC端的H5页面中,客户拖动鼠标可以连选多个选项
效果展示
具体代码如下
<!DOCTYPE html>
<html>
<head>
<title>鼠标拖拽多选功能</title>
<script src="https://cdn.bootcss.com/jquery/1.10.2/jquery.min.js"></script>
<style type="text/css">
* {
box-sizing: border-box;
}
ul {
width: 500px;
height: auto;
margin: 0;
padding: 20px;
font-size: 0;
/*需设置定位*/
position: relative;
}
li {
width: 70px;
height: 70px;
padding: 0;
display: inline-block;
vertical-align: top;
font-size: 13px;
border: 1px solid #d9d9d9;
}
#moveSelected {
position: absolute;
background-color: blue;
opacity: 0.3;
border: 1px dashed #d9d9d9;
top: 0;
left: 0;
}
.selected {
background-color: pink;
}
</style>
</head>
<body>
<ul class="list">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
<li>6</li>
<li>7</li>
<li>8</li>
<li>9</li>
<li>10</li>
<li>11</li>
<li>12</li>
<li>13</li>
<li>14</li>
<li>15</li>
<li>16</li>
<li>17</li>
<li>18</li>
<li>19</li>
<li>20</li>
<li>21</li>
<li>22</li>
<!-- 鼠标拖拽出的遮罩 (定位为 position:absolute)-->
<!-- 遮罩最好是在绑定了mouseover事件的元素内部,并且不要阻止遮罩的冒泡事件。这样鼠标移到了遮罩上面,依然可以利用冒泡执行父元素的mouseover事件,就不会出现遮罩只能扩大,不能缩小的情况了(亲自试过) -->
<div id="moveSelected"></div>
</ul>
</body>
</html>
<script type="text/javascript">
$(document).ready(function () {
let moveSelected = $('#moveSelected')[0];
console.log(moveSelected);
let flag = false; //是搜开启拖拽的标志
let oldLeft = 0; //鼠标按下时的left,top
let oldTop = 0;
let selectedList = []; //拖拽多选选中的块集合
// 鼠标按下时开启拖拽多选,将遮罩定位并展现
$(".list").mousedown(function (event) {
flag = true;
moveSelected.style.top = event.pageY + 'px';
moveSelected.style.left = event.pageX + 'px';
oldLeft = event.pageX;
oldTop = event.pageY;
event.preventDefault(); // 阻止默认行为
event.stopPropagation(); // 阻止事件冒泡
});
// 鼠标移动时计算遮罩的位置,宽 高
$(".list").mousemove(function (event) {
if (!flag) return; //只有开启了拖拽,才进行mouseover操作
if (event.pageX < oldLeft) { //向左拖
moveSelected.style.left = event.pageX + 'px';
moveSelected.style.width = (oldLeft - event.pageX) + 'px';
} else {
moveSelected.style.width = (event.pageX - oldLeft) + 'px';
}
if (event.pageY < oldTop) { //向上
moveSelected.style.top = event.pageY + 'px';
moveSelected.style.height = (oldTop - event.pageY) + 'px';
} else {
moveSelected.style.height = (event.pageY - oldTop) + 'px';
}
event.preventDefault(); // 阻止默认行为
event.stopPropagation(); // 阻止事件冒泡
});
//鼠标抬起时计算遮罩的right 和 bottom,找出遮罩覆盖的块,关闭拖拽选中开关,清除遮罩数据
$(".list").mouseup(function (event) {
moveSelected.style.bottom = Number(moveSelected.style.top.split('px')[0]) + Number(
moveSelected.style.height.split('px')[0]) + 'px';
moveSelected.style.right = Number(moveSelected.style.left.split('px')[0]) + Number(
moveSelected.style.width.split('px')[0]) + 'px';
findSelected();
flag = false;
clearDragData();
event.preventDefault(); // 阻止默认行为
event.stopPropagation(); // 阻止事件冒泡
});
$(".list").mouseleave(function (event) {
flag = false;
moveSelected.style.width = 0;
moveSelected.style.height = 0;
moveSelected.style.top = 0;
moveSelected.style.left = 0;
event.preventDefault(); // 阻止默认行为
event.stopPropagation(); // 阻止事件冒泡
});
function findSelected() {
let blockList = $('.list').find('li');
for (let i = 0; i < blockList.length; i++) {
//计算每个块的定位信息
let left = $(blockList[i]).offset().left;
let right = $(blockList[i]).width() + left;
let top = $(blockList[i]).offset().top;
let bottom = $(blockList[i]).height() + top;
let leftTwo = moveSelected.style.left.split('px')[0]
let rightTwo = moveSelected.style.right.split('px')[0]
let topTwo = moveSelected.style.top.split('px')[0]
let bottomTwo = moveSelected.style.bottom.split('px')[0]
// 判断碰撞
if (!(top > bottomTwo || right < leftTwo || bottom < topTwo || left> rightTwo)) {
// 碰撞的情况
selectedList.push(blockList[i]);
$(blockList[i]).addClass('selected');
}
}
console.log(selectedList);
}
function clearDragData() {
moveSelected.style.width = 0;
moveSelected.style.height = 0;
moveSelected.style.top = 0;
moveSelected.style.left = 0;
moveSelected.style.bottom = 0;
moveSelected.style.right = 0;
}
});
</script>
考虑方便使用Vue框架的情况,新增一个Vue 3的参考代码,且如果多选时处于已经选择状态也会取消选择
//
// MutiSelectPage
// mrs-console-ui
//
// Created by gaolailong on 2024/05/23.
// Copyright © 2024 上海复微迅捷数字科技股份有限公司. All rights reserved.
//
<template>
<div class="page">
<div class="list" @mousedown="handleMouseDown" @mousemove="handleMouseMove" @mouseup="handleMouseUp"
@mouseleave="handleMouseLeave">
<template v-for="(item, index) in list" :key="index">
<div class="can-select-div" :class="item.selected ? 'selected' : ''" :data-index="index">{{ item.text }}
</div>
</template>
<!-- 鼠标拖拽出的遮罩 (定位为 position:absolute)-->
<!-- 遮罩最好是在绑定了mouseover事件的元素内部,并且不要阻止遮罩的冒泡事件。这样鼠标移到了遮罩上面,依然可以利用冒泡执行父元素的mouseover事件,就不会出现遮罩只能扩大,不能缩小的情况了(亲自试过) -->
<div id="moveSelected"></div>
</div>
</div>
</template>
<script lang='ts'>
import { defineComponent, onMounted, reactive } from 'vue'
export default defineComponent({
name: 'MutiSelectPage',
setup() {
let moveSelected: HTMLElement | null = null
onMounted(() => {
moveSelected = document.getElementById('moveSelected') as HTMLElement;
})
const list: Array<Record<string, unknown>> = reactive([])
for (let index = 0; index < 10; index++) {
const listItem = {
text: index,
selected: false
}
list.push(listItem)
}
let flag = false; //是搜开启拖拽的标志
let oldLeft = 0; //鼠标按下时的left,top
let oldTop = 0;
const handleMouseDown = (event: any) => {
// 处理鼠标按下事件
console.log('鼠标按下');
if (!moveSelected) return;
flag = true;
moveSelected.style.top = event.pageY + 'px';
moveSelected.style.left = event.pageX + 'px';
oldLeft = event.pageX;
oldTop = event.pageY;
event.preventDefault(); // 阻止默认行为
event.stopPropagation(); // 阻止事件冒泡
}
const handleMouseMove = (event: any) => {
// 处理鼠标移动事件的逻辑
if (!moveSelected) return;
if (!flag) return; //只有开启了拖拽,才进行mouseover操作
console.log("鼠标移动了");
if (event.pageX < oldLeft) { //向左拖
moveSelected.style.left = event.pageX + 'px';
moveSelected.style.width = (oldLeft - event.pageX) + 'px';
} else {
moveSelected.style.width = (event.pageX - oldLeft) + 'px';
}
if (event.pageY < oldTop) { //向上
moveSelected.style.top = event.pageY + 'px';
moveSelected.style.height = (oldTop - event.pageY) + 'px';
} else {
moveSelected.style.height = (event.pageY - oldTop) + 'px';
}
event.preventDefault(); // 阻止默认行为
event.stopPropagation(); // 阻止事件冒泡
}
const handleMouseUp = (event: any) => {
if (!moveSelected) return;
// 处理鼠标弹起事件
console.log('鼠标弹起');
moveSelected.style.bottom = Number(moveSelected.style.top.split('px')[0]) + Number(
moveSelected.style.height.split('px')[0]) + 'px';
moveSelected.style.right = Number(moveSelected.style.left.split('px')[0]) + Number(
moveSelected.style.width.split('px')[0]) + 'px';
findSelected();
flag = false;
clearDragData();
event.preventDefault(); // 阻止默认行为
event.stopPropagation(); // 阻止事件冒泡
}
const handleMouseLeave = (event: any) => {
console.log('鼠标离开元素');
if (!moveSelected) return;
flag = false;
moveSelected.style.width = '0';
moveSelected.style.height = '0';
moveSelected.style.top = '0';
moveSelected.style.left = '0';
event.preventDefault(); // 阻止默认行为
event.stopPropagation(); // 阻止事件冒泡
}
function findSelected() {
if (!moveSelected) return;
let leftTwo = Number(moveSelected.style.left.split('px')[0])
let rightTwo = Number(moveSelected.style.right.split('px')[0])
let topTwo = Number(moveSelected.style.top.split('px')[0])
let bottomTwo = Number(moveSelected.style.bottom.split('px')[0])
// 使用ref获取dom有一个奇怪的现象,手动切换按天/按周时,ref获取到的值不会丢失之前的数据。除非点击外部的“确认查询”按钮。
const blockList = document.getElementsByClassName('can-select-div')
// console.log(blockList) // 访问DOM元素
for (let i = 0; i < blockList.length; i++) {
//计算每个块的定位信息
let left = (blockList[i] as HTMLDivElement).offsetLeft;
let right = (blockList[i] as HTMLDivElement).offsetWidth + left;
let top = (blockList[i] as HTMLDivElement).offsetTop;
let bottom = (blockList[i] as HTMLDivElement).offsetHeight + top;
// 通过比较两个矩形(用 aDiv 和 bDiv 表示)的位置信息来判断它们是否发生碰撞。以下是该碰撞函数的实现思路:
// 首先,通过获取 aDiv 和 bDiv 的位置信息,分别计算它们的上边界(t1 和 t2)、右边界(r1 和 r2)、下边界(b1 和 b2)以及左边界(l1 和 l2)。
// 接下来,通过比较这些边界信息,判断两个矩形是否发生碰撞。碰撞的情况可以通过以下四个条件中的任意一个来判断:
// 如果矩形 aDiv 的上边界大于矩形 bDiv 的下边界,说明 aDiv 在 bDiv 的上方,没有碰撞。
// 如果矩形 aDiv 的右边界小于矩形 bDiv 的左边界,说明 aDiv 在 bDiv 的左侧,没有碰撞。
// 如果矩形 aDiv 的下边界小于矩形 bDiv 的上边界,说明 aDiv 在 bDiv 的下方,没有碰撞。
// 如果矩形 aDiv 的左边界大于矩形 bDiv 的右边界,说明 aDiv 在 bDiv 的右侧,没有碰撞。
// 如果上述条件中的任意一个不满足,那么矩形 aDiv 和 bDiv 就发生了碰撞,函数返回 true,否则返回 false 表示没有碰撞。
// 判断碰撞
if (!(top > bottomTwo || right < leftTwo || bottom < topTwo || left > rightTwo)) {
// 碰撞的情况
console.log('碰撞了');
const itemIndex = (blockList[i] as HTMLDivElement).dataset.index
if (!itemIndex) {
return
}
const item = list[Number(itemIndex)]
item.selected = !item.selected
list.splice(Number(itemIndex), 1, item)
}
}
}
const clearDragData = () => {
if (!moveSelected) return;
moveSelected.style.width = '0';
moveSelected.style.height = '0';
moveSelected.style.top = '0';
moveSelected.style.left = '0';
moveSelected.style.bottom = '0';
moveSelected.style.right = '0';
}
return {
handleMouseDown,
handleMouseMove,
handleMouseUp,
handleMouseLeave,
list,
}
}
})
</script>
<style scoped>
.page {
box-sizing: border-box;
}
.can-select-div {
width: 70px;
height: 70px;
padding: 0;
display: inline-block;
vertical-align: top;
font-size: 13px;
border: 1px solid #d9d9d9;
}
#moveSelected {
position: absolute;
background-color: blue;
opacity: 0.3;
border: 1px dashed #d9d9d9;
top: 0;
left: 0;
}
.selected {
background-color: pink;
}
</style>
参考1、参考2