div弹窗实现四角缩放功能(vue)

最近项目中需要用到很多的可以收缩的弹窗,于是使用vue3做了一个通用组件,给大家分享一下,效果如下图,附源码。

实现思路:

1、弹窗上加4个角,用来拖拽缩放。

  <div class="resize-handle-tl resize-handle"></div>
  <div class="resize-handle-tr resize-handle"></div>
  <div class="resize-handle-bl resize-handle"></div>
  <div class="resize-handle-br resize-handle"></div>

2、设置4个角的样式以及绝对定位。

.resize-handle-br {
  width: 10px;
  height: 10px;
  position: absolute;
  bottom: 0;
  right: 0;
  cursor: se-resize;
}
.resize-handle-bl {
  width: 10px;
  height: 10px;
  position: absolute;
  bottom: 0;
  left: 0;
  cursor: sw-resize;
}
.resize-handle-tl {
  width: 10px;
  height: 10px;
  position: absolute;
  top: 0;
  left: 0;
  cursor: nw-resize;
}
.resize-handle-tr {
  width: 10px;
  height: 10px;
  position: absolute;
  top: 0;
  right: 0;
  cursor: ne-resize;
}

3、监听4个角的鼠标拖拽事件实现缩放。

onMounted(() => {
  resizableBox = document.getElementById("resizable-box");
  resizeHandle = document.querySelectorAll(".resize-handle");
  resizeHandle.forEach((handle) => {
    handle.addEventListener("mousedown", function (e) {
      e.preventDefault();
      originalWidth = parseFloat(getComputedStyle(resizableBox).width);
      originalHeight = parseFloat(getComputedStyle(resizableBox).height);
      originalMouseX = e.clientX;
      originalMouseY = e.clientY;
      resizeType = this.className;
     
      window.addEventListener("mousemove", resize);
      window.addEventListener("mouseup", stopResize);
    });
  });
});

4、缩放核心代码:

let firstLeft = props.left; // 初始位置
let firstTop = props.top; // 初始位置
let firstBottom = props.bottom; // 初始位置
let firstRight = props.right // 初始位置
let lastTop = 0; // 上次移动距离
let lastLeft = 0; // 上次移动距离
let lastBottom = 0; // 上次移动距离
let lastRight = 0; // 上次移动距离
const resize = (e) => {
  const deltaX = e.clientX - originalMouseX;
  const deltaY = e.clientY - originalMouseY;
  resizableBox = document.getElementById("resizable-box");
  if (resizeType.includes("resize-handle-tl")) {
    if (resizableBox.style.left) {
      resizableBox.style.left = `${
        originalX + deltaX + lastLeft + firstLeft
      }px`;
      resizableBox.style.top = `${originalY + deltaY + lastTop + firstTop}px`;
    }
    resizableBox.style.width = `${originalWidth - deltaX}px`;
    resizableBox.style.height = `${originalHeight - deltaY}px`;
  } else if (resizeType.includes("resize-handle-tr")) {
    if(resizableBox.style.top) {
      resizableBox.style.top = `${originalY + deltaY + firstTop + lastTop}px`;
    }else {
      resizableBox.style.right = `${ originalX - deltaX + firstRight -lastRight}px`;
    }
    resizableBox.style.width = `${originalWidth + deltaX}px`;
    resizableBox.style.height = `${originalHeight - deltaY}px`;
   
  } else if (resizeType.includes("resize-handle-bl")) {
    if( resizableBox.style.left) {
      resizableBox.style.left = `${originalX + deltaX + firstLeft + lastLeft}px`;
    }else {
      resizableBox.style.bottom = `${originalY - deltaY + firstBottom - lastBottom}px`;
    }
    resizableBox.style.width = `${originalWidth - deltaX}px`;
    resizableBox.style.height = `${originalHeight + deltaY}px`;
   
  } else if (resizeType.includes("resize-handle-br")) {
    if(resizableBox.style.right) {
      resizableBox.style.right = `${ originalX - deltaX + firstRight -lastRight}px`;
      resizableBox.style.bottom = `${originalY - deltaY + firstBottom - lastBottom}px`;
    }
    resizableBox.style.width = `${originalWidth + deltaX}px`;
    resizableBox.style.height = `${originalHeight + deltaY}px`;
  }
};

const stopResize = (e) => {
  if(e.target.classList.contains('resize-handle-tl')) {
    lastTop += e.pageY - originalMouseY;
    lastLeft += e.pageX - originalMouseX;
  }else if(e.target.classList.contains('resize-handle-tr')) {
    lastTop += e.pageY - originalMouseY;
    lastRight += e.pageX - originalMouseX;
  }else if(e.target.classList.contains('resize-handle-bl')) {
    lastLeft += e.pageX - originalMouseX;
    lastBottom += e.pageY - originalMouseY
  }else if(e.target.classList.contains('resize-handle-br')) {
    lastBottom += e.pageY - originalMouseY
    lastRight += e.pageX - originalMouseX;
  }

  window.removeEventListener("mousemove", resize);
  window.removeEventListener("mouseup", stopResize);
};

完整代码如下:

components代码(pop-up.vue)

<template>
  <div class="pop-modal" @click.stop="closePop">
    <div
      id="resizable-box"
      class="popup-content"
      :style="{
        width: width,
        height: height,
        top: top + 'px',
        left: left + 'px',
        right: right + 'px',
        bottom: bottom + 'px',
      }"
      @click.stop="stopClose"
    >
      <div class="popup-container overflow-y-auto">
        <div class="popup-title ALIMAMA_SHUHEITI_BOLD relative flex align-center justif-between">
          <span>{{ title }}</span>
          <popUpIconVue class="r-0 t-0 h-p-40 w-20 mr-5"></popUpIconVue>
        </div>
        <el-table
          :data="tableData"
          stripe
          style="width: 100%; overflow-y: auto"
        >
          <el-table-column prop="target" label="指标" />
          <el-table-column prop="unit" label="单位" width="120px" />
          <el-table-column prop="value" :label="yearLabel" />
          <el-table-column prop="speed" label="增长速度%" width="120px" />
        </el-table>
      </div>
      <div class="resize-handle-tl resize-handle"></div>
      <div class="resize-handle-tr resize-handle"></div>
      <div class="resize-handle-bl resize-handle"></div>
      <div class="resize-handle-br resize-handle"></div>
    </div>
  </div>
</template>

<script setup>
import { defineProps, onMounted, defineEmits } from "vue";
import popUpIconVue from "../icons/popUpIcon.vue";
const emit = defineEmits(["close", "change"]);
const props = defineProps({
  width: {
    type: [Number, String],
    default: "38vw", // 默认宽度
  },
  height: {
    type: [Number, String],
    default: "41vh", // 默认高度
  },
  top: {
    type: Number,
    default: null,
  },
  left: {
    type: Number,
    default: null,
  },
  bottom: {
    type: Number,
    default: null,
  },
  right: {
    type: Number,
    default: null,
  },
  title: {
    type: String,
    default: "",
  },
  tableData: {
    type: Array,
    default: [],
  },
  yearLabel: {
    type: String,
    default: "2023年",
  }
});

let originalWidth = 0;
let originalHeight = 0;
let originalX = 0;
let originalY = 0;
let originalMouseX = 0;
let originalMouseY = 0;
let resizableBox = null;
let resizeHandle = [];
let resizeType = "";

onMounted(() => {
  resizableBox = document.getElementById("resizable-box");
  resizeHandle = document.querySelectorAll(".resize-handle");
  resizeHandle.forEach((handle) => {
    handle.addEventListener("mousedown", function (e) {
      e.preventDefault();
      originalWidth = parseFloat(getComputedStyle(resizableBox).width);
      originalHeight = parseFloat(getComputedStyle(resizableBox).height);
      originalMouseX = e.clientX;
      originalMouseY = e.clientY;
      resizeType = this.className;
     
      window.addEventListener("mousemove", resize);
      window.addEventListener("mouseup", stopResize);
    });
  });
});
let firstLeft = props.left;
let firstTop = props.top;
let firstBottom = props.bottom;
let firstRight = props.right
let lastTop = 0;
let lastLeft = 0;
let lastBottom = 0;
let lastRight = 0;
const resize = (e) => {
  const deltaX = e.clientX - originalMouseX;
  const deltaY = e.clientY - originalMouseY;
  resizableBox = document.getElementById("resizable-box");
  if (resizeType.includes("resize-handle-tl")) {
    if (resizableBox.style.left) {
      resizableBox.style.left = `${
        originalX + deltaX + lastLeft + firstLeft
      }px`;
      resizableBox.style.top = `${originalY + deltaY + lastTop + firstTop}px`;
    }
    resizableBox.style.width = `${originalWidth - deltaX}px`;
    resizableBox.style.height = `${originalHeight - deltaY}px`;
  } else if (resizeType.includes("resize-handle-tr")) {
    if(resizableBox.style.top) {
      resizableBox.style.top = `${originalY + deltaY + firstTop + lastTop}px`;
    }else {
      resizableBox.style.right = `${ originalX - deltaX + firstRight -lastRight}px`;
    }
    resizableBox.style.width = `${originalWidth + deltaX}px`;
    resizableBox.style.height = `${originalHeight - deltaY}px`;
   
  } else if (resizeType.includes("resize-handle-bl")) {
    if( resizableBox.style.left) {
      resizableBox.style.left = `${originalX + deltaX + firstLeft + lastLeft}px`;
    }else {
      resizableBox.style.bottom = `${originalY - deltaY + firstBottom - lastBottom}px`;
    }
    resizableBox.style.width = `${originalWidth - deltaX}px`;
    resizableBox.style.height = `${originalHeight + deltaY}px`;
   
  } else if (resizeType.includes("resize-handle-br")) {
    if(resizableBox.style.right) {
      resizableBox.style.right = `${ originalX - deltaX + firstRight -lastRight}px`;
      resizableBox.style.bottom = `${originalY - deltaY + firstBottom - lastBottom}px`;
    }
    resizableBox.style.width = `${originalWidth + deltaX}px`;
    resizableBox.style.height = `${originalHeight + deltaY}px`;
  }
};

const stopResize = (e) => {
  if(e.target.classList.contains('resize-handle-tl')) {
    lastTop += e.pageY - originalMouseY;
    lastLeft += e.pageX - originalMouseX;
  }else if(e.target.classList.contains('resize-handle-tr')) {
    lastTop += e.pageY - originalMouseY;
    lastRight += e.pageX - originalMouseX;
  }else if(e.target.classList.contains('resize-handle-bl')) {
    lastLeft += e.pageX - originalMouseX;
    lastBottom += e.pageY - originalMouseY
  }else if(e.target.classList.contains('resize-handle-br')) {
    lastBottom += e.pageY - originalMouseY
    lastRight += e.pageX - originalMouseX;
  }

  window.removeEventListener("mousemove", resize);
  window.removeEventListener("mouseup", stopResize);
};

const closePop = () => {
  emit('close');
};
const stopClose = () => {};
</script>

<style lang="scss" scoped>
.pop-modal {
  width: 100vw;
  height: 100vh;
  position: fixed;
  top: 0;
  left: 0;
  z-index: 99999;
}
.popup-content {
  background: rgb(161, 177, 255);
  padding: 1rem;
  position: absolute;
  z-index: 1;
}
.popup-container {
  background: #fff;
  width: 100%;
  height: 100%;
  padding: 0.5rem;
}
.popup-title {
  background:linear-gradient(rgb(31,97,255),rgb(53,79,255), rgb(75,66,255),rgb(80,63,255));
  min-height: 9%;
  width: 100%;
  color: #fff;
  padding: 0 0 0 1rem;
  display: flex;
  align-items: center;
}
.resize-handle-br {
  width: 10px;
  height: 10px;
  //   background-color: #333;
  position: absolute;
  bottom: 0;
  right: 0;
  cursor: se-resize;
}
.resize-handle-bl {
  width: 10px;
  height: 10px;
  //   background-color: #333;
  position: absolute;
  bottom: 0;
  left: 0;
  cursor: sw-resize;
}
.resize-handle-tl {
  width: 10px;
  height: 10px;
  //   background-color: #333;
  position: absolute;
  top: 0;
  left: 0;
  cursor: nw-resize;
}
.resize-handle-tr {
  width: 10px;
  height: 10px;
  //   background-color: #333;
  position: absolute;
  top: 0;
  right: 0;
  cursor: ne-resize;
}
</style>
<style>
.popup-content .el-table th.el-table__cell {
  background-color: rgb(236, 236, 255) !important;
  color: #000;
  font-family: "MiSans-Bold";
  font-size: 1.33rem;
}
.popup-content .el-table__body-wrapper .el-table__row .el-table__cell {
  background: #fff !important;
}
.popup-content .el-table__body-wrapper .el-table__row--striped .el-table__cell {
  background: rgb(236, 236, 255) !important;
}

.popup-content .el-table td.el-table__cell {
  border-bottom: none !important;
}
.popup-content .el-table th.el-table__cell.is-leaf {
  border-bottom: none !important;
}
.popup-content .el-table__inner-wrapper:before {
  background-color: #fff !important;
}
</style>

父组件中调用:

 <popUp
    v-if="popShow"
    :title="popTitle"
    :tableData="grossData"
    :top="top"
    :left="left"
    :right="right"
    :bottom="bottom"
	:yearLabel="yearLabel"
    @close="closePop"
  ></popUp>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值