边框线虚线动画效果请参阅边框虚线滚动动画特效_虚线滚动效果_你挚爱的强哥的博客-CSDN博客【代码】边框虚线滚动动画特效。_虚线滚动效果https://blog.csdn.net/qq_37860634/article/details/130507289
碰撞检测原理请前往 原生JS完成“一对一、一对多”矩形DIV碰撞检测、碰撞检查,通过计算接触面积(重叠覆盖面积)大小来判断接触对象DOM_js 碰撞检测_你挚爱的强哥的博客-CSDN博客这里就需要去遍历所有的target,计算每个重叠面积大小,挑出面积最大的那一个。stackArea=0代表没有重叠;stackArea >0代表有交集。为了方便计算比较,我们通常是在上面的代码基础上加一个面积大小判断,_js 碰撞检测https://blog.csdn.net/qq_37860634/article/details/121688431
还可以用此组件实现类似资源管理器的圈选效果
sgRectSelect框选组件源码
<template>
<div :class="$options.name" v-if="startPoint && endPoint" :style="style">
<slot></slot>
</div>
</template>
<script>
export default {
name: 'sgRectSelect',
data: () => ({
startPoint: null,
endPoint: null,
style: {
width: '0px',
height: '0px',
top: '0px',
left: '0px',
},
oldSelectedDoms: [],//记录上一次圈选内容用于对比
}),
props: [
"data",//(必选)建议用一个复杂对象,方便后续识别操作
/* data是一个数组格式:
[
{
dom:文档对象,//必选
index:'索引',
id:'元素的id',
refName:'ref别名',
selectEvent:'选中后的操作',
unSelectEvent:'取消选中后的操作',
} ,
...
]
也可以是一维数组,只包含将可能被选中的DOM数组
*/
"disabled",//屏蔽
"minDragDis",//最少拖拽的距离(单位px)缺省值30
"borderWidth",
"borderColor",
"backgroundColor",
],
watch: {
disabled(d) {
if (d) {
this.__removeEvents_mousemove_mouseup();
}
},
},
mounted() { this.__addEvents(); },
destroyed() { this.__removeEvents(); },
methods: {
__setProperty(dom) {
if (!dom) return;
dom.style.setProperty("--borderWidth", this.borderWidth || '1px');
dom.style.setProperty("--borderColor", this.borderColor || '#409EFF');
dom.style.setProperty("--backgroundColor", this.backgroundColor || '#409EFF22');
},
__addEvents() {
this.__removeEvents();
addEventListener('mousedown', this.mousedown);
this.__addEvents_mousemove_mouseup();
},
__addEvents_mousemove_mouseup() {
this.__removeEvents_mousemove_mouseup();
addEventListener('mousemove', this.mousemove);
addEventListener('mouseup', this.mouseup);
},
__removeEvents() {
removeEventListener('mousedown', this.mousedown);
this.__removeEvents_mousemove_mouseup();
},
__removeEvents_mousemove_mouseup() {
removeEventListener('mousemove', this.mousemove);
removeEventListener('mouseup', this.mouseup);
},
mousedown(e) {
if (this.disabled) return;
this.__addEvents_mousemove_mouseup();
this.startPoint = { x: e.clientX, y: e.clientY };
},
mousemove(e) {
if (this.disabled) return;
if (this.startPoint) {
this.endPoint = { x: e.clientX, y: e.clientY };
let width = this.endPoint.x - this.startPoint.x;
let height = this.endPoint.y - this.startPoint.y;
let dragWdithDis = Math.abs(width), dragHeightDis = Math.abs(height);
this.style = {
left: (width > 0 ? this.startPoint.x : this.endPoint.x) + 'px',
top: (height > 0 ? this.startPoint.y : this.endPoint.y) + 'px',
width: dragWdithDis + 'px',
height: dragHeightDis + 'px',
}
let minDragDis = this.minDragDis || 30;//最少拖拽的距离(单位px)缺省值30
if (dragWdithDis > minDragDis || dragHeightDis > minDragDis) {
this.$nextTick(() => {
this.$emit('start', e);
this.__setProperty(this.$el);
// 当新圈选的对象数组和上次圈选的对象数组不同的时候执行
let newSelectedDoms = this.getSelectedDoms();
this.isSameDoms(newSelectedDoms, this.oldSelectedDoms) || this.$emit('select', this.oldSelectedDoms);
});
}
}
},
mouseup(e) {
if (this.disabled) return;
this.__removeEvents_mousemove_mouseup();
this.oldSelectedDoms = [];
this.startPoint = null;
this.endPoint = null;
this.$emit('end', e);
},
/* 此方法目的:
1、解决不能用JSON.stringify(d)来判断引用对象数组的问题;
2、解决每次圈选的元素没有先来后到的顺序关系 */
isSameDoms(newSelectedDoms, oldSelectedDoms) {
let newDoms = newSelectedDoms.map(v => v.dom);
let isSame = true;
// 从老数组里面剔除新数组里面没有的dom
let oldSelectedDoms_temp = [];
oldSelectedDoms.forEach(v => {
if (newDoms.includes(v.dom)) {
oldSelectedDoms_temp.push(v);
} else {
isSame = false;//只要有一个不同,就代表新圈选的内容和老的内容不同
}
});
let oldDoms = oldSelectedDoms_temp.map(v => v.dom);
newSelectedDoms.forEach(v => {
if (oldDoms.includes(v.dom)) {
} else {
oldSelectedDoms_temp.push(v);
isSame = false;//只要有一个不同,就代表新圈选的内容和老的内容不同
}
});
this.oldSelectedDoms = oldSelectedDoms_temp;
return isSame;
},
// 获取被选中的DOM
getSelectedDoms() {
let r = [];
if (this.data && this.data.length && this.data[0].dom) {
r = this.data.filter(v => {
let selected = this.isCrash(v.dom, this.$el);
(v.selectEvent && selected) && v.selectEvent(v);//执行被框选后的方法
(v.unSelectEvent && !selected) && v.unSelectEvent(v);//执行取消框选后的方法
return selected ? true : false;
});
} else {
let doms = (this.data && this.data.length) ? this.data : this.$parent.$el.querySelectorAll(`*`);
r = [].slice.call(doms || []).filter(targetDom => this.isCrash(targetDom, this.$el));
}
// 获取被圈选的内容
return r || [];
}, //碰撞检测
isCrash(targetDom, moveDom) {
/*
targetDom:目标对象(即将要被碰撞的元素)
moveDom:移动的对象(可能是拖拽移动,也可能是其他原因导致其移动)
*/
if (targetDom === moveDom) return false;//如果目标对象和移动对象是同一个,返回未接触
let tr = targetDom.getBoundingClientRect(), mr = moveDom.getBoundingClientRect();
let t = { x1: tr.x, x2: tr.x + tr.width, y1: tr.y, y2: tr.y + tr.height };//目标对象的四角顶点坐标
let m = { x1: mr.x, x2: mr.x + mr.width, y1: mr.y, y2: mr.y + mr.height };//移动对象的四角顶点坐标
let a = { w: Math.min(t.x2, m.x2) - Math.max(t.x1, m.x1), h: Math.min(t.y2, m.y2) - Math.max(t.y1, m.y1) };//计算相交部分的宽度和高度
let area = (a.w > 0 ? a.w : 0) * (a.h > 0 ? a.h : 0);//计算相交部分的面积
return area ? true : false;//面积>0,即碰撞(这里可以根据业务需求,改成相交部分面积>具体的值才作为碰撞判断)
},
},
}
</script>
<style lang="scss" scoped>
.sgRectSelect {
position: fixed;
z-index: 999; //根据情况自己拿捏
box-sizing: border-box;
border: var(--borderWidth) solid var(--borderColor);
background-color: var(--backgroundColor);
/*边框虚线滚动动画特效*/
&[borderAnimate] {
border: none;
background: linear-gradient(90deg, var(--borderColor) 60%, transparent 60%) repeat-x left top/10px var(--borderWidth),
linear-gradient(0deg, var(--borderColor) 60%, transparent 60%) repeat-y right top/var(--borderWidth) 10px,
linear-gradient(90deg, var(--borderColor) 60%, transparent 60%) repeat-x right bottom/10px var(--borderWidth),
linear-gradient(0deg, var(--borderColor) 60%, transparent 60%) repeat-y left bottom/var(--borderWidth) 10px, var(--backgroundColor);
animation: border-animate .382s infinite linear;
}
@keyframes border-animate {
0% {
background-position: left top, right top, right bottom, left bottom;
}
100% {
background-position: left 10px top, right top 10px, right 10px bottom, left bottom 10px;
}
}
}
</style>
应用组件 :
<template>
<div class="sg-body">
<sgRectSelect borderAnimate borderWidth="2px" borderColor="#F56C6C" backgroundColor="#F56C6C22"
style="border-radius: 8px" @select="select" :data="data" />
<el-checkbox-group v-model="checkboxGroupValue">
<el-checkbox border :ref="`checkbox${i}`" v-for="(a, i) in checkboxs" :label="a.value" :key="i">{{ a.label
}}</el-checkbox>
</el-checkbox-group>
</div>
</template>
<script>
import sgRectSelect from "@/vue/components/sgRectSelect";
export default {
components: { sgRectSelect },
data: () => ({
data: [],
checkboxGroupValue: [],
checkboxs: [...Array(50)].map((v, i) => ({ label: '显示文本' + i, value: i }))
}),
mounted() {
/* data是一个数组格式:
[
{
dom:文档对象,//必选
index:'索引',
id:'元素的id',
refName:'ref别名',
selectEvent:'选中后的操作',
unSelectEvent:'取消选中后的操作',
} ,
...
]
*/
this.data = [...Array(50)].map((v, i) => ({
dom: this.$refs[`checkbox${i}`][0].$el,
index: i,
refName: `checkbox${i}`,
/* selectEvent: () => {
this.checkboxGroupValue = this.checkboxGroupValue.concat(i);
this.checkboxGroupValue = [...new Set(this.checkboxGroupValue)];
},
unSelectEvent: () => {
this.checkboxGroupValue = this.checkboxGroupValue.filter(v => v !== i);
}, */
}));
},
methods: {
select(d) {
this.checkboxGroupValue = [];
d.forEach(v => {
this.checkboxGroupValue = this.checkboxGroupValue.concat(v.index);
this.checkboxGroupValue = [...new Set(this.checkboxGroupValue)];
});
console.log(`选中的对象`, d);
},
}
};
</script>
<style lang="scss" scoped>
.sg-body {
position: absolute;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
</style>