起因
项目中常需要用到拖动弹窗,于是手动实现了一个。为了避免重复造轮子,以作记录!
点击标题即可拖动
vue3 + unocss
组件代码
<template>
<Teleport to="body">
<Transition name="dialog">
<div
v-bind="$attrs"
ref="dialogRef"
v-if="model"
class="dialog"
:style="`width: ${props.width}; height: ${props.height};`"
>
<div class="dialog-close absolute right-10px top-10px z-1 cursor-pointer" @click="handleClose">X</div>
<div ref="dialogHeaderRef" class="dialog-header relative h-50px flex pl-20px lh-50px" :draggable="false">
<slot name="title">
<div class="dialog-title w-100%">{{ props.title }}</div>
</slot>
</div>
<div class="dialog-content">
<slot> </slot>
</div>
</div>
</Transition>
</Teleport>
</template>
<script setup>
import { nextTick, onMounted, ref, defineProps, defineEmits, Teleport, Transition, watch } from 'vue';
const props = defineProps({
title: {
type: String,
default: '',
},
width: {
type: [Number, String],
default: '500px',
},
height: {
type: [Number, String],
default: '400px',
},
});
const model = defineModel({ default: false });
const emit = defineEmits(['close']);
const dialogRef = ref();
const dialogHeaderRef = ref();
const handleClose = () => {
model.value = false;
emit('close');
};
const handleDown = (e) => {
const dialog = dialogRef.value;
e.target.style.cursor = 'move';
var distX = e.pageX - dialog.offsetLeft;
var distY = e.pageY - dialog.offsetTop;
document.onmousemove = function (mouse) {
e.preventDefault();
e.stopPropagation();
// 用鼠标的位置减去鼠标相对元素的位置,得到元素的位置
let left = mouse.clientX - distX;
let top = mouse.clientY - distY;
if (top <= 0) {
top = 0;
} else if (top >= document.documentElement.clientHeight - e.target.clientHeight) {
top = document.documentElement.clientHeight - e.target.clientHeight;
}
if (left <= 0) {
left = 0;
} else if (left >= document.documentElement.clientWidth - e.target.clientWidth) {
left = document.documentElement.clientWidth - e.target.clientWidth;
}
dialog.style.left = left + 'px';
dialog.style.top = top + 'px';
};
document.onmouseup = function () {
e.target.style.cursor = 'default';
document.onmousemove = null;
document.onmouseup = null;
};
};
const handleUp = (e) => {
e.target.style.cursor = 'default';
document.onmousemove = null;
};
watch(
() => model.value,
(val) => {
if (val) {
nextTick(() => {
const { width, height } = dialogRef.value.getBoundingClientRect();
dialogRef.value.style.left = (document.documentElement.clientWidth - width) / 2 + 'px';
dialogRef.value.style.top = (document.documentElement.clientHeight - height) / 2 + 'px';
dialogHeaderRef.value.addEventListener('mouseup', handleUp);
dialogHeaderRef.value.addEventListener('mousedown', handleDown);
});
}
},
);
</script>
<style scoped lang="scss">
.dialog {
position: fixed;
top: 50%;
left: 50%;
z-index: 99;
width: 614px;
height: 451px;
color: #fff;
background: #1f34347e;
background-size: 100% 100%;
transition: opacity 0.3s ease;
.dialog-content {
height: calc(100% - 50px);
transition: all 0.3s ease;
}
}
.dialog-enter-from {
opacity: 0;
}
.dialog-leave-to {
opacity: 0;
}
.dialog-enter-from .dialog-container,
.dialog-leave-to .dialog-container {
-webkit-transform: scale(1.1);
transform: scale(1.1);
}
</style>
父组件使用代码
<template>
<CommonDialog v-model="show" class="layer-view" title="属性表" @close="show = false">
<template #title>
<div>标题</div>
</template>
<div class="view-content">
内容
</div>
</CommonDialog>
</template>
<style lang="scss">
// 若需要通过class修改其样式,就不能加scoped。 因为是用了Teleport 穿透也失效了
.layer-view {
width: 600px !important;
height: 590px !important;
color: #fff;
background: #1f3434b2;
border-radius: 4px;
.view-content {
height: 100%;
padding: 20px;
overflow: auto;
scrollbar-width: thin;
}
}
</style>