demo效果:
实现以上demo需要考虑两个方向:
- 解决鼠标绘制矩形问题
- 绘制的矩形与当前容器里的元素是否成包含或者交叉关系
1.解决鼠标绘制矩形问题
首先要明确用到的鼠标事件:
- mousedown (鼠标按下事件)
- mousemove(鼠标移动过程)
- mouseup (鼠标抬起事件)
进入正题,我们先定义一个容器盒子居中
<div class="box">
<span class="dragCanvas"></span> // 矩形框元素
</div>
.box {
width: 800px;
height: 500px;
border: 1px solid red;
margin: 50px auto 0;
user-select: none;
-webkit-user-select: none; // 防止绘制时选中了容器中出现的文本文案
position: relative; //这是重点设置
overflow: hidden;
}
.box .dragCanvas {
position: absolute;
background-color: rgba(90,187,247, 0.3);
z-index: 999;
}
然后进行js的变量定义
const box = document.querySelector('.box');
const boxInfo = box.getBoundingClientRect(); // 获取容器的位置属性
const dragCanvas = document.querySelector('.dragCanvas');
let offsets = {
x: 0,
y: 0
} // 先设置一个鼠标按下的坐标点存储
let isClicked = false; // 定义鼠标按下时的状态
完成变量定义后正式进入鼠标事件
1.1 鼠标按下事件
box.addEventListener('mousedown', function(e) {
offsets = {
x: e.clientX - boxInfo.x,
y: e.clientY - boxInfo.y,
}
isClicked = true;
dragCanvas.style.cssText += `
display: block;
left: ${offsets.x}px;
top: ${offsets.y}px;
border: 1px solid blue;
`;
})
在容器里鼠标首次按下时需要记录一些状态,并且生成矩形的样式雏型态,并且isClicked变量改成true记录当前已经开始进行绘制中。
这里需要注意的是offsets变量为什么不是直接获取相对于容器的e.offsetX坐标点而是采用了e.clientX基于窗口的坐标?
是因为采用offsetX坐标来计算移动时绘制矩形过程会出现抖动和残影的状况。是因为我们在鼠标移动过程中一直更改dom元素的宽高会导致浏览器重绘而至于当前容器的坐标点发生变化,所以需要我们基于窗口坐标来减去容器的偏移量来计算,这样效果才能达到丝滑。
1.2 鼠标移动事件
box.addEventListener('mousemove', function(e) {
if(isClicked) {
const currentX = e.clientX - boxInfo.x;
const currentY = e.clientY - boxInfo.y;
const isUpDownY = currentY < offsets.y; // 如果是负数鼠标是往下移动
const isUpDownX = currentX < offsets.x;
const rect = {
width: Math.abs(currentX - offsets.x),
height: Math.abs(currentY - offsets.y)
}
dragCanvas.style.cssText += `
left: ${isUpDownX ? currentX : offsets.x}px;
top: ${isUpDownY ? currentY : offsets.y}px;
width: ${rect.width}px;
height: ${rect.height}px;
`;
}
})
详解:
鼠标在容器里移动时,这里的函数会进行操作,然后我们需要根据之前定义的isClicked变量来判断是否需要进行绘制,
currentX:是获取当前的鼠标移动产生的x轴位置
currentY: 是获取当前的鼠标移动产生的y轴位置
这两个坐标为什么要减去容器的偏移量呢?
是因为之前所说到的需要根据以窗口位置存在的偏移量来减去容器的偏移量得到相对于容器里的位置偏移量这样不会出现绘制抖动效果。
这里还需要判断当前鼠标是否是往上或者往左移动,这时就要更改矩形的顶点位置,其中代码给予的解决判断就是通过isUpDownY和isUpDownX两个变量。
- isUpDownX:
如果鼠标移动时获取的X位置小于鼠标按下记录的X位置,则说明它是往左偏移反之就是往右 - isUpDownY:
如果鼠标移动时获取的Y位置小于鼠标按下记录的Y位置,则说明它是往上偏移反之就是往下
1.这时我们给矩形设置的顶点位置(left/top)就需要根据isUpDownX和isUpDownY的方向来判断当前应该是设置鼠标移动产生坐标还是使用鼠标按下记录的坐标点.
2.矩形的宽高则需要通过绝对值计算
1.3 鼠标抬起事件
box.addEventListener('mouseup', function(e) {
isClicked = false;
console.log("抬起")
dragCanvas.style.cssText += `
display: none;
width: 0;
height: 0;
`;
})
鼠标抬起事件就只需要做一些矩形框的销毁处理就行和变量状态重置,简简单单的就结束了鼠标绘制的过程。
效果展示:
2. 绘制的矩形与当前容器里的元素是否成包含或者交叉关系
现在鼠标绘制矩形做好了,需要针对矩形来实现选中元素的操作了
2.1 首先生成一个list
在之前容器里添加一个ul标签,样式这里就不做演示了
<div class="box">
<span class="dragCanvas"></span>
<ul></ul>
</div>
通过js自动生成27个li标签
const ul = document.querySelector('ul');
new Array(27).fill(0).forEach((item, index) => {
const li = document.createElement('li');
li.innerHTML = index
ul.append(li)
})
得到效果图:
鼠标绘制选中当前某些元素的主要逻辑在于元素的边界计算
判断当前某个元素是否包含在另一个元素里
function isContained(rect1, rect2) {
return (
rect1.left >= rect2.left &&
rect1.right <= rect2.right &&
rect1.top >= rect2.top &&
rect1.bottom <= rect2.bottom
);
}
判断当前某个元素与另一个元素是否存在交集
function isIntersecting(rect1, rect2) {
return !(
rect1.right <= rect2.left ||
rect1.left >= rect2.right ||
rect1.bottom <= rect2.top ||
rect1.top >= rect2.bottom
);
}
通过这两个方法后我们就只需要在鼠标移动事件里进行处理就行
box.addEventListener('mousemove', function(e) {
//...之前的矩形绘制方法省略
const lis = document.querySelectorAll('li');
lis.forEach((el, index) => {
const liInfo = el.getBoundingClientRect();
if(isIntersecting(dragCanvasInfo, liInfo) || isContained(dragCanvasInfo, liInfo)) {
// console.log("包含了", index)
el.classList.add('active')
} else {
el.classList.remove('active');
}
})
})
这里获取当前所有的li标签属性,然后在循环将单个li标签元素和绘制的矩形框元素放在isIntersecting和isContained方法里进行判断。符合当前交叉包含的条件元素给添加上“active”样式名就行,否则就移除掉active就行
.box ul li.active {
background-color: red;
}
最后想让每次鼠标按下时清空之前的框选高亮的元素,需要进行一次清空处理
box.addEventListener('mousedown', function(e) {
//省略掉之前的逻辑
//...
const lis = document.querySelectorAll('li');
lis.forEach((el) => {
el.classList.remove('active')
});
})
这就是全部的鼠标绘制矩形的全部详解教程了,后续有时间了会补上一份git的源码,敬请关注