react + 原生JS实现弹框拖拽解决方案

前言

项目github地址
最近项目需要实现一个弹框拖拽的功能,本来多快好省的方案就是使用antd的弹框,再配合gayhub上找的拖拽插件来实现。
但过程中遇到一些麻烦,而且用别的东西一时爽,要实现个性化或者改一些东西,需要看别人的源码,会极其的麻烦。
这时,固执的程序员就会抛下一切,全程自己开发,比如我。正所谓求人不如求己,自己动手,丰衣足食。
自己开发,时间可能会多花一些,但收益也是有目共睹的:
1.风险可控,出了问题能快速定位并解决(毕竟是亲手制作);
2.能最大程度实现个性化,比如增加或者减少一些功能;
3.杜绝知其然而不知其所以然。通过手把手搭建项目,实现功能,深入最底层理解逻辑,学习成长。有时还会有意外收获,学到项目之外的知识;
4.知道该功能有哪些坑,然后通过自己尝试或者观摩成熟解决方案来克服,避免以后遇到类似的坑;
5.自己做出来后,能与同事或者小组甚至上传到gayhub和所有开发者共享,为社区做出自己的贡献,收获成就感。久而久之,就成为该细分领域的专家,所有人有问题都来找你,那是何等的荣誉!
。。。


位置参数科普

各种内置位置参数

想开发拖拽功能首先得理解各种位置参数,如图,大概分为这几类:

event.clientX、event.clientY
鼠标相对于浏览器窗口可视区域的X,Y坐标(窗口坐标),可视区域不包括工具栏和滚动条。IE事件和标准事件都定义了这2个属性

event.pageX、event.pageY
类似于event.clientX、event.clientY,但它们使用的是文档坐标而非窗口坐标。这2个属性不是标准属性,但得到了广泛支持。IE事件中没有这2个属性。

event.offsetX、event.offsetY
鼠标相对于事件源元素(srcElement)的X,Y坐标,只有IE事件有这2个属性,标准事件没有对应的属性。

event.screenX、event.screenY
鼠标相对于用户显示器屏幕左上角的X,Y坐标。标准事件和IE事件都定义了这2个属性

clientWidth、clientHeight
返回元素的宽高(包括元素宽高、内边距,不包括边框和外边距)

offsetWidth、offsetHeight
返回元素的宽高(包括元素宽高、内边距和边框,不包括外边距)

功能介绍及思路

功能页面

基本功能很简单,就是点击弹框header部分按住进行拖动,弹框能跟着鼠标移动,松开则停止。左上角为鼠标坐标与弹框左上角坐标。
细心的读者可以发现,两个坐标有一个差值。恭喜你发现了彩蛋,这个差值就是实现拖拽功能的核心!
鼠标移动时我们能随时获取其位置,我们还能设置弹框左上角的位置,这两者是需要变化的值,同时,前者决定后者,后者跟随前者变化。
那么,二者存在怎样的关系?我们需要一个不变的值,正如数学公式中的常量或系数一样。
没错,就是前面提到的差值。我们发现,拖拽进行时,弹框左上角和鼠标的相对位置是不变的!
顺藤摸瓜,也就是说,只要获取鼠标点击header时,其相对弹框左上角的坐标差,以此为常量,来控制弹框位置就可以了!
另外还有一些优化,比如边界控制、位置修正等,详情见源码。

代码实现

Talk is cheap.Show me the code!

// 获取鼠标点击title时的坐标、title的坐标以及两者的位移
  getPosition (e) {
    // 标题DOM元素titleDom
    const titleDom = e.target
    // titleDom的坐标(视窗)
    const X = titleDom.getBoundingClientRect().left
    // 由于Y轴出现滚动条,需要与鼠标保持一致,存储页面相对位置
    const Y = document.getElementsByClassName('group')[0].offsetTop

    // 鼠标点击的坐标(页面)
    let mouseX = e.pageX
    let mouseY = e.screenY
    // 鼠标点击位置与modal的位移
    const diffX = mouseX - X
    const diffY = mouseY - Y
    return {X, Y, mouseX, mouseY, diffX, diffY}
  }
 
  /**
   * 鼠标按下,设置modal状态为可移动,并注册鼠标移动事件
   * 计算鼠标按下时,指针所在位置与modal位置以及两者的差值
   **/
  onMouseDown (e) {
    const position = this.getPosition(e)
    window.onmousemove = this.onMouseMove
    window.onmouseup = this.onMouseUp
    this.setState({moving: true, diffX: position.diffX, diffY: position.diffY})
  }
 
  // 松开鼠标,设置modal状态为不可移动
  onMouseUp (e) {
    const { moving } = this.state
    moving && this.setState({moving: false});
  }
 
  // 鼠标移动重新设置modal的位置
  onMouseMove (e) {
    const {moving, diffX, diffY} = this.state
    if (moving) {
      // 获取鼠标位置数据
      const position = this.getPosition(e)
      // 计算modal应该随鼠标移动到的坐标
      const x = position.mouseX - diffX
      const y = position.mouseY - diffY
      // 窗口大小,结构限制,需要做调整,减去侧边栏宽度
      const { clientWidth, clientHeight } = document.documentElement
      const modal = document.getElementsByClassName("group")[0]
      if (modal) {
        // 计算modal坐标的最大值
        const maxHeight = clientHeight - modal.offsetHeight
        const maxWidth = clientWidth - modal.offsetWidth
        // 判断得出modal的最终位置,不得超出浏览器可见窗口
        const left = x > 0 ? (x < maxWidth ? x : maxWidth) : 0
        const top = y > 0 ? (y < maxHeight ? y : maxHeight) : 0
        this.setState({pageX: left, pageY: top})
      }
    }
  }

原创不易,转载请注明出处。

你可能感兴趣的:(react + 原生JS实现弹框拖拽解决方案)