边框线虚线动画效果请参阅边框虚线滚动动画特效_虚线滚动效果_你挚爱的强哥的博客-CSDN博客【代码】边框虚线滚动动画特效。_虚线滚动效果/qq_37860634/article/details/130507289
碰撞检测原理请前往原生JS完成“一对一、一对多”矩形DIV碰撞检测、碰撞检查,通过计算接触面积(重叠覆盖面积)大小来判断接触对象DOM_js 碰撞检测_你挚爱的强哥的博客-CSDN博客这里就需要去遍历所有的target,计算每个重叠面积大小,挑出面积最大的那一个。stackArea=0代表没有重叠;stackArea >0代表有交集。为了方便计算比较,我们通常是在上面的代码基础上加一个面积大小判断,_js 碰撞检测/qq_37860634/article/details/121688431
还可以用此组件实现类似资源管理器的圈选效果
sgRectSelect框选组件源码
<template><div :class="$options.name" v-if="startPoint && endPoint" :style="style":borderAnimate="borderAnimate === '' || borderAnimate"><slot></slot></div></template><script>import $g from "@/js/sg";export default {name: 'sgRectSelect',data() {return {targets: [],//圈选目标数组startPoint: null,endPoint: null,style: {width: '0px',height: '0px',top: '0px',left: '0px',},oldSelectedDoms: [],//记录上一次圈选内容用于对比 disabled_: false,triggerRectElement_: null,rangeRectElement_: null,minDragDis_: null,}},props: ["data",//(必选)建议用一个复杂对象,方便后续识别操作/* data是一个数组格式:[{dom:文档对象,//必选index:'索引',id:'元素的id',refName:'ref别名',selectEvent:'选中后的操作',unSelectEvent:'取消选中后的操作',} ,...]也可以是一维数组,只包含将可能被选中的DOM数组*/"disabled",//屏蔽"disabledWhenShowSels",//当出现对应['sel','sel','sel',...]的时候,屏蔽拖拽选区(譬如出现element的v-modal)"minDragDis",//最少拖拽的距离(单位px)缺省值20"borderWidth","borderColor","backgroundColor","borderAnimate",//是否显示边框动画"triggerRectElement",//允许触发拖拽圈选的容器元素"rangeRectElement",//拖拽圈选的显示范围容器元素],watch: {data: {handler(newValue, oldValue) {this.targets = newValue || [];},deep: true,//深度监听immediate: true,//立即执行},disabled: {handler(newValue, oldValue) {this.disabled_ = newValue;},deep: true,//深度监听immediate: true,//立即执行},disabled_: {handler(newValue, oldValue) {newValue ? this.__removeWindowEvents() : this.__addWindowEvents();},deep: true,//深度监听immediate: true,//立即执行},minDragDis: {handler(newValue, oldValue) {this.minDragDis_ = newValue;},deep: true,//深度监听immediate: true,//立即执行},triggerRectElement: {handler(newValue, oldValue) {this.triggerRectElement_ = newValue;},deep: true,//深度监听immediate: true,//立即执行},rangeRectElement: {handler(newValue, oldValue) {this.rangeRectElement_ = newValue;},deep: true,//深度监听immediate: true,//立即执行},},destroyed() { this.__removeWindowEvents(); },methods: {__checkDisabled(d) {let aa = this.disabledWhenShowSels || [];aa && (Array.isArray(aa) || (aa = [aa]));let r = [];for (let i = 0, len = aa.length; i < len; i++) {let a = aa[i];let dom = document.querySelector(a);if (dom) {r.push(dom);return true;}}return r.length !== 0;},__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');},__addWindowEvents() {this.__removeWindowEvents();addEventListener('mousedown', this.mousedown);},__addWindowEvents_mousemove_mouseup() {this.__removeWindowEvents_mousemove_mouseup();addEventListener('mousemove', this.mousemove);addEventListener('mouseup', this.mouseup);},__removeWindowEvents() {removeEventListener('mousedown', this.mousedown);this.__removeWindowEvents_mousemove_mouseup();},__removeWindowEvents_mousemove_mouseup() {removeEventListener('mousemove', this.mousemove);removeEventListener('mouseup', this.mouseup);},__mousedown(e) {this.__addWindowEvents_mousemove_mouseup();let { x, y } = e;this.startPoint = { x, y };},mousedown(e) {if (this.disabled) return;if (this.__checkDisabled()) return;if (this.triggerRectElement_&& !$g.isMouseInTarget(this.triggerRectElement_, e, true)&& this.$el&& !$g.isMouseInTarget(this.$el, e, true)) return;this.__mousedown(e);},mousemove(e) {if (this.disabled) return;if (this.__checkDisabled()) return;if (this.triggerRectElement_&& !$g.isMouseInTarget(this.triggerRectElement_, e, true)&& this.$el&& !$g.isMouseInTarget(this.$el, e, true)) 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);let style = {left: (width > 0 ? this.startPoint.x : this.endPoint.x),top: (height > 0 ? this.startPoint.y : this.endPoint.y),width: dragWdithDis,height: dragHeightDis,}// 不允许圈选区域超出指定元素范围if (this.rangeRectElement_) {let { left, top, width, height } = this.rangeRectElement_.getBoundingClientRect();style.left < left && (style.left = left);style.top < top && (style.left = top);style.left + style.width > left + width && (style.width = left + width - style.left);style.top + style.height > top + height && (style.height = top + height - style.top);}Object.keys(style).forEach(k => { style[k] = `${style[k]}px` }), this.style = style;let minDragDis = this.minDragDis_ || 20;//最少拖拽的距离(单位px)缺省值20if (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;if (this.__checkDisabled()) return;this.__removeWindowEvents_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;// 从老数组里面剔除新数组里面没有的domlet 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;},// 获取被选中的DOMgetSelectedDoms() {let r = [];if (this.targets && this.targets.length && this.targets[0].dom) {r = this.targets.filter(v => {let selected = $g.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.targets && this.targets.length) ? this.targets : this.$parent.$el.querySelectorAll(`*`);r = [].slice.call(doms || []).filter(targetDom => $g.isCrash(targetDom, this.$el));}// 获取被圈选的内容return r || [];},},}</script><style lang="scss" scoped>.sgRectSelect {z-index: 999999; //根据情况自己拿捏position: fixed;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>
【sgRectSelect】自定义组件:Vue实现拖拽鼠标圈选 划区域 框选组件:矩形区域选中checkbox 并回调相关选中 取消选中的操作。