antd vue+tsx+vueuse 实现可拖拽并且右边可以拉伸的模态框
其中拉伸指令可以用我https://www.cnblogs.com/llcdbk/p/18648406 这篇文章提到的自定义指令,
拖拽功能主要参考 antd中关于模态框拖拽的代码,但是我这里做了一些改进,我要求模态框只能在我的页面窗口内拖拽,向下和向右向左的时候要做边界判断,并且,窗口大小重新调整的时候,模态框的位置还原为初始化,具体代码如下:
import { computed, defineComponent, onBeforeUnmount, onMounted, ref, watch, watchEffect, } from 'vue' // 样式引入 import { Button, Input, Modal } from 'ant-design-vue' import './index.less' // 引入样式文件 import { useDraggable } from '@vueuse/core' // 引入拖拽功能 // 定义组件 export default defineComponent({ name: 'MyModal', // 组件名称 setup() { //#region 弹窗拖拽功能 const modalTitleRef: any = ref() // 模态框标题的引用,用于拖拽 // 使用vueuse的拖拽功能,获取当前拖拽位置和状态 const { x, y, isDragging } = useDraggable(modalTitleRef) const startX = ref<number>(0) // 拖拽开始时的X坐标 const startY = ref<number>(0) // 拖拽开始时的Y坐标 const startedDrag = ref(false) // 拖拽状态标识 const transformX = ref(0) // 当前X轴变换值 const transformY = ref(0) // 当前Y轴变换值 const preTransformX = ref(0) // 拖拽前的X轴变换值 const description = ref('') // 模态框描述内容 const preTransformY = ref(0) // 拖拽前的Y轴变换值 const dragRect = ref({ left: 0, right: 0, top: 0, bottom: 0 }) // 拖拽区域的边界 const rightBoundary = 30 // 右侧边界 const bottomBoundary = 20 // 底部边界 // 监听拖拽位置变化 watch([x, y], () => { if (!startedDrag.value) { startX.value = x.value // 记录拖拽开始时的X坐标 startY.value = y.value // 记录拖拽开始时的Y坐标 const bodyRect = document.body.getBoundingClientRect() // 获取文档的边界矩形 const titleRect = modalTitleRef.value.getBoundingClientRect() // 获取标题的边界矩形 const modalRect = document .getElementById('modal-render') .getBoundingClientRect() // 获取模态框的边界矩形 // 计算拖拽区域的右边界和底边界 dragRect.value.right = bodyRect.width - titleRect.width - rightBoundary dragRect.value.bottom = bodyRect.height - modalRect.height + bottomBoundary preTransformX.value = transformX.value // 记录当前的X轴变换值 preTransformY.value = transformY.value // 记录当前的Y轴变换值 } startedDrag.value = true // 设置拖拽状态为已开始 }) // 监听拖拽状态变化 watch(isDragging, () => { if (!isDragging) { startedDrag.value = false // 如果不再拖拽,重置拖拽状态 } }) // 监听拖拽效果 watchEffect(() => { if (startedDrag.value) { // 计算新的transformX transformX.value = preTransformX.value + Math.min( Math.max(dragRect.value.left, x.value), // 限制X轴的最小值和最大值 dragRect.value.right ) - startX.value // 减去拖拽开始时的X坐标 // 计算新的transformY transformY.value = preTransformY.value + Math.min( Math.max(dragRect.value.top, y.value), // 限制Y轴的最小值和最大值 dragRect.value.bottom ) - startY.value // 减去拖拽开始时的Y坐标 } }) // 计算模态框的变换样式 const transformStyle: any = computed(() => { return { transform: `translate(${transformX.value}px, ${transformY.value}px)`, // 设置变换样式 position: 'relative', // 设置相对定位 cursor: isDragging.value ? 'grabbing' : 'grab', // 根据拖拽状态设置光标样式 } }) //#endregion // 更新拖拽区域的边界 const updateDragRect = () => { const bodyRect = document.body.getBoundingClientRect() // 获取文档的边界矩形 const modalRect = document .getElementById('modal-render') .getBoundingClientRect() // 获取模态框的边界矩形 startedDrag.value = false // 重置拖拽状态 if (modalTitleRef.value) { const ballRect = modalTitleRef.value.getBoundingClientRect() // 获取标题的边界矩形 dragRect.value.right = bodyRect.width - ballRect.width // 更新右边界 dragRect.value.bottom = bodyRect.height - ballRect.height // 更新底边界 // 计算左侧边界 const leftBoundary = 0 // 左侧边界为0 // 如果transformX小于左侧边界,调整transformX if (transformX.value < leftBoundary) { transformX.value = leftBoundary // 设置为左侧边界 } // 如果transformX小于左侧边界减去自身宽度,调整transformX if (transformX.value < leftBoundary - modalRect.width) { transformX.value = leftBoundary - modalRect.width // 确保模态框完全可见 } } } // 组件挂载时添加事件监听 onMounted(() => { window.addEventListener('resize', updateDragRect) // 监听窗口大小变化 }) // 组件卸载时移除事件监听 onBeforeUnmount(() => { window.removeEventListener('resize', updateDragRect) // 移除窗口大小变化监听 }) // 渲染模态框 return () => ( <> <Modal open={true} // 模态框是否打开 mask={false} // 是否显示遮罩 width={500} // 模态框宽度 destroyOnClose={true} // 关闭时销毁模态框 maskClosable={false} // 点击遮罩是否关闭模态框 wrapClassName="config-panel" // 模态框外层类名 closable={false} // 是否显示关闭按钮 bodyStyle={{ height: 'calc(100vh - 250px)', overflow: 'auto' }} // 模态框内容样式 v-slots={{ title: () => ( <div class="flex items-center justify-between" // 标题样式 ref={modalTitleRef} // 标题引用 style="width: 100%; cursor: move" // 设置宽度和光标样式 > 我的测试模态框 </div> ), modalRender: ({ originVNode }) => { return ( <div id="modal-render" // 模态框渲染区域ID class="modal-render" // 模态框渲染区域类名 style={{ ...transformStyle.value }} // 应用变换样式 v-el-drawer-drag-width // 自定义指令 > {originVNode} </div> ) }, footer: () => ( <div class="flex justify-end"> <Button size="middle" style="margin-right: 8px"> 取消 </Button> <Button size="middle" type="primary"> 提交 </Button> </div> ), }} > <div class="action-description"> <Input v-model={description.value} placeholder="添加描述" /> </div> </Modal> </> ) }, })
效果如下
积累小的知识,才能成就大的智慧,希望网上少一些复制多一些原创有用的答案
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
2020-01-03 docker 加速镜像的地址收集
2020-01-03 mongodb 的ID转换实体要注意的地方
2018-01-03 大数据量高并发的数据库优化详解(MSSQL)