likes
comments
collection
share

vue自定义指令—可拖拽抽屉(拖拽修改抽屉宽度)前言 今天需求评审里有个小点:让抽屉支持拖拽改变宽度,这种给组件库的现成

作者站长头像
站长
· 阅读数 22

前言

今天需求评审里有个小点:让抽屉支持拖拽改变宽度,这种给组件库的现成组件添加逻辑的操作,用指令最合适了,因为指令提供的生命周期更加接近于底层,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
评论
请登录