vue3 自定义拖动弹窗

起因

项目中常需要用到拖动弹窗,于是手动实现了一个。为了避免重复造轮子,以作记录!

点击标题即可拖动

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>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值