• Vue 拖拽功能 之 自定义指令实现元素拖拽功能


    拖拽功能主要操作的是真实的DOM元素以及鼠标事件,在vue中使用自定义指令最合适不过了。 当然使用组件封装起来,然后拖拽这个组件,也是可以实现相同的效果,不过我觉得这样有点大材小用,也缺乏灵活性,好处是可以传入更多的控制属性。总之,在组件与指令之间找到平衡点,自有取舍。

    言归正传,要想实现拖拽的效果 要么使用 drag 事件, 要么使用 mouse 鼠标事件,我这里选用的是 mouse 鼠标事件组合。

    拖拽元素的主要思路是:

    1. 鼠标按下时(mousedown 事件),记录下坐标,作为开始拖拽的起点
    2. 鼠标移动时(mousemove 事件),用移动后的坐标 与 开始拖拽的起点坐标,计算出移动的距离
    3. 用元素所在位置的坐标,加减移动的距离,则可以计算出元素移动后的位置坐标
    4. 既然已经知道了,元素移动后的位置坐标,那就可以为所欲为了
    5. 最后,鼠标松开时(mouseup 事件),移除监听事件,恢复到初始状态

    使用方法:

    1. 注册自定义指令 v-draggable, 全局注册使用或者局部注册使用

    全局注册

    // main.js
    import draggable from "./directives/draggable";
    Vue.directive("draggable", draggable);
    
    • 1
    • 2
    • 3

    局部注册

    // 需要使用的文件
    import draggable from "./directives/draggable";
    export default {
      data() {
        return {
          // ...
        };
      },
      directives:{ 
        directive
      }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    2. 简单应用

    元素可以随意拖拽,没有边界限制

    <div style="position: fixed;" v-draggable>div>
    
    • 1

    3. 简单应用

    元素可以随意拖拽,有边界限制, 无法拖动到屏幕之外, 关键在于 设置 【sticky】 修饰符

    <div style="position: fixed;" v-draggable.sticky>div>
    
    • 1

    4. 自定义应用

    拖拽的数据,可以通过 handleDraggable 返回,由你决定数据如何使用, 如何显示

    <div style="position: fixed;" v-draggable="handleDraggable">div>
    <script>
    export default {
      methods: {
        handleDraggable(config){
          console.log(config)
          if (config.dragging) {
            this.style = {
              left: `${config.x}px`,
              top: `${config.y}px`,
              width: `${config.rect.width}px`,
              height: `${config.rect.height}px`,
            };
          }     
        }
      }
    };
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    5. 如何区分点击事件

    当拖拽的元素,必须需要获取点击事件时,我们可以通过自定义应用的方式实现这一个目标;
    一般情况下,我们认为元素没有移动时,鼠标的一次按下与松开,为一次点击事件,故而可以如下处理

    handleDraggable(config){
      console.log(config)
      const {type, isMove} = config
      if (type === "mouseup" && !isMove) {
        // 这里为点击事件
        return;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    6. 返回的数据有哪些

    id: 唯一自增id
    binding, 自定义指令中的 binding
    vnode, 自定义指令中的 vnode
    target: 拖拽元素

    type: 触发的鼠标事件名称,用于区分不同阶段 mousedown、mousemove、mouseup
    rect: 返回getBoundingClientRect()获取拖拽元素的位置

    x: 拖拽元素距离屏幕左侧的距离
    y: 拖拽元素距离屏幕上方的距离

    dragstartX: 鼠标按下时坐标
    dragstartY:

    dragendX: 鼠标松开时坐标, 无值时为undefined
    dragendY:

    startX: 拖拽起点坐标
    startY:

    diffX: 拖拽元素当前移动端额狙击
    diffY:

    dragging: 元素是否可以拖拽
    isMove: 拖拽的元素是否有移动

    7. 自定义指令实现元素拖拽功能源码

    let seed = 0;
    const ctx = "@@draggableContext";
    function handleMousedown(event) {
      event.preventDefault();
      const el = this;
      const rect = el.getBoundingClientRect();
    
      Object.assign(el[ctx], {
        type: "mousedown",
        rect: rect,
        x: rect.x || rect.left,
        y: rect.y || rect.top,
        dragstartX: event.clientX, // 鼠标按下时坐标
        dragstartY: event.clientY,
        dragendX: void 0, // 鼠标松开时坐标
        dragendY: void 0,
        startX: event.clientX, // 起点坐标
        startY: event.clientY,
        dragging: true,
        isMove: false,
      });
    
      callback(el);
    
      window.addEventListener("mousemove", el[ctx]._handleMousemove, false);
      window.addEventListener("mouseup", el[ctx]._handleMouseup, false);
    }
    
    function handleMousemove(el) {
      return function(event) {
        event.preventDefault();
    
        if (event.target === document.documentElement) return;
    
        const current = {
          x: event.clientX,
          y: event.clientY,
        };
    
        const diff = {
          x: current.x - el[ctx].startX,
          y: current.y - el[ctx].startY,
        };
    
        if (el[ctx].binding.modifiers.sticky) {
          // 不会拖出屏幕边缘
          const clientWidth = document.documentElement.clientWidth;
          const clientHeight = document.documentElement.clientHeight;
    
          const {
            x,
            y,
            rect: { width, height },
          } = el[ctx];
    
          if (diff.x < 0 && x + diff.x <= 0) {
            el[ctx].x = 0;
          } else if (diff.x > 0 && x + width - clientWidth >= 0) {
            el[ctx].x = clientWidth - width;
          } else {
            el[ctx].x += diff.x;
          }
    
          if (diff.y < 0 && y + diff.y <= 0) {
            el[ctx].y = 0;
          } else if (diff.y > 0 && y + height - clientHeight >= 0) {
            el[ctx].y = clientHeight - height;
          } else {
            el[ctx].y += diff.y;
          }
        } else {
          el[ctx].x += diff.x;
          el[ctx].y += diff.y;
        }
    
        Object.assign(el[ctx], {
          type: "mousemove",
          startX: current.x,
          startY: current.y,
          diffX: diff.x,
          diffY: diff.y,
          isMove: true,
        });
    
        callback(el);
      };
    }
    function handleMouseup(el) {
      return function(event) {
        event.preventDefault();
    
        const lastType = el[ctx].type;
    
        Object.assign(el[ctx], {
          type: "mouseup",
          dragendX: event.clientX, // 鼠标按下时坐标
          dragendY: event.clientY,
          dragging: false,
          isMove: lastType === "mousemove",
        });
    
        callback(el);
    
        window.removeEventListener("mousemove", el[ctx]._handleMousemove, false);
        window.removeEventListener("mouseup", el[ctx]._handleMouseup, false);
      };
    }
    
    function callback(el) {
      const bindingFn = el[ctx]?.binding?.value;
      if (typeof bindingFn === "function") {
        bindingFn({ ...el[ctx], target: el });
      } else {
        const { x, y, rect, dragging } = el[ctx];
        if (!dragging) return;
        el.style.cssText = `
          left: ${x}px;
          top: ${y}px;
          width: ${rect.width}px;
          height: ${rect.height}px;
        `;
      }
    }
    /**
     * v-draggable
     * @desc
     * @example
     * ```vue
     * 
    * *
    *
    * ``` */ export default { bind(el, binding, vnode) { const id = seed++; el[ctx] = { id, binding, vnode, _handleMousemove: handleMousemove(el, binding, vnode), _handleMouseup: handleMouseup(el, binding, vnode), }; el.addEventListener("mousedown", handleMousedown, false); }, unbind(el) { window.removeEventListener("mousemove", el[ctx]._handleMousemove, false); window.removeEventListener("mouseup", el[ctx]._handleMouseup, false); el.removeEventListener("mousedown", handleMousedown, false); delete el[ctx]; }, };
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
  • 相关阅读:
    如何进行数据库备份
    数据结构“入门”—堆的实现
    【前端设计模式】之组合模式
    用Notepad++写java代码
    还在为sql注入眼花缭乱的过滤而烦恼?一文教您快速找出所有过滤内容
    c++ vector的模拟实现以及迭代器失效问题
    ctfshow sql171-179
    现代计算与光学的跨界机遇——
    第一季:8spring支持的常用数据库事务传播属性和事务隔离级别【Java面试题】
    【【萌新的FPGA学习之Vivado下的仿真入门-2】】
  • 原文地址:https://blog.csdn.net/shijue98/article/details/126022408