代码之家  ›  专栏  ›  技术社区  ›  Ivan Jeffrey Zhao

最小化事件侦听器的数量以实现“可拖动”行为

  •  0
  • Ivan Jeffrey Zhao  · 技术社区  · 6 年前

    目标

    试图复制jQueryUI的 .draggable 行为。

    $(function() {
      $(".box").draggable();
    });
    .box {
      width: 100px;
      height: 100px;
      background: pink;
    }
    <head>
      <script src="https://code.jquery.com/jquery-1.12.4.js"></script>
      <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
    </head>
    
    <body>
      <div class="box"></div>
    </body>

    A 工作溶液

    我已经创建了一个函数 draggable HTMLElement :需要可拖动的元素。函数需要遵循以下两点:

    • 可以对多个元素调用该函数,这样就可以拖动页面上的多个元素(一次只能拖动一个元素,就像jQueryUI那样)

    • 可以将可拖动的元素拖动到用户希望它在页面上移动的任何位置(更确切地说是在父容器中)。

    我已经开始编写函数,它的工作原理如下:

    1. 它增加了一个 onmousedown

      当事件触发时,距离(开 x y )从鼠标位置到被拖动元素的一角保存在里面 relativeX relativeY dragging 已从更改 false true .

    2. 它增加了一个 onmousemove 打开事件侦听器 document

      拖动 是的 . 当国家真的 ,表示用户正在拖动元素。因此需要更新元素的位置。我使用绝对定位来得到这些位置,考虑到相对位置( 相对论 已提前保存)。

    3. 两者兼而有之 onmouseleave onmouseup 文件 在需要时终止行为:

      当用户停止拖动元素时( 鼠标左键 此函数用于更改 拖动 鼠标移动时 听者会继续听,但直到 拖动 设置为 是的 再一次。

    const relativePos = (e, p) => {
      let rect = e.target.getBoundingClientRect()
      return [e.clientX - rect.x, e.clientY - rect.y];
    }
    
    const draggable = draggedE => {
    
      let isDragging, relativeX, relativeY;
    
      // checking if the user is dragging
      document.onmousemove = (event) => {
        if (isDragging) {
          draggedE.style.left = `${event.clientX - relativeX}px`
          draggedE.style.top = `${event.clientY - relativeY}px`
        }
      }
    
      // when the user initiate the dragging behaviour
      draggedE.onmousedown = e => {
        [isDragging, relativeX, relativeY] = [true, ...relativePos(e)]
      }
    
      // listener to end the behaviour 
      document.onmouseup = () => isDragging = false
      document.onmouseleave = () => isDragging = false
      
    }
    
    draggable(document.querySelector('.box'))
    .box {
      width: 100px;
      height: 100px;
      background: pink;
      position: absolute;
    }
    <div class="box"></div>

    事件侦听器的问题

    代码运行得很好。 :

    我使用了很多可拖动的元素。对于它们中的每一个,都有一个附加到文档的事件来检查其状态 拖动 . 这似乎对性能不太好。

    如果你仔细看下面的动画(使用jQueryUI的 .可拖动 方法):

    enter image description here

    文件 body 不起作用。


    我的问题

    )在保证同样行为的前提下?


    Teemu 建议:

    • 我创建了一个对象 拖动 包含所有方法

    • mousedown 上的事件处理程序 文件

    const draggable = {
      startDrag: function(event) {
        this.element = event.target
        const rect = event.target.getBoundingClientRect();
        this.offset = [event.clientX - rect.x, event.clientY - rect.y];
      },
      dragging: function(e) {
        this.element.style.left = `${e.clientX - this.offset[0]}px`;
        this.element.style.top = `${e.clientY - this.offset[1]}px`;
      },
      endDrag: function() {},
      element: '',
      offset: []
    };
    
    document.addEventListener('mousedown', e => {
    
      if (e.target.classList.contains('draggable')) {
    
        draggable.startDrag(e)
    
        const _mousemove = draggable.dragging.bind(draggable)
        document.addEventListener('mousemove', _mousemove);
    
        document.addEventListener('mouseup', function _mouseup(event) {
          document.removeEventListener('mousemove', _mousemove)
          document.removeEventListener('mouseup', _mouseup)
        });
      }
    });
    .box {
      width: 100px;
      height: 100px;
      background: pink;
      position: absolute;
      user-select: none;
    }
    
    .box:nth-child(1) {
      background: lightblue;
      top: 125px;
      left: 125px;
    }
    
    .box:nth-child(2) {
      background: lightgreen;
      top: 250px;
      left: 250px;
    }
    <div class="draggable box"></div>
    <div class="draggable box"></div>
    <div class="draggable box"></div>

    必须命名处理程序使用的函数,以便以后删除它们,这让我很烦恼。 没有其他方法可以删除事件而不必调用吗 bind ?

    1 回复  |  直到 6 年前
        1
  •  0
  •   Ivan Jeffrey Zhao    6 年前

    感谢 Teemu 我找到了解决我问题的办法。其思想是在用户不拖动时运行一个唯一的事件侦听器。当他拖动一个元素时,会添加其他事件侦听器 并在用户结束拖动过程后立即删除 mouseup mouseleave

    draggable startDrag , dragging endDrag 和信息: element offset 关于拖动机制。

    为了删除事件侦听器,我需要将事件函数保存在变量中。

    bind() 为了得到 的属性。所以我想到了财产 events 充满了 拖动 拖动

    所以我想到:

    (function() {
        this.events = {
          _mousemove: this.dragging.bind(this),
          _mouseup: this.endDrag.bind(this)
        }
    }.bind(draggable))()
    

    const _mousemove = draggable.dragging.bind(draggable)
    const _mouseup = draggable.endDrag.bind(draggable)
    

    所以现在我可以停止与 _mousemove _mouseup 里面 最终位置 通过使用 this.events._mousemove this.events._mouseup .

    现在可以添加事件侦听器:

    document.addEventListener('mousemove', draggable.events._mousemove);
    
    document.addEventListener('mouseup', draggable.events._mouseup);
    

    以下是完整的JavaScript代码:

    const draggable = {
      events: {},
      startDrag: function(event) {
        this.element = event.target
        const rect = event.target.getBoundingClientRect();
        this.offset = [event.clientX - rect.x, event.clientY - rect.y];
      },
      dragging: function(e) {
        this.element.style.left = `${e.clientX - this.offset[0]}px`;
        this.element.style.top = `${e.clientY - this.offset[1]}px`;
      },
      endDrag: function() {
        document.removeEventListener('mousemove', this.events._mousemove)
        document.removeEventListener('mouseup', this.events._mouseup)
      },
      element: '',
      offset: []
    };
    
    document.addEventListener('mousedown', e => {
    
      (function() {
        this.events = {
          _mousemove: this.dragging.bind(this),
          _mouseup: this.endDrag.bind(this)
        }
      }.bind(draggable))()
    
    
      if (e.target.classList.contains('draggable')) {
    
        draggable.startDrag(e)
    
        document.addEventListener('mousemove', draggable.events._mousemove);
    
        document.addEventListener('mouseup', draggable.events._mouseup);
    
      }
    });
    .box {
      width: 100px;
      height: 100px;
      background: pink;
      position: absolute;
      user-select: none;
    }
    
    .box:nth-child(1) {
      background: lightblue;
      top: 125px;
      left: 125px;
    }
    
    .box:nth-child(2) {
      background: lightgreen;
      top: 250px;
      left: 250px;
    }
    <div class="draggable box"></div>
    <div class="draggable box"></div>
    <div class="draggable box"></div>