vue单选框实现思路(原生JSVue实现框选功能)
vue单选框实现思路(原生JSVue实现框选功能)根据框选矩形大小、位置,计算在范围内的 checkbox 数量,进行对应处理鼠标松开 mouseup,移除 mousemove、mouseup 事件<div class="box" > <div class="mask" v-show="is_show_mask" :style="'width:' mask_width 'left:' mask_left 'height:' mask_height 'top:' mask_top"> </div> <el-checkbox-group> <el-checkbox> </el-checkbox> </el-c
作者:小豪
转发链接:https://segmentfault.com/a/1190000023072352
效果分析该过程,可拆分成两个步骤:
- 鼠标框选一段区域
- 判断框选区域包含的 checkbox,进行对应处理
该布局基于 element-ui,一个父容器 box,里面一个 mask div,一个 el-checkbox-group 块。其中父容器设置 position: relative; 子 mask 容器设置 position: absolute;并且其宽、高、偏移值根据鼠标当前位置动态计算
<div class="box" >
<div class="mask" v-show="is_show_mask" :style="'width:' mask_width 'left:' mask_left 'height:' mask_height 'top:' mask_top">
</div>
<el-checkbox-group>
<el-checkbox>
</el-checkbox>
</el-checkbox-group>
</div>
.box {
width: 800px;
margin: 20px auto;
position: relative;
overflow: hidden;
user-select: none;
.mask {
position: absolute;
background: #409eff;
opacity: 0.4;
}
}
JS 实现框选
该部分逻辑实际上可拆分为 4 个步骤:
鼠标按下 mousedown,记录当前起点坐标 start_x,start_y,并绑定 mousemove、mouseup 事件
鼠标移动 mousemove,实时更新终点坐标 end_x,end_y,即可框选矩形大小、位置
鼠标松开 mouseup,移除 mousemove、mouseup 事件
根据框选矩形大小、位置,计算在范围内的 checkbox 数量,进行对应处理
1.给 box 绑定 mousedown 事件
<div class="box" @mousedown="handleMouseDown"></div>
handleMouseDown(event: any) {
this.start_x = event.clientX;
this.start_y = event.clientY;
document.body.addEventListener('mousemove' this.handleMouseMove);
document.body.addEventListener('mouseup' this.handleMouseUp);
}
2.mousemove 事件,比较简单,只是更新 end_x,end_y 坐标
handleMouseMove(event: MouseEvent) {
this.end_x = event.clientX;
this.end_y = event.clientY;
}
3.mouseup 事件,移除 mousemove、mouseup 事件,并调用判断方法
handleMouseUp() {
document.body.removeEventListener('mousemove' this.handleMouseMove);
document.body.removeEventListener('mouseup' this.handleMouseUp);
this.handleDomSelect();
this.resSetXY();
}
4.处理框选逻辑
难点是如何判断元素是否被框选住
问题可转化为 框选矩形是否与 checkbox 矩形 相交或者包含在内 即两矩形是否相交或者存在包含关系
假定矩形 A1 左上角坐标为 (x1 y1);矩形宽度为 width1 高度为 height1;
假定矩形 A2 左上角坐标为 (x2 y2);矩形宽度为 width2 高度为 height2;
画图分析,只看水平方向:
由图可以得出,x 方向上:
令 maxX = Math.max(x1 width1 x2 width2)
令 minX = Math.max(x1 x2)
若相交或包含则必满足:maxX - minX <= width1 width2;
同理可以容易得到 y 轴相交的判断
使用 Element.getBoundingClientRect()获取 dom 元素位置信息
Element.getBoundingClientRect() 方法返回元素的大小及其相对于视口的位置。返回值是一个 DOMRect 对象,返回的结果是包含完整元素的最小矩形,并且拥有 left top right bottom x y width 和 height 这几个以像素为单位的只读属性用于描述整个边框。
该部分逻辑如下,比较简单
collide(rect1: any rect2: any): boolean {
const maxX: number = Math.max(rect1.x rect1.width rect2.x rect2.width);
const maxY: number = Math.max(rect1.y rect1.height rect2.y rect2.height);
const minX: number = Math.min(rect1.x rect2.x);
const minY: number = Math.min(rect1.y rect2.y);
if (maxX - minX <= rect1.width rect2.width && maxY - minY <= rect1.height rect2.height) {
return true;
} else {
return false;
}
}
难点已经攻破,遍历 checkbox 集合,每个 checkbox 都执行上面的矩形相交判断,并进行相应的勾选处理,此处不再多累述
完整代码<template>
<div class="box" @mousedown="handleMouseDown">
<div class="mask" v-show="is_show_mask" :style="'width:' mask_width 'left:' mask_left 'height:' mask_height 'top:' mask_top">
</div>
<el-checkbox-group v-model="select_list">
<el-checkbox v-for="(item index) in data_list" :label="item.city_id" :key="index">
<p @click.stop.prevent> {{item.city_name}}</p>
</el-checkbox>
</el-checkbox-group>
</div>
</template>
<script lang="ts">
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Vue Component } from 'vue-property-decorator';
interface City {
city_id: number;
city_name: string;
}
@Component({})
export default class Debug extends Vue {
data_list: Array<City> = [
{ city_id: 35 city_name: '香港特別行政區' }
{ city_id: 34 city_name: '北京市' }
{ city_id: 33 city_name: '江苏省' }
{ city_id: 32 city_name: '吉林省' }
{ city_id: 31 city_name: '内蒙古自治区' }
];
select_list: Array<number> = [];
is_show_mask = false;
box_screen_left = 0;
box_screen_top = 0;
start_x = 0;
start_y = 0;
end_x = 0;
end_y = 0;
get mask_width() {
return `${Math.abs(this.end_x - this.start_x)}px;`;
}
get mask_height() {
return `${Math.abs(this.end_y - this.start_y)}px;`;
}
get mask_left() {
return `${Math.min(this.start_x this.end_x) - this.box_screen_left}px;`;
}
get mask_top() {
return `${Math.min(this.start_y this.end_y) - this.box_screen_top}px;`;
}
mounted() {
const dom_box: any = document.querySelector('.box');
this.box_screen_left = dom_box.getBoundingClientRect().left;
this.box_screen_top = dom_box.getBoundingClientRect().top;
}
/* 方法 */
handleMouseDown(event: any) {
if (event.target.tagName === 'SPAN') return false;
this.is_show_mask = true;
this.start_x = event.clientX;
this.start_y = event.clientY;
this.end_x = event.clientX;
this.end_y = event.clientY;
document.body.addEventListener('mousemove' this.handleMouseMove);
document.body.addEventListener('mouseup' this.handleMouseUp);
}
handleMouseMove(event: MouseEvent) {
this.end_x = event.clientX;
this.end_y = event.clientY;
}
handleMouseUp() {
document.body.removeEventListener('mousemove' this.handleMouseMove);
document.body.removeEventListener('mouseup' this.handleMouseUp);
this.is_show_mask = false;
this.handleDomSelect();
this.resSetXY();
}
handleDomSelect() {
const dom_mask: any = window.document.querySelector('.mask');
const rect_select = dom_mask.getClientRects()[0];
const add_list: Array<number> = [];
const del_list: Array<number> = [];
document.querySelectorAll('.el-checkbox-group .el-checkbox').forEach((node index) => {
const rects = node.getClientRects()[0];
if (this.collide(rects rect_select) === true) {
if (this.select_list.includes(this.data_list[index].city_id)) {
del_list.push(this.data_list[index].city_id);
} else {
add_list.push(this.data_list[index].city_id);
}
}
});
this.select_list = this.select_list.concat(add_list).filter((item: number) => !del_list.includes(item));
}
// eslint-disable-next-line class-methods-use-this
collide(rect1: any rect2: any): boolean {
const maxX: number = Math.max(rect1.x rect1.width rect2.x rect2.width);
const maxY: number = Math.max(rect1.y rect1.height rect2.y rect2.height);
const minX: number = Math.min(rect1.x rect2.x);
const minY: number = Math.min(rect1.y rect2.y);
if (maxX - minX <= rect1.width rect2.width && maxY - minY <= rect1.height rect2.height) {
return true;
} else {
return false;
}
}
resSetXY() {
this.start_x = 0;
this.start_y = 0;
this.end_x = 0;
this.end_y = 0;
}
}
</script>
<style lang="scss" scoped>
.box {
width: 800px;
margin: 20px auto;
position: relative;
overflow: hidden;
user-select: none;
.mask {
position: absolute;
background: #409eff;
opacity: 0.4;
}
.el-checkbox-group {
overflow: auto;
.el-checkbox {
width: 137px;
margin: 0 20px 10px 0;
float: left;
::v-deep .el-checkbox__input {
padding-top: 3px;
vertical-align: top;
}
p {
width: 110px;
white-space: pre-wrap;
}
}
}
}
</style>
END
推荐Vue学习资料文章:《Vue.js轮播库热门精选》
《一文带你搞懂vue/react应用中实现ssr(服务端渲染)》
《Vue CSS3 实现图片滑块效果》
《教你Vue3 Compiler 优化细节,如何手写高性能渲染函数(上)》
《教你Vue3 Compiler 优化细节,如何手写高性能渲染函数(下)》
《vue实现一个6个输入框的验证码输入组件》
《一用惊人的Vue实践技巧「值得推荐」》
《Vue常见的面试知识点汇总(上)「附答案」》
《Vue常见的面试知识点汇总(下)「附答案」》
《Kbone原理详解与小程序技术选型》
《为什么我不再用Vue,改用React?》
《让Jenkins自动部署你的Vue项目「实践」》
《20个免费的设计资源 UI套件背景图标CSS框架》
《Deno将停止使用TypeScript,并公布五项具体理由》
《前端骨架屏都是如何生成的》
《Vue原来可以这样写开发效率杠杠的》
《用vue简单写一个音乐播放组件「附源码」》
《为什么Vue3.0不再使用defineProperty实现数据监听?》
《「干货」学会这些Vue小技巧,可以早点下班和女神约会》
《探索 Vue-Multiselect》
《细品30张脑图带你从零开始学Vue》
《Vue后台项目中遇到的技术难点以及解决方案》
《手把手教你Electron Vue实战教程(五)》
《手把手教你Electron Vue实战教程(四)》
《手把手教你Electron Vue实战教程(三)》
《手把手教你Electron Vue实战教程(二)》
《手把手教你Electron Vue实战教程(一)》
《收集22种开源Vue模板和主题框架「干货」》
《如何写出优秀后台管理系统?11个经典模版拿去不谢「干货」》
《手把手教你实现一个Vue自定义指令懒加载》
《基于 Vue 和高德地图实现地图组件「实践」》
《一个由 Vue 作者尤雨溪开发的 web 开发工具—vite》
《是什么让我爱上了Vue.js》
《1.1万字深入细品Vue3.0源码响应式系统笔记「上」》
《1.1万字深入细品Vue3.0源码响应式系统笔记「下」》
《「实践」Vue 数据更新7 种情况汇总及延伸解决总结》
《尤大大细说Vue3 的诞生之路「译」》
《提高10倍打包速度工具Snowpack 2.0正式发布,再也不需要打包器》
《大厂Code Review总结Vue开发规范经验「值得学习」》
《Vue3 插件开发详解尝鲜版「值得收藏」》
《带你五步学会Vue SSR》
《记一次Vue3.0技术干货分享会》
《Vue 3.x 如何有惊无险地快速入门「进阶篇」》
《「干货」微信支付前后端流程整理(Vue Node)》
《带你了解 vue-next(Vue 3.0)之 炉火纯青「实践」》
《「干货」Vue 高德地图实现页面点击绘制多边形及多边形切割拆分》
《「干货」Vue Element前端导入导出Excel》
《「实践」Deno bytes 模块全解析》
《细品pdf.js实践解决含水印、电子签章问题「Vue篇」》
《基于vue element的后台管理系统解决方案》
《Vue仿蘑菇街商城项目(vue koa mongodb)》
《基于 electron-vue 开发的音乐播放器「实践」》
《「实践」Vue项目中标配编辑器插件Vue-Quill-Editor》
《基于 Vue 技术栈的微前端方案实践》
《消息队列助你成为高薪 Node.js 工程师》
《Node.js 中的 stream 模块详解》
《「干货」Deno TCP Echo Server 是怎么运行的?》
《「干货」了不起的 Deno 实战教程》
《「干货」通俗易懂的Deno 入门教程》
《Deno 正式发布,彻底弄明白和 node 的区别》
《「实践」基于Apify node react/vue搭建一个有点意思的爬虫平台》
《「实践」深入对比 Vue 3.0 Composition API 和 React Hooks》
《前端网红框架的插件机制全梳理(axios、koa、redux、vuex)》
《深入Vue 必学高阶组件 HOC「进阶篇」》
《深入学习Vue的data、computed、watch来实现最精简响应式系统》
《10个实例小练习,快速入门熟练 Vue3 核心新特性(一)》
《10个实例小练习,快速入门熟练 Vue3 核心新特性(二)》
《教你部署搭建一个Vue-cli4 Webpack移动端框架「实践」》
《2020前端就业Vue框架篇「实践」》
《详解Vue3中 router 带来了哪些变化?》
《Vue项目部署及性能优化指导篇「实践」》
《Vue高性能渲染大数据Tree组件「实践」》
《尤大大细品VuePress搭建技术网站与个人博客「实践」》
《10个Vue开发技巧「实践」》
《是什么导致尤大大选择放弃Webpack?【vite 原理解析】》
《带你了解 vue-next(Vue 3.0)之 小试牛刀【实践】》
《带你了解 vue-next(Vue 3.0)之 初入茅庐【实践】》
《实践Vue 3.0做JSX(TSX)风格的组件开发》
《一篇文章教你并列比较React.js和Vue.js的语法【实践】》
《手拉手带你开启Vue3世界的鬼斧神工【实践】》
《深入浅出通过vue-cli3构建一个SSR应用程序【实践】》
《怎样为你的 Vue.js 单页应用提速》
《聊聊昨晚尤雨溪现场针对Vue3.0 Beta版本新特性知识点汇总》
《【新消息】Vue 3.0 Beta 版本发布,你还学的动么?》
《Vue真是太好了 壹万多字的Vue知识点 超详细!》
《Vue Koa从零打造一个H5页面可视化编辑器——Quark-h5》
《深入浅出Vue3 跟着尤雨溪学 TypeScript 之 Ref 【实践】》
《手把手教你深入浅出vue-cli3升级vue-cli4的方法》
《Vue 3.0 Beta 和React 开发者分别杠上了》
《手把手教你用vue drag chart 实现一个可以拖动 / 缩放的图表组件》
《Vue3 尝鲜》
《总结Vue组件的通信》
《Vue 开源项目 TOP45》
《2020 年,Vue 受欢迎程度是否会超过 React?》
《尤雨溪:Vue 3.0的设计原则》
《使用vue实现HTML页面生成图片》
《实现全栈收银系统(Node Vue)(上)》
《实现全栈收银系统(Node Vue)(下)》
《vue引入原生高德地图》
《Vue合理配置WebSocket并实现群聊》
《多年vue项目实战经验汇总》
《vue之将echart封装为组件》
《基于 Vue 的两层吸顶踩坑总结》
《Vue插件总结【前端开发必备】》
《Vue 开发必须知道的 36 个技巧【近1W字】》
《构建大型 Vue.js 项目的10条建议》
《深入理解vue中的slot与slot-scope》
《手把手教你Vue解析pdf(base64)转图片【实践】》
《使用vue node搭建前端异常监控系统》
《推荐 8 个漂亮的 vue.js 进度条组件》
《基于Vue实现拖拽升级(九宫格拖拽)》
《手摸手,带你用vue撸后台 系列二(登录权限篇)》
《手摸手,带你用vue撸后台 系列三(实战篇)》
《前端框架用vue还是react?清晰对比两者差异》
《Vue组件间通信几种方式,你用哪种?【实践】》
《浅析 React / Vue 跨端渲染原理与实现》
《10个Vue开发技巧助力成为更好的工程师》
《手把手教你Vue之父子组件间通信实践讲解【props、$ref 、$emit】》
《1W字长文 多图,带你了解vue的双向数据绑定源码实现》
《深入浅出Vue3 的响应式和以前的区别到底在哪里?【实践】》
《干货满满!如何优雅简洁地实现时钟翻牌器(支持JS/Vue/React)》
《基于Vue/VueRouter/Vuex/Axios登录路由和接口级拦截原理与实现》
《手把手教你D3.js 实现数据可视化极速上手到Vue应用》
《吃透 Vue 项目开发实践|16个方面深入前端工程化开发技巧【上】》
《吃透 Vue 项目开发实践|16个方面深入前端工程化开发技巧【中】》
《吃透 Vue 项目开发实践|16个方面深入前端工程化开发技巧【下】》
《Vue3.0权限管理实现流程【实践】》
《后台管理系统,前端Vue根据角色动态设置菜单栏和路由》
作者:小豪
转发链接:https://segmentfault.com/a/1190000023072352