记录 Vue 中 封装边框拖拽 指令-前端文章论坛-前端交流-技术鸭(jishuya.cn)

记录 Vue 中 封装边框拖拽 指令

const dragSize = {
  install(Vue) {
    Vue.directive('dragSize', {
      // Vue2 inserted
      beforeMount(el, binding) {
        const state = {
          element: el,
          allowedDirections: {
            top: false,
            bottom: false,
            left: false,
            right: false,
            topLeft: false,
            topRight: false,
            bottomLeft: false,
            bottomRight: false
          },
          dragWidth: 3, // 拖拽条的 宽度或者高度
          class: 'dragSize', // 拖拽条的class
          touch: { lastTime: 0, interval: 300, startX: 0, startY: 0 },
          moveX: 0,
          moveY: 0,
          mousemove_: null,
          mouseup_: null
        };
        // onStart 开始前
        // onMove 拖住时
        // onEnd 拖拽结束时
        const { minWidth, maxWidth, minHeight, maxHeight, onStart, onMove, onEnd, all, ...directions } = binding.value || {};
        const isNumber = (value) => Object.prototype.toString.call(value) === '[object Number]' && !isNaN(value);
        ['minWidth', 'maxWidth', 'minHeight', 'maxHeight'].forEach((property) => (isNumber(binding.value[property]) ? (el.style[property] = `${binding.value[property]}px`) : null));
        state.allowedDirections = ['top', 'bottom', 'left', 'right', 'topLeft', 'topRight', 'bottomLeft', 'bottomRight'].reduce((acc, key) => {
          acc[key] = !!all || !!directions[key];
          return acc;
        }, {});
        state.class = directions?.class ? state.class + ' ' + directions?.class : state?.class;
        state.dragWidth = isNumber(directions?.dragWidth) ? directions?.dragWidth : 3;
        el.style.position = 'relative';
        let isOnMove = false;
        state.mousemove_ = (e) => {
          if (!state.touch.init) return;
          state.moveX = e.pageX - state.touch.startX;
          state.moveY = e.pageY - state.touch.startY;
          if (onMove && typeof onMove === 'function' && !isOnMove) {
            onMove(el, e);
            isOnMove = true;
          }

          if (['right', 'left', 'topRight', 'bottomRight', 'topLeft', 'bottomLeft'].includes(state.touch.type)) {
            const newWidth = state.touch.width + (state.touch.type.includes('left') ? -state.moveX : state.moveX);
            el.style.width = `${newWidth}px`;
          }

          if (['bottom', 'top', 'topRight', 'bottomRight', 'topLeft', 'bottomLeft'].includes(state.touch.type)) {
            const newHeight = state.touch.height + (state.touch.type.includes('top') ? -state.moveY : state.moveY);
            el.style.height = `${newHeight}px`;
          }
        };

        state.mouseup_ = (e) => {
          document.body.style.cursor = 'default';
          if (!state.touch.init) return;
          if (onEnd && typeof onEnd === 'function') {
            onEnd(el, e);
          }
          isOnMove = false;
          state.touch.init = false;
          document.removeEventListener('mousemove', state.mousemove_);
          document.removeEventListener('mouseup', state.mouseup_);
        };

        const addHandle = (cursor, type, styles = {}, enabled) => {
          if (!enabled) return;
          const dom = document.createElement('DIV');
          Object.assign(dom.style, {
            position: 'absolute',
            zIndex: '10',
            cursor: cursor,
            userSelect: 'none',
            touchAction: 'none'
          });
          if (state.class) {
            const classNames = state.class.split(' ').filter((className) => className.trim());
            dom.classList.add(...classNames);
          }
          Object.assign(dom.style, styles);
          dom.addEventListener('mousedown', (e) => {
            const now = +new Date();
            if (now - state.touch.lastTime < state.touch.interval) {
              return (state.touch.init = false);
            }
            state.touch.init = true;
            state.touch.startX = e.pageX;
            state.touch.startY = e.pageY;

            const computedStyle = window.getComputedStyle(el);
            if (computedStyle.position === 'relative' || computedStyle.position === 'absolute') {
              if (computedStyle.left === 'auto' || computedStyle.left === '') {
                el.style.left = `${el.offsetLeft}px`;
              }
              if (computedStyle.top === 'auto' || computedStyle.top === '') {
                el.style.top = `${el.offsetTop}px`;
              }
            }
            document.body.style.cursor = cursor;
            const rect = el.getBoundingClientRect();
            Object.assign(state.touch, {
              width: rect.width,
              height: rect.height,
              left: parseFloat(el.style.left) || 0,
              top: parseFloat(el.style.top) || 0,
              type: type
            });

            document.addEventListener('mousemove', state.mousemove_, { passive: false });
            document.addEventListener('mouseup', state.mouseup_);
          });

          el.appendChild(dom);
        };
        if (onStart && typeof onStart === 'function') {
          onStart(el);
        }
        [
          ['ns-resize', 'top', { top: '0', left: '0', right: '0', height: state.dragWidth + 'px' }, state.allowedDirections.top],
          ['ns-resize', 'bottom', { bottom: '0', left: '0', right: '0', height: state.dragWidth + 'px' }, state.allowedDirections.bottom],
          ['ew-resize', 'left', { top: '0', bottom: '0', left: '0', width: state.dragWidth + 'px' }, state.allowedDirections.left],
          ['ew-resize', 'right', { top: '0', bottom: '0', right: '0', width: state.dragWidth + 'px' }, state.allowedDirections.right],
          ['nwse-resize', 'topLeft', { top: '0', left: '0', width: '10px', height: '10px' }, state.allowedDirections.topLeft],
          ['nesw-resize', 'topRight', { top: '0', right: '0', width: '10px', height: '10px' }, state.allowedDirections.topRight],
          ['nesw-resize', 'bottomLeft', { bottom: '0', left: '0', width: '10px', height: '10px' }, state.allowedDirections.bottomLeft],
          ['nwse-resize', 'bottomRight', { bottom: '0', right: '0', width: '10px', height: '10px' }, state.allowedDirections.bottomRight]
        ].forEach((item) => addHandle(...item));

        // 保存状态
        el.__dragState__ = state;
      },
      // Vue2 unbind
      unmounted(el) {
        const state = el.__dragState__;
        if (state) {
          document.removeEventListener('mousemove', state.mousemove_);
          document.removeEventListener('mouseup', state.mouseup_);
        }
        delete el.__dragState__;
      }
    });
  }
};
export default dragSize;

 

请登录后发表评论

    请登录后查看回复内容