vue2 实现鼠标左键拖拽实现框选功能

一、实现如图所示功能

在这里插入图片描述

二、鼠标mousedown事件和click事件重复,不设置click事件可以达到相同效果
// 代码如下
<template>
  <el-dialog
    title="时间段选择"
    :visible.sync="dialogVisible"
    :close-on-click-modal="false"
    custom-class="custom-dialog-style"
    width="1000px"
    :close="close"
  >
    <div class="container">
      <div class="wrap">
        <div class="left">
          <div class="merge-column">星期/时间</div>
          <div class="td-title" v-for="(weekItem, index) in week" :key="index">{{ weekItem }}</div>
        </div>
        <div class="right">
          <div class="top">
            <div class="merge-row">00:00-12:00</div>
            <div class="merge-row">12:00-24:00</div>
            <div class="unit" v-for="(item, index) in 24" :key="index">{{ index }}</div>
          </div>
          <div class="bottom" @mousedown="handleMouseDown">
            <div
              :style="'width:' + mask_width + 'left:' + mask_left + 'height:' + mask_height + 'top:' + mask_top"
              v-show="positionList.is_show_mask"
              class="mask"
            ></div>
            // 鼠标mousedown事件和click事件重复,不设置click事件可以达到相同效果 @click="handleClickSelected(item)"
            <div
              class="select-item"
              v-for="(item, index) in dataItem"
              :key="index"
              :class="{selected: item.selected ? true : false}"
            ></div>
          </div>
        </div>
      </div>
    </div>
    <div slot="footer">
      <el-button @click="close">取 消</el-button>
      <el-button type="primary" @click="submit">确 定</el-button>
    </div>
  </el-dialog>
</template>

<script>
const positionList = {
  is_show_mask: false,
  box_screen_left: 0, // 盒子距离浏览器左侧的距离
  box_screen_top: 0, //盒子距离浏览器顶部的距离
  start_x: 0,
  start_y: 0,
  end_x: 0,
  end_y: 0
};
export default {
  data() {
    return {
      dialogVisible: false,
      week: ["星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"],
      totalNum: 336,
      dataItem: [],
      isClick: true,
      positionList: {...positionList}
    };
  },
  computed: {
    mask_width() {
      return `${Math.abs(this.positionList.end_x - this.positionList.start_x)}px;`;
    },
    mask_height() {
      return `${Math.abs(this.positionList.end_y - this.positionList.start_y)}px;`;
    },
    mask_left() {
      return `${Math.min(this.positionList.start_x, this.positionList.end_x) - this.positionList.box_screen_left}px;`;
    },
    mask_top() {
      return `${Math.min(this.positionList.start_y, this.positionList.end_y) - this.positionList.box_screen_top}px;`;
    }
  },
  methods: {
    init() { // ref 调用init弹窗即可
      this.dialogVisible = true;
      this.initList();
    },
    initList() {
      for (let i = 1; i <= this.totalNum; i++) {
        let map = {
          selected: false,
          key: i
        };
        this.dataItem.push(map);
      }
    },
    setData(value) {
      this.initList();
      value.forEach((v) => {
        this.dataItem[v-1].selected = true;
      });
    },
    handleMouseDown(event) {
      this.positionList.is_show_mask = true;
      this.positionList.start_x = event.clientX;
      this.positionList.start_y = event.clientY;
      this.positionList.end_x = event.clientX;
      this.positionList.end_y = event.clientY;
      this.positionList.box_screen_left = document.querySelector(".bottom").getBoundingClientRect().left;
      this.positionList.box_screen_top = document.querySelector(".bottom").getBoundingClientRect().top;
      document.body.addEventListener("mousemove", this.handleMouseMove); //监听鼠标移动事件
      document.body.addEventListener("mouseup", this.handleMouseUp); //监听鼠标抬起事件
    },
    handleMouseMove(event) {
      this.positionList.end_x = event.clientX;
      this.positionList.end_y = event.clientY;
    },
    handleMouseUp(event) {
      document.body.removeEventListener("mousemove", this.handleMouseMove);
      document.body.removeEventListener("mouseup", this.handleMouseUp);
      this.positionList = {...positionList};
      this.positionList.is_show_mask = false;
      this.handleDomSelect();
      this.resSetXY();
    },
    handleDomSelect() {
      const dom_mask = window.document.querySelector(".mask");
      // getClientRects()方法 每一个盒子的边界矩形的矩形集合
      const rect_select = dom_mask.getClientRects()[0];
      document.querySelectorAll(".select-item").forEach((node, index) => {
        const rects = node.getClientRects()[0];
        if (this.collide(rects, rect_select) === true) {
          this.dataItem[index].selected = !this.dataItem[index].selected;
        }
      });
    },
    collide(rect1, rect2) {
      const maxX = Math.max(rect1.x + rect1.width, rect2.x + rect2.width);
      const maxY = Math.max(rect1.y + rect1.height, rect2.y + rect2.height);
      const minX = Math.min(rect1.x, rect2.x);
      const minY = Math.min(rect1.y, rect2.y);
      return maxX - minX <= rect1.width + rect2.width && maxY - minY <= rect1.height + rect2.height;
    },
    resSetXY() {
      this.positionList.start_x = 0;
      this.positionList.start_y = 0;
      this.positionList.end_x = 0;
      this.positionList.end_y = 0;
    },
    handleClickSelected(item) {
      item.selected = !item.selected;
    },
    close() {
      this.dialogVisible = false;
      this.dataItem = [];
    },
    submit() {
      const list = [];
      for (let i = 0; i < this.dataItem.length; i++) {
        if (this.dataItem[i].selected == true) {
          list.push(this.dataItem[i].key);
        }
      }
      if (list.length == 0) {
        this.$message({
          message: "请选择时间段",
          type: "warning"
        });
        return;
      } else {
        this.close();
        this.$emit("event-data", list);
      }
    }
  }
};
</script>

<style lang="scss" scoped>
.container {
  .wrap {
    display: flex;
    border: 1px solid #f1f1f1;
    border-bottom: 0;
    .left {
      .merge-column {
        width: 4vw;
        height: 6vh;
        padding: 3px 5px;
        min-width: 76px;
        min-height: 55px;
        display: flex;
        align-items: center;
        border-right: 1px solid #f1f1f1;
        border-bottom: 1px solid #f1f1f1;
      }
      .td-title {
        width: 4vw;
        height: 3vh;
        min-height: 27px;
        min-width: 76px;
        padding: 3px 5px;
        text-align: center;
        border-right: 1px solid #f1f1f1;
        border-bottom: 1px solid #f1f1f1;
      }
      &:last-child {
        border-bottom: 0;
      }
    }
    .right {
      display: flex;
      flex-wrap: wrap;
      .top {
        width: 100%;
        display: flex;
        flex-wrap: wrap;
        align-content: flex-start;
        .merge-row {
          width: 50%;
          height: 3vh;
          padding: 3px 5px;
          min-width: 420px;
          min-height: 27px;
          text-align: center;
          border-right: 1px solid #f1f1f1;
          border-bottom: 1px solid #f1f1f1;
          &:nth-child(2) {
            border-right: 0;
          }
        }
        .unit {
          width: calc(100% / 24);
          height: 3vh;
          padding: 3px 5px;
          min-width: 35px;
          min-height: 27px;
          text-align: center;
          border-right: 1px solid #f1f1f1;
          border-bottom: 1px solid #f1f1f1;
          &:last-child {
            border-right: 0;
          }
        }
      }
      .bottom {
        width: 100%;
        position: relative;
        display: flex;
        flex-wrap: wrap;
        .mask {
          position: absolute;
          background: #409eff;
          opacity: 0.4;
          z-index: 100;
          left: 0;
          top: 0;
        }
        .select-item {
          width: calc(100% / 48);
          height: 3vh;
          min-width: 17px;
          min-height: 27px;
          cursor: pointer;
          padding: 3px 5px;
          border-right: 1px solid #f1f1f1;
          border-bottom: 1px solid #f1f1f1;
          &:hover {
            background: #f1f1f1;
          }
        }
        .selected {
          background: #7bc8ff;
          &:hover {
            background: #7bc8ff;
          }
        }
        *:nth-child(48n + 1) {
          border-right: 0;
        }
      }
    }
  }
}
</style>
参考文章:https://blog.csdn.net/qq_37656005/article/details/123656240
  • 6
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
以下是一个简单的Vue组件,可以实现鼠标滚轮缩放元素和空格加鼠标左键长按拖动元素的功能。 ```vue <template> <div ref="container" @wheel="onWheel" @mousedown="onMouseDown" @mousemove="onMouseMove" @mouseup="onMouseUp" @mouseleave="onMouseLeave" style="position: relative; width: 500px; height: 500px; overflow: hidden;"> <div ref="content" :style="{transform: `scale(${scale})`, transformOrigin: 'top left', position: 'absolute', left: offsetX + 'px', top: offsetY + 'px'}"> <slot></slot> </div> </div> </template> <script> export default { data() { return { scale: 1, offsetX: 0, offsetY: 0, isDragging: false, startX: 0, startY: 0, lastX: 0, lastY: 0, }; }, methods: { onWheel(event) { const { deltaX, deltaY } = event; const scaleDelta = 0.05; const newScale = deltaY > 0 ? this.scale - scaleDelta : this.scale + scaleDelta; this.scale = Math.max(newScale, 0.1); // 限制最小缩放比例为0.1 }, onMouseDown(event) { if (event.button === 0 && event.shiftKey) { event.preventDefault(); this.isDragging = true; this.startX = this.lastX = event.pageX; this.startY = this.lastY = event.pageY; } }, onMouseMove(event) { if (this.isDragging) { const deltaX = event.pageX - this.lastX; const deltaY = event.pageY - this.lastY; this.offsetX += deltaX; this.offsetY += deltaY; this.lastX = event.pageX; this.lastY = event.pageY; } }, onMouseUp(event) { if (this.isDragging && event.button === 0) { event.preventDefault(); this.isDragging = false; } }, onMouseLeave() { if (this.isDragging) { this.isDragging = false; } }, }, }; </script> ``` 在该组件中,我们使用一个`<div>`元素作为容器,它的`ref`属性为`container`。另外,我们还使用一个`<div>`元素作为内容,它的`ref`属性为`content`,并在其中使用了`<slot>`元素,以便使用该组件时可以传入任意内容。 在组件的`data`中,我们定义了一些状态变量,包括缩放比例`scale`、水平和垂直偏移量`offsetX`和`offsetY`,以及拖动状态`isDragging`、开始拖动时鼠标的位置`startX`和`startY`,以及上一次鼠标移动时的位置`lastX`和`lastY`。 在组件的`methods`中,我们实现了`onWheel`、`onMouseDown`、`onMouseMove`、`onMouseUp`和`onMouseLeave`五个方法,分别对应鼠

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值