vue自定义指令—可拖拽抽屉(拖拽修改抽屉宽度)前言 今天需求评审里有个小点:让抽屉支持拖拽改变宽度,这种给组件库的现成
前言
今天需求评审里有个小点:让抽屉支持拖拽改变宽度,这种给组件库的现成组件添加逻辑的操作,用指令最合适了,因为指令提供的生命周期更加接近于底层,bind、update等都是针对虚拟dom提供的生命周期钩子,而且面对黑盒的组件,我们也只能从dom操作入手来拓展组件内逻辑。话不多说,代码如下(附带详细注释与编码思路)
源码
import Vue from 'vue';
/**
* 使用示例:
* <mtd-drawer v-dragDrawer></mtd-drawer>
* 效果:
* 抽屉可拖动改变宽度
*/
// 获取目标元素距离屏幕左边缘的距离
function getOffsetLeft(target: HTMLElement): number {
let offsetLeft = 0;
while (target != window.document.body && target != null) {
offsetLeft += target.offsetLeft;
target = target.offsetParent as HTMLElement;
}
return offsetLeft;
}
// 可拉动的边的宽度(用户可点击区域宽度)
const resizeSideWidth = 10;
// 抽屉的最小宽度
const minWidth = 400;
// 可拉动边元素
let resizeSide: HTMLDivElement;
// 可拉动图标指引用户
let resizeIconContainer: HTMLDivElement;
const dragDrawer: Vue.DirectiveOptions = {
// bind中完成元素创建
bind() {
// 创建可拉动边元素,定位到抽屉的左边
resizeSide = document.createElement('div');
resizeSide.style.cursor = 'col-resize'; // 设置鼠标样式为可左右伸缩,且中间有一个竖线(其他可能的选项:n-resize-向上箭头、se-resize-右下箭头...)
resizeSide.style.position = 'absolute';
resizeSide.style.height = '100%';
resizeSide.style.width = `${resizeSideWidth}px`;
resizeSide.style.left = '0px';
resizeSide.style.top = '0px';
// 鼠标悬浮时添加样式
resizeSide.addEventListener('mouseenter', () => {
resizeSide.style.borderLeft = '2px solid #1c6cdc';
});
// 鼠标移出时移除样式
resizeSide.addEventListener('mouseleave', () => {
resizeSide.style.borderLeft = '';
});
// 创建可拉动图标指引用户
resizeIconContainer = document.createElement('div');
const resizeIcon = document.createElement('i');
resizeIcon.classList.add('mtdicon', 'mtdicon-triangle-left');
resizeIconContainer.appendChild(resizeIcon);
resizeIconContainer.style.fontSize = '25px';
resizeIcon.style.position = 'absolute';
resizeIcon.style.left = '-14px';
resizeIcon.style.top = '50%';
},
update(el) {
// update和Vue.nextTick保证抽屉的内容部分存在,并且onmousedown添加回调逻辑是覆盖性的,不会存在回调叠加
Vue.nextTick(() => {
// mtd抽屉的主体部分为一个div.mtd-drawer元素
const drawer = el.querySelector('.mtd-drawer') as HTMLDivElement;
if (!drawer) {
return;
}
// 保证icon完整显示
drawer.style.overflow = 'visible';
if (!drawer.contains(resizeSide)) {
drawer.appendChild(resizeSide);
}
if (!drawer.contains(resizeIconContainer)) {
drawer.appendChild(resizeIconContainer);
}
// 给可拉动元素添加鼠标点击回调函数
resizeSide.onmousedown = (clickEvent) => {
// 获取抽屉主体部分的宽度
const drawerWidth = drawer.clientWidth; // clientWidth为元素包含border在内的宽度
// 获取抽屉距离页面左端的距离
const drawerOffsetLeft = getOffsetLeft(drawer);
// 获取用户点击的位置
const clickX = clickEvent.clientX; // 点击事件对象的clientX,clientY参照点为页面的左上角
// 通过document.onmousemove监听鼠标的移动事件
document.onmousemove = function (moveEvent) {
moveEvent.preventDefault();
const { clientX: moveX } = moveEvent;
// 判断用户是否点击到了可拉动区域
if (clickX > drawerOffsetLeft && clickX < drawerOffsetLeft + resizeSideWidth) {
// 鼠标移动后的坐标小于点击时的坐标,即往左拖拽,抽屉宽度增加
if (moveX < clickX) {
drawer.style.width = drawerWidth + (clickX - moveX) + 'px';
} else if (moveX > clickX) {
// 往右拖拽,抽屉宽度减小
if (drawer.clientWidth >= minWidth) {
drawer.style.width = drawerWidth - (moveX - clickX) + 'px';
}
}
}
};
// 鼠标点击结束,清除鼠标移动监听
document.onmouseup = function () {
document.onmousemove = null;
document.onmouseup = null;
};
// 注释补充:在鼠标点击回调中添加onmousemove鼠标移动的回调逻辑,并在鼠标弹起时清除,相当于onmousemove只在鼠标点击过程中生效
};
});
},
};
export default dragDrawer;
转载自:https://juejin.cn/post/7404776486707724322