拖拽功能开发

更新时间: 2021-07-30 17:07:09

# 实现拖拽

实现拖拽的原理很简单:

  1. 鼠标在domEl上按下,拖拽开始,此时记录鼠标按下的位置
  2. 鼠标在window上移动时,计算当前的鼠标位置和按下时的初始位置,将坐标相减得到差值,将这个差值加到现有的 topleft 属性上

因此我绑定四个事件:

  1. domEl上绑定mousedown事件,用于记录当鼠标点下的位置,和将代表拖拽中的isdragging置为true:
function onMousedown(e) {
    this.dragOrign.x = e.pageX
    this.dragOrign.y = e.pageY
    this.isdragging = true
}

//为了onMousedown方法可以使用this而使用了bind
this.bindMousedown = onMousedown.bind(this)

//之所以这样写是为了方便注销这个事件
this.domEl.addEventListener("mousedown",this.bindMousedown)

1
2
3
4
5
6
7
8
9
10
11
12
  1. window上绑定mousemove事件,用于更新domEl的位置和this.config的配置,之所以不绑定在domEl上是因为在移动鼠标的时候,经常会不小心将鼠标移出当前正在拖动的元素,体验不是很好
function onMousemove(e) {
    if(this.isdragging){
        const moveX = e.pageX - this.dragOrign.x
        const moveY = e.pageY - this.dragOrign.y
        let newX = this.x.num + moveX
        let newY = this.y.num + moveY
        //domEl是否可以拖拽出父元素
        if(!this.dragOutable){
            const maxX = this.parentElWidth - this.width.num
            const maxY = this.parentElHeight - this.height.num
            const minX = 0
            const minY = 0
            if(newX < minX) {
                newX = minX
            }
            if(newX > maxX) {
                newX = maxX
            }
            if(newY < minY) {
                newY = minY
            }
            if(newY > maxY) {
                newY = maxY
            }
        }
        this.x.num = newX
        this.y.num = newY
        this.dragOrign.x = e.pageX
        this.dragOrign.y = e.pageY
        this._setStyle()
        this._updatePositionConfig()
    }
}

/**
* 更新config的width和height,x,y参数
*/
this._updatePositionConfig = function() {
    this.config.x = getSizeText(this.x,this.parentElWidth)
    this.config.y = getSizeText(this.y,this.parentElHeight)
    this.config.width = getSizeText(this.width,this.parentElWidth)
    this.config.height = getSizeText(this.height,this.parentElHeight)
}
/**
* 设置元素样式
*/
this._setStyle = function() {
    this.domEl.style.position = 'absolute'
    this.domEl.style.left = this.x.num + 'px'
    this.domEl.style.top = this.y.num + 'px'
    this.domEl.style.width = this.width.num + 'px'
    this.domEl.style.height = this.height.num + 'px'
}

this.bindMouseMove = onMousemove.bind(this)
window.addEventListener("mousemove",this.bindMouseMove)
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
  1. window上绑定mouseleave事件,移出window之后就不能再拖拽
function onMouseleave(e) {
    if(this.isdragging) {
        this.isdragging = false
    }
}
this.bindMouseLeave = onMouseleave.bind(this)
window.addEventListener("mouseleave",this.bindMouseLeave,false)
1
2
3
4
5
6
7
  1. window上绑定mouseup事件,鼠标松开后也不能再拖拽
function onMouseup(e) {
    this.isdragging = false
}
this.bindMouseUp = onMouseup.bind(this)
window.addEventListener("mouseup",this.bindMouseUp)
1
2
3
4
5

销毁整个实例时需要注销掉这几个事件:











 





function removeDragMethods() {
    this.domEl.removeEventListener("mousedown",this.bindMousedown)
    window.removeEventListener("mousemove",this.bindMouseMove)
    window.removeEventListener("onMouseleave",this.bindMouseLeave)
    window.removeEventListener("mouseup",this.bindMouseUp)
}

//Accelerator的destroy方法做一下修改
destroy() {
    clearInterval(this.watchParentInterval)
    removeDragMethods(this)
    this.watchParentInterval = null
    const index = Accelerator._instanceList.findIndex((instance) => { return this.id === instance.id })
    Accelerator._instanceList.splice(index,1)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 代码分割

拖拽的几个事件一加代码可读性立马就变差了,由于我需要的仅仅只是两个操作,注册拖拽相关事件,和注销拖拽相关事件,所以我可以将拖拽的方法都抽出去,仅仅暴露两个注册和注销的方法出来: 创建src/accelerator/drag.js:

export function onMousedown(e) {
    //...
}

export function onMousemove(e) {
    //...
}

export function onMouseleave(e) {
    //...
}

export function onMouseup(e) {
    //...
}

export function setDragMethods(_this) {
    if(_this.dragable){
        _this.bindMousedown = onMousedown.bind(_this)
        _this.domEl.addEventListener("mousedown",_this.bindMousedown)
        _this.bindMouseMove = onMousemove.bind(_this)
        window.addEventListener("mousemove",_this.bindMouseMove)
        _this.bindMouseLeave = onMouseleave.bind(_this)
        window.addEventListener("mouseleave",_this.bindMouseLeave,false)
        _this.bindMouseUp = onMouseup.bind(_this)
        window.addEventListener("mouseup",_this.bindMouseUp)
    }
}

export function removeDragMethods(_this) {
    _this.domEl.removeEventListener("mousedown",_this.bindMousedown)
    window.removeEventListener("mousemove",_this.bindMouseMove)
    window.removeEventListener("onMouseleave",_this.bindMouseLeave)
    window.removeEventListener("mouseup",_this.bindMouseUp)
}
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

定义在Accelerator上的静态属性和方法也有点碍眼,新建src/accelerator/registerStatic.js

export function registerStaticMethod (Accelerator) {
    Accelerator.ID = 1
    Accelerator.x = 0;
    Accelerator.y = 0;
    Accelerator.width = '100px'
    Accelerator.height = '100px'
    Accelerator.autoCount = false
    Accelerator.dragable = true
    Accelerator.dragOutable = true
    Accelerator._instanceList = []

    /**
     * 
     * @param {*} config 设置Accelerator的静态属性
     */
    Accelerator.setStaticConfig = function (config){
        Accelerator.x = config.x || Accelerator.x
        Accelerator.y = config.y || Accelerator.y
        Accelerator.width = config.width || Accelerator.width
        Accelerator.height = config.height || Accelerator.height
        Accelerator.autoCount = config.autoCount || Accelerator.autoCount
        Accelerator.dragable = config.dragable || Accelerator.dragable
        Accelerator.dragOutable = config.dragOutable || Accelerator.dragOutable
    }

    /**
     * 销毁所有Accelerator实例
     */
    Accelerator.destroyAll = function() {
        for(let i = 0;i < Accelerator._instanceList.length; i++){
            const instance = Accelerator._instanceList[i]
            instance.destroy()
            i--
        }
    }
}
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

然后在src/accelerator/index.js中调用:

 
 
 
 
 

















 




 



 




import { registerStaticMethod } from './registerStatic'
import {
    setDragMethods,
    removeDragMethods
} from './drag'

class Accelerator {
    /**
     * 
     * @param {*} domEl dom元素,必传
     * @param {*} config  配置项
     */
    constructor(domEl,config = {}){
        //...
        this._init()
    }
    /**
     * 初始化元素的大小和位置,并且刷新Accelerator上的静态参数
     */
    _init(){
        //...
        //设置拖拽
        setDragMethods(this)
    }
    //...
    destroy() {
       //...
        removeDragMethods(this)
    }
}

registerStaticMethod(Accelerator)

window.Accelerator = Accelerator
export default Accelerator
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

# attr()方法更改属性值

一般实例化传入的配置都是可以在后面改动的,改配置的同时页面上渲染的也应该实时改变。

先设计一下attr()方法: 1.有两个参数attrNameattrValue,attrName是要更改的属性名,attrValue是属性值,使用方法类似下面:

const domEl = document.createElement('div')
const Ac = new accelerator(domEl)
Ac.attr('x','10%')
1
2
3

2.如果attrValue没有传的话就返回这个属性绑定的值:

Ac.attr('x') //'10%'
1

3.如果attrName传入的是一个Object对象,就将这个对象的值更新到this.config上,例如:

Ac.attr({
    x:'20%',
    y:'20%'
})
1
2
3
4

逻辑整理完毕,代码如下:

/**
* 
* @param { string | object } } attrName 属性名 或 object类型的属性及属性值
* @param {*} attrValue 属性值
*/
attr(attrName, attrValue = ''){
    //先判断attrName的类型
    const type = typeof(attrName)
    const orignDragable = this.dragable
    if(type === 'string'){
        //字符串的话就验证第二个attrValue的值
        if(attrValue || attrValue === false) {
            //不为空就重新设置一下这个值
            if(attrName!='id') {
                this.config[attrName] = attrValue
            }
        } else {
            //attrValue为空就返回attrName这个参数的值
            return this.config[attrName]
        }
    }
    else if(type === 'object'){
        //attrName为Object
        //设置this.config
        //不允许改变id
        if(attrName.id) {
            delete attrName.id
        }
        this.config = {
            ...this.config,...attrName
        }
    }

    //重新计算参数值
    this._computedConfig(this.config)
    //重新设置位置
    this._setStyle()
    //其他控制方面的参数变化
    if(this.dragable != orignDragable) {
        if(this.dragable){
            setDragMethods(this)
        }else{
            removeDragMethods(this)
        }
    }
}
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

# 示例

最后写个例子验证一下,老规矩不放代码,看一下效果: