低代码平台非常有用的功能-画框选中

最近看到了一个比较有意思的功能,就是鼠标画一个区域,在这个区域内的元素都被选中。类似于windows桌面的鼠标滑动选中了图标一样。就觉得这个功能用在低代码平台上很有用,比如可以批量删除,批量移动位置等。
先看个效果图:

这个功能可以分两步走:

  1. 画出鼠标滑动的区域
  2. 计算是否被框选

绘制鼠标滑动区域

鼠标滑动区域绘制需要监听mousedown、mousemove、mouseup这三个方法。

首先准备一个容器和绘制区域的容器。给container加上三个监听事件,给area加上实时绘制的样式。

html 复制代码
<template>
  <div class="container"@mousedown="mousedown" @mousemove="mousemove" @mouseup="mouseup">
    <div class="area" :style="areaStyle"></div>
  </div>
</template>

当完成这一套划区的操作首先是在mousedown监听事件中记录鼠标点击的位置,在mousemove监听函数中实时计算滑动区域的宽高,在mouseup中取消划区的操作即可。

首先创建一个对象保存滑动区域的信息

javascript 复制代码
// 鼠标滑动区域信息
const areaInfo = ref({
  startX: 0, // 开始滑动的x坐标
  startY: 0, // 开始滑动的y坐标
  width: 0, // 宽度
  height: 0 // 高度
})

接着获取开始位置和计算宽高

javascript 复制代码
const isStart = ref(false) // 标识开始划区
const mousedown = (e) => {
  isStart.value = true

  // 获取开始点的位置信息
  areaInfo.value.startX = e.clientX
  areaInfo.value.startY = e.clientY
}

const mousemove = (e) => {
  if (!isStart.value) { return }

  const { startX, startY } = areaInfo.value
  const { clientX, clientY } = e

  // 计算出滑动区域的宽高
  areaInfo.value.width = clientX - startX
  areaInfo.value.height = clientY - startY
}

const mouseup = () => {
  areaInfo.value = {} // 停止划区
  isStart.value = false
}

这样就拿到了划区的关键信息,接着添加样式看下效果

js 复制代码
// 框选区域的样式
const areaStyle = computed(() => {
  return areaInfo.value.width && { // 这里需要判断当前信息是否存在,不存在就不显示划区
    width: areaInfo.value.width + 'px',
    height: areaInfo.value.height + 'px',
    left: areaInfo.value.startX + 'px',
    top: areaInfo.value.startY + 'px',
  }
})

加上css

css 复制代码
<style scoped lang="less">
.container{
  user-select: none;
  position: relative;
  height: 100vh;
  .area{
    position: fixed;
    background-color: rgba(0, 140, 255, .3);
  }
}
</style>

看结果

好像完成了,但是仔细看并不是那么回事。当鼠标从左上方向右下方滑动可以触发划区,其他任何方向滑动都不行。

mousemove方法中打印当前划区信息,就能发现宽高可能会是负数。造成这一结果的原因就是在计算宽高的时候没有规划到负值,但负值也是宽高。那么加上Math.abs()方法

js 复制代码
// 计算出滑动区域的宽高
areaInfo.value.width = Math.abs(clientX - startX)
areaInfo.value.height = Math.abs(clientY - startY)

看效果

东西是出来了,但是位置不对,仔细观察之后,现在的划区生成方式都是从左上到右下。

造成这种问题的原因是,当从右下角往左上角滑动的时候,当前生成的划区的开始坐标和结束坐标还是在一开始鼠标按下的那一个位置。此时应当改变开始和结束的坐标。 在mousemove中加一个判断

js 复制代码
const mousemove = (e) => {
  if (!isStart.value) { return }
  
  const { startX, startY } = areaInfo.value
  const { clientX, clientY } = e

  // 计算出滑动区域的宽高
  areaInfo.value.width = Math.abs(clientX - startX)
  areaInfo.value.height = Math.abs(clientY - startY)

// ----------------------------------------------------------新增
  if (clientX < startX) {
    areaInfo.value.left = clientX
  }
  if (clientY < startY) {
    areaInfo.value.top = clientY
  }
// ----------------------------------------------------------新增
}

但这样会引发另一个问题,此时宽高和开始坐标都在实时变化,那么宽高计算出来的一定会是0。要避免这个问题那么开始的坐标是不能直接改的,需要引入新的变量。

js 复制代码
// 鼠标滑动区域信息
const areaInfo = ref({
  startX: 0,
  startY: 0,
  width: 0,
  height: 0,
  // ----------新增
  left: 0,
  top: 0
  // ----------新增
})

// 框选区域的样式
const areaStyle = computed(() => {
  return areaInfo.value.width && {
    width: areaInfo.value.width + 'px',
    height: areaInfo.value.height + 'px',
    // ----------------------------------------------替换
    // left: areaInfo.value.startX + 'px',
    // top: areaInfo.value.startY + 'px',
    left: areaInfo.value.left + 'px',
    top: areaInfo.value.top + 'px',
    // ----------------------------------------------替换
  }
})

// left和top同样也需要在mousedown中赋值, 当从左上到右下滑动时left、top不变
const mousedown = (e) => {
  isStart.value = true
  // 获取开始点的位置信息
  // ------------------------------------------------------替换
  // areaInfo.value.startX = e.clientX
  // areaInfo.value.startY = e.clientY
  areaInfo.value.startX = areaInfo.value.left = e.clientX
  areaInfo.value.startY = areaInfo.value.top = e.clientY
  // ------------------------------------------------------替换
}

看效果

完美实现

添加框选

首先生成十个随机位置和大小的元素

js 复制代码
const componentsList = ref([
  { id: 1, selected: false, style: { left: 112, top: 59, width: 78, height: 100, borderRadius: 10 }},
  { id: 2, selected: false, style: { left: 200, top: 100, width: 23, height: 55 }},
  { id: 3, selected: false, style: { left: 435, top: 223, width: 88, height: 50, borderRadius: 30 }},
  { id: 4, selected: false, style: { left: 225, top: 500, width: 180, height: 180 }},
  { id: 5, selected: false, style: { left: 376, top: 50, width: 20, height: 190, borderRadius: 30 }},
  { id: 6, selected: false, style: { left: 200, top: 250, width: 100, height: 100 }},
  { id: 7, selected: false, style: { left: 250, top: 150, width: 100, height: 100, borderRadius: '50%' }},
  { id: 8, selected: false, style: { left: 550, top: 200, width: 100, height: 100, borderRadius: 30 }},
  { id: 9, selected: false, style: { left: 800, top: 460, width: 100, height: 100 }},
  { id: 10, selected: false, style: { left: 900, top: 100, width: 100, height: 100, borderRadius: 30 }},
])
html 复制代码
<template>
  <div class="container" @mousedown="mousedown" @mousemove="mousemove" @mouseup="mouseup">
    <div class="area" :style="areaStyle"></div>
    <!-- ---------------------------新增------------------------------- -->
    <div
      class="component"
      v-for="item in componentsList"
      :key="item"
      :style="{
        left: item.style?.left + 'px',
        top: item.style?.top + 'px',
        width: item.style?.width + 'px',
        height: item.style?.height + 'px',
        borderRadius: typeof item.style?.borderRadius == 'string' ? item.style?.borderRadius : item.style?.borderRadius + 'px'
      }"
      :class="[item.selected && 'selected']">
      {{ item.id }}
    </div>
    <!-- ---------------------------新增------------------------------- -->
  </div>
</template>
css 复制代码
.component{
  background-color: rgb(55, 165, 255);
  text-align: center;
  color: #fff;
  position: absolute;
}
.selected{
  background-color: green;
  opacity: 0.5;
}

这样就生成了十个元素。由于是用在低代码平台中,元素都有自己的位置和大小等信息,就不主动获取页面目标元素的大小位置。

要判断元素被完全框选住了,就是判断两个点。划区的左上角的点与目标元素左上角对比,划区的右下角与目标元素的右下角对比。划区元素左上角坐标要比目标元素小,划区元素右下角坐标要比目标元素大。

mousemove中添加判断

js 复制代码
const mousemove = (e) => {
  if (!isStart.value) { return }

  const { startX, startY } = areaInfo.value
  const { clientX, clientY } = e

  // 计算出滑动区域的宽高
  areaInfo.value.width = Math.abs(clientX - startX)
  areaInfo.value.height = Math.abs(clientY - startY)

  if (clientX < startX) {
    areaInfo.value.left = clientX
  }
  if (clientY < startY) {
    areaInfo.value.top = clientY
  }
// --------------------------------------------------------------------新增
  componentsList.value.forEach(item => {
    const { left, top, width, height } = item.style
    const { left: aLeft, top: aTop, width: aWidth, height: aHeight } = areaInfo.value
    if (
      aLeft < left && aTop < top && // 左上角
      aLeft + aWidth > left + width && aTop + aHeight > top + height // 右下角
    ) {
      item.selected = true
    } else {
      item.selected = false
    }
  })
// --------------------------------------------------------------------新增
}

看效果

完美!

相关推荐
青稞儿26 分钟前
面试题高频之token无感刷新(vue3+node.js)
vue.js·node.js
程序员凡尘1 小时前
完美解决 Array 方法 (map/filter/reduce) 不按预期工作 的正确解决方法,亲测有效!!!
前端·javascript·vue.js
Bug缔造者7 小时前
Element-ui el-table 全局表格排序
前端·javascript·vue.js
xnian_8 小时前
解决ruoyi-vue-pro-master框架引入报错,启动报错问题
前端·javascript·vue.js
罗政8 小时前
[附源码]超简洁个人博客网站搭建+SpringBoot+Vue前后端分离
vue.js·spring boot·后端
阿树梢9 小时前
【Vue】VueRouter路由
前端·javascript·vue.js
随笔写10 小时前
vue使用关于speak-tss插件的详细介绍
前端·javascript·vue.js
陈小唬12 小时前
html页面整合vue2或vue3
前端·vue.js·html
花下的晚风12 小时前
Vue实用操作篇-1-第一个 Vue 程序
前端·javascript·vue.js
有颜有货12 小时前
低代码开发平台系统架构概述
低代码·系统架构