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

最近看到了一个比较有意思的功能,就是鼠标画一个区域,在这个区域内的元素都被选中。类似于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
    }
  })
// --------------------------------------------------------------------新增
}

看效果

完美!

相关推荐
化作繁星23 分钟前
在 Vue 3 中,如何缓存和复用动态组件
前端·vue.js·缓存
停止重构29 分钟前
【开源】低代码 C++程序框架,Linux多线程程序
c++·低代码·多线程·开源框架·云计算引擎
jonyleek32 分钟前
「JVS更新日志」低代码、企业会议、智能BI、智能排产2.26更新说明
java·大数据·低代码·数据分析·软件需求
CaptainDrake1 小时前
React低代码项目:问卷编辑器
react.js·低代码·编辑器
一只小姜丝3321 小时前
解决各大浏览器中http地址无权限调用麦克风摄像头问题
网络·vue.js·网络协议·http
林的快手4 小时前
CSS默认样式
前端·css·vue.js·chrome·css3
林涧泣8 小时前
【Uniapp-Vue3】开发userStore用户所需的相关操作
前端·vue.js·uni-app
皓月当空hy10 小时前
vue3中测试:单元测试、组件测试、端到端测试
vue.js
码上飞扬10 小时前
Vue.js组件开发:从基础到进阶
vue.js
昨日余光11 小时前
仅需三分钟,使用Vue3.x版本组件式风格实现一个消息提示组件!
前端·javascript·css·vue.js·typescript·html