拖拽功能开发
更新时间: 2021-07-30 17:07:09
# 实现拖拽
实现拖拽的原理很简单:
- 鼠标在
domEl
上按下,拖拽开始,此时记录鼠标按下的位置 - 鼠标在
window
上移动时,计算当前的鼠标位置和按下时的初始位置,将坐标相减得到差值,将这个差值加到现有的top
和left
属性上
因此我绑定四个事件:
- 在
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
2
3
4
5
6
7
8
9
10
11
12
- 在
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
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
- 在
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
2
3
4
5
6
7
- 在
window
上绑定mouseup
事件,鼠标松开后也不能再拖拽
function onMouseup(e) {
this.isdragging = false
}
this.bindMouseUp = onMouseup.bind(this)
window.addEventListener("mouseup",this.bindMouseUp)
1
2
3
4
5
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
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
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
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
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.有两个参数attrName
和attrValue
,attrName
是要更改的属性名,attrValue
是属性值,使用方法类似下面:
const domEl = document.createElement('div')
const Ac = new accelerator(domEl)
Ac.attr('x','10%')
1
2
3
2
3
2.如果attrValue
没有传的话就返回这个属性绑定的值:
Ac.attr('x') //'10%'
1
3.如果attrName
传入的是一个Object
对象,就将这个对象的值更新到this.config
上,例如:
Ac.attr({
x:'20%',
y:'20%'
})
1
2
3
4
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
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
# 示例
最后写个例子验证一下,老规矩不放代码,看一下效果: