因为项目有rem适配,使用第三方插件无法处理适配问题,所有只能自己写拖拽功能了
拖拽一般都会想到按下,移动,放开,但是本人亲测,就在div绑定一个按下事件就行了(在事件里面写另外两个事件),另外两个绑上,会莫名其妙卡死,那种莫名其妙的问题
推荐几个开发调试时使用的第三方拖动插件吧,虽然没用上,但是他们是真的好vue-drag-resize
和vuedraggable
,其中前者更轻量化,后者功能更全
主要功能:
效果图:
界面:(就是大的父盒子包着几个小盒子,盒子里面有图片和文字)
<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>
逻辑:
<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>