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

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

看效果

完美!

相关推荐
燃先生._.2 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js
2401_857600955 小时前
SSM 与 Vue 共筑电脑测评系统:精准洞察电脑世界
前端·javascript·vue.js
2401_857600955 小时前
数字时代的医疗挂号变革:SSM+Vue 系统设计与实现之道
前端·javascript·vue.js
GDAL5 小时前
vue入门教程:组件透传 Attributes
前端·javascript·vue.js
轻口味5 小时前
Vue.js 核心概念:模板、指令、数据绑定
vue.js
2402_857583495 小时前
基于 SSM 框架的 Vue 电脑测评系统:照亮电脑品质之路
前端·javascript·vue.js
java_heartLake6 小时前
Vue3之性能优化
javascript·vue.js·性能优化
ddd君317747 小时前
组件的声明、创建、渲染
vue.js
前端没钱8 小时前
从 Vue 迈向 React:平滑过渡与关键注意点全解析
前端·vue.js·react.js
顽疲8 小时前
springboot vue 会员收银系统 含源码 开发流程
vue.js·spring boot·后端