通过 Vue 自定义指令方式为 Element UI 的 el-dialog 组件添加:
点击头部全屏按钮切换全屏/恢复
拖拽头部移动对话框
全屏按钮控制
Vue 自定义指令:通过 directive 方法创建可复用指令
DOM 操作:动态修改对话框样式属性
事件监听:mousedown/mousemove/mouseup 实现拖拽逻辑
新建 full.js
文件, 核心代码:
// 全屏处理函数
function handleFullScreen(dialog, isFullScreen, marginTop, width) {
// ...(见原始代码)
}
const fullScreenDirective = Vue => {
Vue.directive('fullScreen', {
bind(el, binding, vnode) {
// 核心实现逻辑
}
})
}
export default fullScreenDirective
在 main.js 中全局注册:
import fullScreenDirective from '@/directive/full.js';
Vue.use(fullScreenDirective)
const dialogHeaderEl = el.querySelector('.el-dialog__header')
const dragDom = el.querySelector('.el-dialog')
dialogHeaderEl.onmousedown = (e) => {
// 计算初始偏移量
const disX = e.clientX - dialogHeaderEl.offsetLeft
const disY = e.clientY - dialogHeaderEl.offsetTop
// 获取屏幕尺寸
const screenWidth = document.body.clientWidth
const screenHeight = document.body.clientHeight
// 边界处理参数
const minDragDomLeft = dragDom.offsetLeft
const maxDragDomLeft = screenWidth - dragDom.offsetLeft - dragDom.offsetWidth
}
document.onmousemove = function (e) {
// 计算新位置
let left = e.clientX - disX
let top = e.clientY - disY
// 边界限制
left = Math.max(-minDragDomLeft, Math.min(left, maxDragDomLeft))
top = Math.max(-minDragDomTop, Math.min(top, maxDragDomTop))
// 更新位置
dragDom.style.cssText += `;left:${left + styL}px;top:${top + styT}px;`
}
function handleFullScreen(dialog, isFullScreen, marginTop, width) {
if (!dialog) return false;
// 保存原始样式
if (!dialog.__originalStyle) {
dialog.__originalStyle = {
margin: dialog.style.margin,
padding: dialog.style.padding,
boxSizing: dialog.style.boxSizing,
position: dialog.style.position,
top: dialog.style.top,
left: dialog.style.left,
width: dialog.style.width,
height: dialog.style.height
};
}
if (isFullScreen) {
// 保存当前可视位置
dialog.__scrollTop = document.documentElement.scrollTop;
Object.assign(dialog.style, {
margin: '0',
padding: '0',
boxSizing: 'border-box',
position: 'fixed',
top: '-56px',
left: '258px',
width: 'calc(100% - 258px)',
height: '100vh',
zIndex: 9999
});
} else {
// 还原原始样式
Object.assign(dialog.style, dialog.__originalStyle);
// 恢复滚动位置
if (dialog.__scrollTop !== undefined) {
document.documentElement.scrollTop = dialog.__scrollTop;
}
}
}
const fullScreenBtn = document.createElement('button')
fullScreenBtn.innerHTML = ''
dialogHeaderEl.append(fullScreenBtn)
// 双击事件
dialogHeaderEl.addEventListener('dblclick', toggleFullScreen)
function handleFullScreen(dialog, isFullScreen, marginTop, width) {
if (!dialog) return false;
// 保存原始样式
if (!dialog.__originalStyle) {
dialog.__originalStyle = {
margin: dialog.style.margin,
padding: dialog.style.padding,
boxSizing: dialog.style.boxSizing,
position: dialog.style.position,
top: dialog.style.top,
left: dialog.style.left,
width: dialog.style.width,
height: dialog.style.height
};
}
if (isFullScreen) {
// 保存当前可视位置
dialog.__scrollTop = document.documentElement.scrollTop;
Object.assign(dialog.style, {
margin: '0',
padding: '0',
boxSizing: 'border-box',
position: 'fixed',
top: '-56px',
left: '258px',
width: 'calc(100% - 258px)',
height: '100vh',
zIndex: 9999
});
} else {
// 还原原始样式
Object.assign(dialog.style, dialog.__originalStyle);
// 恢复滚动位置
if (dialog.__scrollTop !== undefined) {
document.documentElement.scrollTop = dialog.__scrollTop;
}
}
}
const fullScreenDirective = Vue => {
Vue.directive('fullScreen', { // 属性名称draggable-dialog,前面加v- 使用
bind(el, binding, vnode) {
const dialogHeaderEl = el.querySelector('.el-dialog__header')
const dragDom = el.querySelector('.el-dialog')
dialogHeaderEl.style.cssText += ';cursor:move;'
dragDom.style.cssText += ';top:0px;'
// 获取原有属性 ie dom元素.currentStyle 火狐谷歌 window.getComputedStyle(dom元素, null);
const getStyle = (function () {
if (window.document.currentStyle) {
return (dom, attr) => dom.currentStyle[attr]
} else {
return (dom, attr) => getComputedStyle(dom, false)[attr]
}
})()
dialogHeaderEl.onmousedown = (e) => {
// 鼠标按下,计算当前元素距离可视区的距离
const disX = e.clientX - dialogHeaderEl.offsetLeft
const disY = e.clientY - dialogHeaderEl.offsetTop
const dragDomWidth = dragDom.offsetWidth
// const dragDomHeight = dragDom.offsetHeight
const screenWidth = document.body.clientWidth
const screenHeight = document.body.clientHeight
const minDragDomLeft = dragDom.offsetLeft
const maxDragDomLeft = screenWidth - dragDom.offsetLeft - dragDomWidth
const minDragDomTop = dragDom.offsetTop
// const maxDragDomTop = screenHeight - dragDom.offsetTop - dragDomHeight
const maxDragDomTop = screenHeight - dragDom.offsetTop// 不需要-dragDomHeight
// 获取到的值带px 正则匹配替换
let styL = getStyle(dragDom, 'left')
let styT = getStyle(dragDom, 'top')
if (styL.includes('%')) {
styL = +document.body.clientWidth * (+styL.replace(/\%/g, '') / 100)
styT = +document.body.clientHeight * (+styT.replace(/\%/g, '') / 100)
} else {
styL = +styL.replace(/\px/g, '')
styT = +styT.replace(/\px/g, '')
}
document.onmousemove = function (e) {
// 通过事件委托,计算移动的距离
let left = e.clientX - disX
let top = e.clientY - disY
// 边界处理
if (-(left) > minDragDomLeft) {
left = -minDragDomLeft
} else if (left > maxDragDomLeft) {
left = maxDragDomLeft
}
if (-(top) > minDragDomTop) {
top = -minDragDomTop
} else if (top > maxDragDomTop) {
top = maxDragDomTop
}
// 移动当前元素
dragDom.style.cssText += `;left:${left + styL}px;top:${top + styT}px;`
// emit onDrag event
vnode.child.$emit('draggable-dialog')
}
document.onmouseup = function (e) {
document.onmousemove = null
document.onmouseup = null
}
}
let isFullScreen = false
const header = el.querySelector('.el-dialog__header')
if (!dialogHeaderEl || !dragDom || !vnode) return
dialogHeaderEl.style.paddingRight = '43px'
dialogHeaderEl.style.boxShadow = '0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)'
const { width, top } = vnode.componentInstance
const fullScreenBtn = document.createElement('button')
fullScreenBtn.type = 'button'
fullScreenBtn.style = 'float:right;padding:0;background: 0 0;border:0;outline:0;cursor:pointer;font-size:16px;'
const fullScreenIcon = document.createElement('i')
fullScreenIcon.className = 'el-icon el-icon-full-screen'
fullScreenBtn.append(fullScreenIcon)
fullScreenBtn.addEventListener('click', () => {
isFullScreen = !isFullScreen
handleFullScreen(dragDom, isFullScreen, top, width)
return false
})
dialogHeaderEl.append(fullScreenBtn)
dialogHeaderEl.addEventListener('dblclick', () => {
isFullScreen = !isFullScreen
handleFullScreen(dragDom, isFullScreen, top, width)
return false
})
}
})
}
export default fullScreenDirective
全屏时使用 position: fixed
脱离文档流
计算 calc(100% - 258px)
适应侧边栏布局: 排除菜单的宽度,这里可以改成动态获取
保存原始样式实现完美还原
// 横向边界
left = Math.max(-minDragDomLeft, Math.min(left, maxDragDomLeft))
// 纵向边界
top = Math.max(-minDragDomTop, Math.min(top, maxDragDomTop))
使用事件委托提升性能
及时移除事件监听防止内存泄漏
document.onmouseup = function() {
document.onmousemove = null
document.onmouseup = null
}
z-index 层级:全屏时设置为 9999 确保置顶
滚动位置保存:记录 document.documentElement.scrollTop
百分比单位处理:转换为实际像素值
if (styL.includes('%')) {
styL = +document.body.clientWidth * (+styL.replace(/%/g, '') / 100)
}
自定义全屏按钮样式:通过 CSS 修改图标
限制拖拽范围:修改边界计算参数
动画效果:添加过渡动画提升体验
后续补齐..