mousedown拖拽功能(vue3+ts)

因为项目有rem适配,使用第三方插件无法处理适配问题,所有只能自己写拖拽功能了
拖拽一般都会想到按下,移动,放开,但是本人亲测,就在div绑定一个按下事件就行了(在事件里面写另外两个事件),另外两个绑上,会莫名其妙卡死,那种莫名其妙的问题

推荐几个开发调试时使用的第三方拖动插件吧,虽然没用上,但是他们是真的好vue-drag-resizevuedraggable,其中前者更轻量化,后者功能更全

主要功能:

效果图
mousedown拖拽功能(vue3+ts)_第1张图片

界面:(就是大的父盒子包着几个小盒子,盒子里面有图片和文字)

        <div class="range" id="range" ref="range">
          <div
            class="iconItem"
            v-for="(item, index) in pointList"
            :key="index"
            @mousedown.stop.prevent.native="mousedown($event, item)"
            :style="{
              left: item.dx + 'px',
              top: item.dy + 'px',
              'z-index': item.zIndex,
            }"
          >
            
            <img
              draggable="false"
              :src="typeList[item.type].src"
              :alt="typeList[item.type].name + item.EName"
            />
            <span>{{ typeList[item.type].name + item.EName }}span>
          div>
        div>

逻辑

<script setup lang="ts">
import { ref, reactive, watch, computed, Ref } from "vue";
import { mapPunctuation } from "@/utils/youran";
let rem = ref(0.005208); // 10/1920  做好功能给上面的left top乘上去就行了 left: item.dx * rem + 'px'

const range: Ref = ref(null);

// 这里只是把存在文件里的base64图片文件取出来,
let typeList = reactive([
  {
    type: 1,
    src: "",
    name: "球机-摄像头",
  },
  {
    type: 2,
    src: "",
    name: "抢机-摄像头",
  },
  {
    type: 3,
    src: "",
    name: "无源打卡设备",
  },
  {
    type: 4,
    src: "",
    name: "无源打卡设备",
  },
  {
    type: 5,
    src: "",
    name: "反向控制",
  },
]);

typeList.forEach((item, index) => {
  item.src = mapPunctuation[index].src;
});

let pointList = ref([
  {
    fId: "111",
    type: 1,
    EId: "",
    EName: "",
    dx: 0,
    dy: 0,
    zIndex: 2,
  },
]);

// 鼠标事件
let downType = ref(false);
let disX = 0;
let disY = 0;
let odiv: any = null;
let mousedown = (e: any, item: any) => {
  downType.value = true;
  console.log("按下事件");
  odiv = e.target;
  disX = e.clientX - odiv.offsetLeft;
  disY = e.clientY - odiv.offsetTop;

  document.onmousemove = (e) => {
    console.log("移动事件");
    //计算元素位置(需要判断临界值)
    let left = e.clientX - disX;
    let top = e.clientY - disY;
    let { offsetHeight: pondModelHeight, offsetWidth: pondModelWidth } =
      range.value;
    let { offsetHeight: sonNodeHeight, offsetWidth: sonNodeWidth } = odiv;
    // 左上角(left)
    if (left < 0) {
      left = 0;
    }
    if (top < 0) {
      top = 0;
    }
    // 左下角
    if (top > pondModelHeight - sonNodeHeight) {
      top = pondModelHeight - sonNodeHeight;
    }
    if (left > pondModelWidth - sonNodeWidth) {
      left = pondModelWidth - sonNodeWidth;
    }
    item.dx = left;
    item.dy = top;
    item.zIndex = 999;
  };
  document.onmouseup = (e) => {
    console.log("放开事件");
    document.onmousemove = null;
    document.onmouseup = null;
    item.zIndex = 1;
    odiv = null;
  };
};
</script>

css:本来不该放出来,但是我在这里踩坑了,觉得其他人也会(img图片有默认的拖拽,很难禁止,所以拿一个伪元素直接放在img上面,不给点img就不会踩坑)

      .range {
        width: 960px;
        height: 540px;
        background-color: pink;
        position: relative;
        .iconItem {
          position: absolute;
          left: 10px;
          top: 10px;
          z-index: 2;
          display: flex;
          align-items: center;
          cursor: move;
          user-select: none;
          width: 32px;
          height: 32px;
          background: yellow;
          img {
            width: 32px;
            height: 32px;
          }
          // 关键
          &::before {
            content: " ";
            width: 100%;
            height: 100%;
            position: absolute;
            top: 0;
            left: 0;
            z-index: 3;
          }
          &:hover {
            // span {
            //   display: block;
            // }
          }
          span {
            display: none;
            font-size: 12px;
            font-family: YouSheBiaoTiHei;
            color: red;
          }
        }
      }

完整代码:(建议按照上面的一点点复制吧,有几个文件是外部的base64图片)

<template>
  <div class="PastureMap">
    <div class="mapContent">
      <div class="mapBox">
        <div class="range" id="range" ref="range">
          <div
            class="iconItem"
            v-for="(item, index) in pointList"
            :key="index"
            @mousedown.stop.prevent.native="mousedown($event, item)"
            :style="{
              left: item.dx + 'px',
              top: item.dy + 'px',
              'z-index': item.zIndex,
            }"
          >
            <!--
            @mousemove.stop.prevent.native="mousemove($event, item)"
            @mouseup.stop.prevent.native="mouseup($event, item)"
             -->
            <img
              draggable="false"
              :src="typeList[item.type].src"
              :alt="typeList[item.type].name + item.EName"
            />
            <span>{{ typeList[item.type].name + item.EName }}</span>
          </div>
        </div>
      </div>
      <div class="operationPanel">
        <div class="addIConCard">
          <div class="title">
            <span>新增图标</span>
          </div>
          <div class="box">
            <div class="bgImg">
              <div class="left">
                <span>背景图:</span>
              </div>
              <div class="right">
                <button>选择图片</button>
                <span>建议尺寸:960*540</span>
              </div>
            </div>
            <div class="iconBtnForm">
              <div class="cell">
                <div class="left">
                  <span>圈舍</span>
                </div>
                <div class="right">
                  <input type="text" placeholder="请选择圈舍" />
                </div>
              </div>
              <div class="cell">
                <div class="left">
                  <span>设备编号</span>
                </div>
                <div class="right">
                  <input type="text" placeholder="请输入设备编号" />
                </div>
              </div>
              <div class="cell">
                <div class="left">
                  <span>类型</span>
                </div>
                <div class="right">
                  <input type="text" placeholder="请选择类型" />
                </div>
              </div>
            </div>
            <div class="addBtn">
              <button>新增</button>
            </div>
          </div>
        </div>
        <div class="iconList">
          <div class="item" v-for="(item, index) in pointList" :key="index">
            <div class="left">
              <span>类型</span>
            </div>
            <div class="right">
              <input type="text" placeholder="名称" />
            </div>
            <div class="del">
              <img src="" alt="del" />
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, reactive, watch, computed, Ref } from "vue";
import { mapPunctuation } from "@/utils/youran";
let rem = ref(0.005208); // 10/1920

const range: Ref = ref(null);
let typeList = reactive([
  {
    type: 1,
    src: "",
    name: "球机-摄像头",
  },
  {
    type: 2,
    src: "",
    name: "抢机-摄像头",
  },
  {
    type: 3,
    src: "",
    name: "无源打卡设备",
  },
  {
    type: 4,
    src: "",
    name: "无源打卡设备",
  },
  {
    type: 5,
    src: "",
    name: "反向控制",
  },
]);

typeList.forEach((item, index) => {
  item.src = mapPunctuation[index].src;
});

let pointList = ref([
  {
    fId: "111",
    type: 1,
    EId: "",
    EName: "",
    dx: 0,
    dy: 0,
    zIndex: 2,
  },
]);

// 鼠标事件
let downType = ref(false);
let disX = 0;
let disY = 0;
let odiv: any = null;
let mousedown = (e: any, item: any) => {
  downType.value = true;
  console.log("按下事件");
  odiv = e.target;
  disX = e.clientX - odiv.offsetLeft;
  disY = e.clientY - odiv.offsetTop;

  document.onmousemove = (e) => {
    console.log("移动事件");
    //计算元素位置(需要判断临界值)
    let left = e.clientX - disX;
    let top = e.clientY - disY;
    let { offsetHeight: pondModelHeight, offsetWidth: pondModelWidth } =
      range.value;
    let { offsetHeight: sonNodeHeight, offsetWidth: sonNodeWidth } = odiv;
    // 左上角(left)
    if (left < 0) {
      left = 0;
    }
    if (top < 0) {
      top = 0;
    }
    // 左下角
    if (top > pondModelHeight - sonNodeHeight) {
      top = pondModelHeight - sonNodeHeight;
    }
    if (left > pondModelWidth - sonNodeWidth) {
      left = pondModelWidth - sonNodeWidth;
    }
    item.dx = left;
    item.dy = top;
    item.zIndex = 999;
  };
  document.onmouseup = (e) => {
    console.log("放开事件");
    document.onmousemove = null;
    document.onmouseup = null;
    item.zIndex = 1;
    odiv = null;
  };
};
</script>

<style lang="less" scoped>
.PastureMap {
  height: 100%;
  .mapContent {
    display: flex;
    height: 100%;
    .mapBox {
      flex: 1;
      height: 100%;
      .range {
        width: 960px;
        height: 540px;
        background-color: pink;
        position: relative;
        .iconItem {
          position: absolute;
          left: 10px;
          top: 10px;
          z-index: 2;
          display: flex;
          align-items: center;
          cursor: move;
          user-select: none;
          width: 32px;
          height: 32px;
          background: yellow;
          img {
            width: 32px;
            height: 32px;
          }
          &::before {
            content: " ";
            width: 100%;
            height: 100%;
            position: absolute;
            top: 0;
            left: 0;
            z-index: 3;
          }
          &:hover {
            // span {
            //   display: block;
            // }
          }
          span {
            display: none;
            font-size: 12px;
            font-family: YouSheBiaoTiHei;
            color: red;
          }
        }
      }
    }

    .operationPanel {
      width: 270px;
      .addIConCard {
        .title {
          span {
          }
        }
        .box {
          .bgImg {
            display: flex;
            align-items: center;
            .left {
            }
            .right {
            }
          }
          .iconBtnForm {
            .cell {
              display: flex;
              align-items: center;
              .left {
                span {
                }
              }
              .right {
                input {
                }
              }
            }
          }
        }
      }
      .iconList {
        .item {
          display: flex;
          align-items: center;
          position: relative;
          .left {
            span {
            }
          }
          .right {
            input {
            }
          }
          .del {
            position: absolute;
            top: 0;
            right: 0;
          }
        }
      }
    }
  }
}
</style>

你可能感兴趣的:(封装,vue,javascript,前端,vue.js)