diff --git a/src/router/modules/demo.ts b/src/router/modules/demo.ts index cb16983..1e946bb 100644 --- a/src/router/modules/demo.ts +++ b/src/router/modules/demo.ts @@ -138,6 +138,10 @@ const titleArr = [ key: 'base64Tool', title: 'base64编码解码工具', }, + { + key: 'resizeElementDialog', + title: '动态调整element弹窗大小', + }, ]; // @/views/demo/**/*.vue diff --git a/src/views/demo/resizeElementDialog/directives/resize.js b/src/views/demo/resizeElementDialog/directives/resize.js new file mode 100644 index 0000000..6064ccb --- /dev/null +++ b/src/views/demo/resizeElementDialog/directives/resize.js @@ -0,0 +1,74 @@ +export default { + updated(el) { + let minWidth = 600; + let minHeight = 200; + const dialogHeaderEl = el.querySelector('.el-dialog__header'); + const dragDom = el.querySelector('.el-dialog'); + if (!dialogHeaderEl || !dragDom) return; + + dragDom.style.overflow = 'auto'; // 滚动条导致无法缩放(通过加大右下角添加的dom即可修复) + // dragDom.style.overflow = 'hidden'; + dialogHeaderEl.onselectstart = () => false; + dialogHeaderEl.style.cursor = 'move'; + + const sty = dragDom.currentStyle || window.getComputedStyle(dragDom, null); + let moveDown = (e) => { + const disX = e.clientX - dialogHeaderEl.offsetLeft; + const disY = e.clientY - dialogHeaderEl.offsetTop; + + let styL = +sty.left.replace(/px/g, ''); + let styT = +sty.top.replace(/px/g, ''); + + document.onmousemove = function (e) { + const l = e.clientX - disX; + const t = e.clientY - disY; + dragDom.style.left = `${l + styL}px`; + dragDom.style.top = `${t + styT}px`; + }; + document.onmouseup = () => { + document.onmousemove = null; + document.onmouseup = null; + }; + }; + dialogHeaderEl.onmousedown = moveDown; + + // 创建一个拉伸块的函数(右下角) + function createResizeHandle(cursor, styles, onMouseMoveHandler) { + const handle = document.createElement('div'); + Object.assign(handle.style, { + position: 'absolute', + zIndex: '99', + ...styles, + cursor, + }); + dragDom.appendChild(handle); + handle.onmousedown = (e) => { + document.onmousemove = (ev) => { + ev.preventDefault(); + onMouseMoveHandler(ev, e); + }; + document.onmouseup = () => { + document.onmousemove = null; + document.onmouseup = null; + }; + }; + } + + // 示例:右下角拖拽 + createResizeHandle( + 'se-resize', + { + width: '20px', + height: '20px', + right: '0px', + bottom: '0px', + }, + (ev, e) => { + let width = ev.clientX - dragDom.getBoundingClientRect().left; + let height = ev.clientY - dragDom.getBoundingClientRect().top; + dragDom.style.width = width > minWidth ? width + 'px' : minWidth + 'px'; + dragDom.style.height = height > minHeight ? height + 'px' : minHeight + 'px'; + } + ); + }, +}; diff --git a/src/views/demo/resizeElementDialog/index.vue b/src/views/demo/resizeElementDialog/index.vue new file mode 100644 index 0000000..3ff7c23 --- /dev/null +++ b/src/views/demo/resizeElementDialog/index.vue @@ -0,0 +1,61 @@ + + + diff --git a/src/views/demo/resizeElementDialog/resizeElementDialog.md b/src/views/demo/resizeElementDialog/resizeElementDialog.md new file mode 100644 index 0000000..d4bb263 --- /dev/null +++ b/src/views/demo/resizeElementDialog/resizeElementDialog.md @@ -0,0 +1,223 @@ +## 代码详解 + +当然可以,下面是对这段 Vue.js 自定义指令(`updated` 钩子)代码的**逐行详细解释**,帮助你全面理解其功能、原理和实现细节。 + +--- + +## 🎯 **整体功能** +这段代码是一个 Vue 的自定义指令(或更准确地说,是一个 **组件的 `updated` 生命周期钩子**),用于: +> **让 `el-dialog` 组件支持“拖拽移动”和“右下角缩放”**。 + +它通过原生 DOM 操作,在 `el-dialog` 被更新后,动态添加拖拽和缩放功能,使其像桌面窗口一样可拖动、可调整大小。 + +--- + +## ✅ 一、核心结构:`updated(el)` +```js +updated(el) { +``` +- `updated` 是 Vue 组件的生命周期钩子,当组件的 VDOM 更新后执行。 +- `el` 是该指令作用的 DOM 元素(即 `` 的根节点)。 +- 这里利用 `updated` 在 DOM 渲染完成后注入行为,确保元素存在。 + +--- + +## ✅ 二、定义最小尺寸 +```js +let minWidth = 600; +let minHeight = 200; +``` +- 设置对话框的**最小宽度和高度**,防止用户把窗口拖得太小。 +- 保证用户体验:不会出现“看不见内容”的情况。 + +--- + +## ✅ 三、获取关键 DOM 元素 +```js +const dialogHeaderEl = el.querySelector('.el-dialog__header'); +const dragDom = el.querySelector('.el-dialog'); +``` +- `dialogHeaderEl`:对话框的标题栏区域,用于拖拽移动。 +- `dragDom`:整个对话框容器(`.el-dialog`),用于移动和缩放操作。 +- 如果没有找到 `dragDom`,直接 `return`,避免报错。 + +--- + +## ✅ 四、设置样式与禁用选择 +```js +dragDom.style.overflow = 'hidden'; +dialogHeaderEl.onselectstart = () => false; +dialogHeaderEl.style.cursor = 'move'; +``` +- `overflow: hidden`:防止拖拽过程中出现滚动条或内容溢出。 +- `onselectstart = () => false`:禁止在标题栏上选中文本(避免误选)。 +- `cursor: move`:鼠标悬停在标题栏时显示“移动”光标,提升交互提示。 + +--- + +## ✅ 五、获取当前样式(兼容 IE 和现代浏览器) +```js +const sty = dragDom.currentStyle || window.getComputedStyle(dragDom, null); +``` +- `currentStyle`:IE 浏览器使用。 +- `getComputedStyle`:现代浏览器使用。 +- 获取当前 `dragDom` 的 `left`、`top` 等样式值,用于后续计算偏移量。 + +--- + +## ✅ 六、拖拽移动功能(核心逻辑) +```js +let moveDown = (e) => { + const disX = e.clientX - dialogHeaderEl.offsetLeft; + const disY = e.clientY - dialogHeaderEl.offsetTop; +``` +- `moveDown` 是鼠标按下时的回调函数。 +- `disX`, `disY`:记录鼠标点击位置与标题栏左上角的偏移量(即“相对偏移”)。 +- 这是实现“拖拽”的关键:鼠标在移动时,用这个偏移量计算新位置。 + +```js +let styL = +sty.left.replace(/px/g, ''); +let styT = +sty.top.replace(/px/g, ''); +``` +- 把 `left` 和 `top` 的字符串值(如 `"200px"`)转成数字(如 `200`)。 +- 用于后续计算:新位置 = 鼠标位置 - 偏移量 + 原始位置。 + +```js +document.onmousemove = function (e) { + const l = e.clientX - disX; + const t = e.clientY - disY; + dragDom.style.left = `${l + styL}px`; + dragDom.style.top = `${t + styT}px`; +}; +``` +- `onmousemove`:鼠标移动时触发。 +- 计算新位置:`新 left = 鼠标 X - 偏移 X + 原始 left` +- 动态设置 `left` 和 `top`,实现拖拽移动。 + +```js +document.onmouseup = () => { + document.onmousemove = null; + document.onmouseup = null; +}; +``` +- `onmouseup`:鼠标松开时,清除事件监听,防止内存泄漏。 + +```js +dialogHeaderEl.onmousedown = moveDown; +``` +- 将 `moveDown` 绑定到标题栏的 `mousedown` 事件,启动拖拽。 + +--- + +## ✅ 七、创建缩放 handle(右下角拖拽) +```js +function createResizeHandle(cursor, styles, onMouseMoveHandler) { + const handle = document.createElement('div'); + Object.assign(handle.style, { + position: 'absolute', + zIndex: '99', + ...styles, + cursor, + }); + dragDom.appendChild(handle); + handle.onmousedown = (e) => { + document.onmousemove = (ev) => { + ev.preventDefault(); + onMouseMoveHandler(ev, e); + }; + document.onmouseup = () => { + document.onmousemove = null; + document.onmouseup = null; + }; + }; +} +``` +- **封装一个创建“缩放句柄”的工具函数**。 +- `cursor`:光标样式(如 `se-resize` 表示右下角可缩放)。 +- `styles`:CSS 样式,比如宽高、位置。 +- `onMouseMoveHandler`:鼠标移动时的回调函数,用于处理缩放逻辑。 +- 创建一个 `div` 作为缩放手柄,添加到 `dragDom` 中。 +- 手柄在 `mousedown` 时绑定 `mousemove` 和 `mouseup`,实现缩放。 + +--- + +## ✅ 八、添加右下角缩放手柄 +```js +createResizeHandle( + 'se-resize', + { + width: '10px', + height: '10px', + right: '0px', + bottom: '0px', + }, + (ev, e) => { + let width = ev.clientX - dragDom.getBoundingClientRect().left; + let height = ev.clientY - dragDom.getBoundingClientRect().top; + dragDom.style.width = width > minWidth ? width + 'px' : minWidth + 'px'; + dragDom.style.height = height > minHeight ? height + 'px' : minHeight + 'px'; + } +); +``` +- 在对话框**右下角**添加一个 `10px × 10px` 的缩放手柄。 +- `se-resize`:表示“右下角可缩放”。 +- `right: 0px`, `bottom: 0px`:定位在右下角。 +- `onMouseMoveHandler` 中: + - `getBoundingClientRect().left/top`:获取对话框当前左上角坐标。 + - `ev.clientX - left`:计算当前宽度。 + - `ev.clientY - top`:计算当前高度。 + - 限制最小宽高:`width > minWidth`,避免窗口过小。 + +--- + +## ✅ 九、总结:功能一览 +| 功能 | 实现方式 | +|------|----------| +| ✅ 拖拽移动 | 标题栏 `mousedown`,通过 `clientX/Y` 和偏移量计算新位置 | +| ✅ 禁止选中文本 | `onselectstart = () => false` | +| ✅ 右下角缩放 | 创建 `div` 手柄,监听 `mousemove` 动态修改 `width` 和 `height` | +| ✅ 最小尺寸限制 | 通过 `minWidth` / `minHeight` 保护窗口不被缩放过小 | +| ✅ 防止样式溢出 | `overflow: hidden` | + +--- + +## ✅ 十、使用场景建议 +- 适合用于 **需要自定义窗口行为的弹窗**,比如: + - 自定义的“设置面板” + - “可视化编辑器”中的浮动窗口 + - “调试工具”弹窗 +- 但注意: + - 不建议在移动端使用(拖拽体验差)。 + - 不能用于 `v-model` 控制的普通弹窗,需配合 `v-if` 或 `v-show` 确保 DOM 存在。 + +--- + +## ✅ 十一、可扩展性建议 +你可以在 `createResizeHandle` 中添加更多方向的手柄: +```js +// 例如:添加右上角、左下角、左上角缩放 +createResizeHandle('ne-resize', { width: '10px', height: '10px', right: '0', top: '0' }, ...); +createResizeHandle('sw-resize', { width: '10px', height: '10px', left: '0', bottom: '0' }, ...); +createResizeHandle('nw-resize', { width: '10px', height: '10px', left: '0', top: '0' }, ...); +``` + +--- + +## ✅ 十二、注意事项 +1. **需确保 `el-dialog` 的 `class` 为 `.el-dialog`**,否则 `querySelector` 会失败。 +2. **该代码在 `updated` 钩子中执行,仅当组件更新时生效**,若对话框是动态渲染,需确保 `updated` 被触发。 +3. **建议封装为一个全局指令**,以便复用: + ```js + Vue.directive('drag-dialog', { updated }); + ``` + 然后在模板中使用: + ```vue + + ``` + +--- + +## ✅ 总结一句话: +> 这段代码通过原生 DOM 操作,在 `el-dialog` 上动态添加“可拖拽移动”和“右下角缩放”功能,实现类似桌面窗口的交互体验,适用于需要高度自定义弹窗行为的场景。 + +如有需要,我可以帮你将其封装成一个可复用的 Vue 指令或组件。 \ No newline at end of file