【sgRectSelect】Vue实现拖拽鼠标圈选、划区域、框选组件:矩形区域选中checkbox,并回调相关选中、取消选中的操作

news2024/10/6 20:41:16

边框线虚线动画效果请参阅边框虚线滚动动画特效_虚线滚动效果_你挚爱的强哥的博客-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>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/740498.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

嵌入式c语言编码规范

学习嵌入式的同学应该首先掌握嵌入式编码规范&#xff0c;这样才能更好的嵌入式系统。 下面就从这几个方面讲解一下嵌入式c编码规范。 注释风格、排版风格、头文件风格、变量定义、宏定义、函数 1 注释风格 1.1 注释的原则是有助于对程序的阅读和理解&#xff0c;注释不宜太多…

通过(.zip 压缩文件)安装及卸载MySQL

文章目录 一、MySQL安装1.下载MySQL压缩包2.将下载好的压缩包解压到一个没有中文路径的目录下3.配置MySQL环境变量4.验证是否配置成功5.初始化MySQL数据库6.注册MySQL服务7.启动MySQL服务8.修改MySQL数据库默认账户密码9.登录MySQL数据库10.退出MySQL 二、MySQL卸载1.以管理员身…

什么是EDI 180 退货授权和通知?

EDI 180 退货授权和通知是零售商和供应商在退货过程中使用的电子数据交换&#xff08;EDI&#xff09;文件。它既可以作为请求和授权&#xff0c;也可以作为通知文件。 EDI 180 的基本组成部分是什么&#xff1f; EDI 180 交易需要包括有关退货的关键信息。由于EDI 180可以双…

Linux 批量杀掉进程(包含某个关键字)

一、场景说明 现场环境有十多个包含 ”celery” 关键字的进程在运行&#xff0c;每次重启服务&#xff0c;需要将这些进行kill掉&#xff0c;然后重新启动。 可以用如下命令批量kill掉这些进程&#xff1a; kill -9 PID1 PID2 PID3 PID4.....其中&#xff0c;PID是查询到的进…

第九章——内存模型和名称空间

单独编译 C允许程序员将组件函数放在独立的文件中。下面列出了头文件中常包含的内容&#xff1a; 函数原型使用#define或const定义的符号常量结构声明类声明模板声明内联函数 将结构声明放在头文件中是可以的&#xff0c;因为它们不创建变量&#xff0c;而只是在源代码文件…

数据结构--树的存储结构

数据结构–树的存储结构 树的逻辑结构 树是 n ( n ≥ 0 &#xff09; n (n\ge0&#xff09; n(n≥0&#xff09;个结点的有限集合&#xff0c;n 0 时&#xff0c;称为空树&#xff0c;这是一种特殊情况。 在任意一棵非空树中应满足: 1&#xff09;有且仅有一个特定的称为 根 …

如何执行Photoshop脚本

环境 Photoshop: CC2017 OS: Windows 10 脚本放置位置 C:\Program Files\Adobe\Adobe Photoshop CC 2015\Presets\Scripts #也就是 PS的安装目录\Presets\Scripts

操作系统接口 MIT 6.828 - 1. Lab 01: Xv6 and Unix utilities

本文会将lab1中的思路以及知识点进行分析&#xff0c;并作为作者学习MIT 6.828的一个学习总结&#xff0c;希望能够帮助到学习该lab的同学们 中文版书籍&#xff1a;中文版书籍 实验教案地址&#xff1a;教案地址 操作系统接口 在操作系统中&#xff0c;为了能够有效地与操作系…

Amelia、Bookly 和 Booked:哪个WordPress预约插件更好?

各种企业都需要预订软件来管理预约。然而&#xff0c;开发预订系统是网站中最复杂和最昂贵的元素之一。 那些使用 WordPress 构建网站的人有一个优势。只需点击几下&#xff0c;他们就可以将预约插件集成到他们的网站中。 预约插件是一个预订向导&#xff0c;可以自动执行和管…

数据结构——堆的实现(细)

目录 1.1 二叉树的顺序结构 普通的二叉树是不适合用数组来存储的&#xff0c;因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储。现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储&#xff0c;需要注意的是这里的堆和操作系统虚拟进程地址空间中的堆…

Netty核心技术十--Netty 核心源码剖析

1. 基本说明 只有看过Netty源码&#xff0c;才能说是真的掌握了Netty框架。 在 io.netty.example 包下&#xff0c;有很多Netty源码案例&#xff0c;可以用来分析 2. netty 启动过程源码分析 本次分析使用的是example包下的echo 2.1 源码剖析的目的 用源码分析的方式走一下…

Java 动态规划 Leetcode 63. 不同路径 II

该题大部分思路可以根据Leetcode 62. 不同路径这篇博客了解 这里进行基于上面那篇博客后来对该题进行补充 代码展示&#xff1a; class Solution {public int uniquePathsWithObstacles(int[][] obstacleGrid) {int mobstacleGrid.length;int nobstacleGrid[0].length;//创建…

HDFS块详解

HDFS块详解 传统型分布式文件系统的缺点 现在想象一下这种情况&#xff1a;有四个文件 0.5TB的file1&#xff0c;1.2TB的file2&#xff0c;50GB的file3&#xff0c;100GB的file4&#xff1b;有7个服务器&#xff0c;每个服务器上有10个1TB的硬盘。 在存储方式上&#xff0c;我…

Docker安装ElasticSearch8.X docker安装elasticsearch8.X完整详细教程

Docker安装ElasticSearch8.X docker安装elasticsearch8.X完整详细教程 Docker 上安装 ElasticSearch 8.8.1 的步骤&#xff1a;选择要安装的ElasticSearch 版本1、拉取 ElasticSearch 镜像2、创建并运行容器关闭容器启动容器重启容器 3、elasticsearch常用端口以及作用4、测试&…

基于spring cloud alibaba的低代码核心工具,jvs-logic逻辑引擎

在现代企业管理中&#xff0c;决策扮演着至关重要的角色。然而&#xff0c;随着业务规模的扩大和数据量的增加&#xff0c;人工决策变得越来越困难和耗时&#xff0c;而且容易受到主观因素的影响。逻辑引擎的出现为企业提供了一种高效、准确的决策推理工具&#xff0c;能够以逻…

[工业互联-23]:EtherCat从站 - EtherCAT协议栈与工作原理, 软硬件解决方案

目录 第1章 EtherCAT通信原理 1.1 网络架构 1.2 分层模型 2.1 物理层 1.2 数据链路层 1.2.1 EtherCAT数据帧结构 1.2.2 EtherCAT报文寻址 第2章 EtherCAT从站 2.1 概述 2.2 EtherCAT从站的组成包括&#xff1a; 2.3 EtherCAT从站的硬件 2.4 从站控制信息芯片&#…

LeetCode[394]字符串解码

难度&#xff1a;Medium 题目&#xff1a; 给定一个经过编码的字符串&#xff0c;返回它解码后的字符串。 编码规则为: k[encoded_string]&#xff0c;表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。 你可以认为输入字符串总是有效的&#xff1b;…

【LeetCode热题100】打卡第34天:排序链表乘积最大的子数组

文章目录 【LeetCode热题100】打卡第34天&#xff1a;排序链表&乘积最大的子数组⛅前言 排序链表&#x1f512;题目&#x1f511;题解 乘积最大的子数组&#x1f512;题目&#x1f511;题解 【LeetCode热题100】打卡第34天&#xff1a;排序链表&乘积最大的子数组 ⛅前…

如何将文字转化为语音?三个方法帮你轻松实现!

如何将文字转化为语音&#xff1f;在工作或学习中&#xff0c;我们可能会遇到需要将文字转化为语音的情况&#xff0c;这可能会让一些人感到困惑&#xff0c;不知道如何实现这个转换。其实&#xff0c;只需要利用一些第三方工具&#xff0c;就可以轻松地将文字转化为语音。下面…

十四、flex弹性容器属性样式2

目录&#xff1a; 1.准备工作 2.属性解析&#xff1a; align-items 3.属性解析&#xff1a; align-content 4.弹性元素的属性 一、准备工作 我们在前面的基础上&#xff0c;修改代码&#xff0c;把ul的高度定下来&#xff0c;设置800px, li的高度不定。 然后&#xff0c;body里…