金沙网址原文出处

后面一个底子升级(10卡塔 尔(阿拉伯语:قطر‎:面向对象实战之封装拖拽对象

2017/04/02 · JavaScript
·
面向对象

初稿出处: 波同学   

金沙网址 1

终于

前面几篇小说,作者跟大家大吃大喝了JavaScript的有个别底工知识,那篇文章,将会进去第二个实战环节:利用前边几章的所关联到的知识,封装五个拖拽对象。为了能够扶植我们探听越来越多的秘诀与拓宽相比较,小编会使用三种差异的措施来兑现拖拽。

  • 不封装对象直接落成;
  • 采纳原生JavaScript封装拖拽对象;
  • 由此扩大jQuery来兑现拖拽对象。

本文的事例会停放于codepen.io中,供我们在翻阅时一向查看。借使对于codepen不打听的同学,能够花点时间稍稍理解一下。

拖拽的兑现过程会波及到足够多的实用小知识,因而为了加固本人要好的知识积存,也为了大家能够学到更加的多的学识,小编会尽量详细的将一些细节分享出来,相信咱们认真读书之后,一定能学到一些事物。

1、怎么样让一个DOM元素动起来

大家平常会经过更正成分的top,left,translate来其的岗位发生转移。在底下的例证中,每点击叁回按键,对应的因素就可以移动5px。大家可点击查阅。

点击查看叁个让成分动起来的小例子

由于校正三个因素top/left值会挑起页面重绘,而translate不会,因而从质量优化上来剖断,我们会预先使用translate属性。

2、如何收获当前浏览器协理的transform包容写法

transform是css3的性子,当大家应用它时就只可以面临包容性的难题。分歧版本浏览器的相称写法大致有如下二种:

['transform', 'webkitTransform', 'MozTransform', 'msTransform', 'OTransform']

之所以大家必要剖断当前浏览器蒙受援助的transform属性是哪后生可畏种,方法如下:

JavaScript

// 获取当前浏览器扶助的transform包容写法 function getTransform() { var
transform = ”, divStyle = document.createElement(‘div’).style, //
大概波及到的两种宽容性写法,通过巡回寻找浏览器度和胆识别的那多少个 transformArr
= [‘transform’, ‘webkitTransform’, ‘MozTransform’, ‘msTransform’,
‘OTransform’], i = 0, len = transformArr.length; for(; i < len; i++)
{ if(transformArr[i] in divStyle) { // 找到之后立刻赶回,停止函数
return transform = transformArr[i]; } } //
若无找到,就径直回到空字符串 return transform; }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 获取当前浏览器支持的transform兼容写法
function getTransform() {
    var transform = ”,
        divStyle = document.createElement(‘div’).style,
        // 可能涉及到的几种兼容性写法,通过循环找出浏览器识别的那一个
        transformArr = [‘transform’, ‘webkitTransform’, ‘MozTransform’, ‘msTransform’, ‘OTransform’],
 
        i = 0,
        len = transformArr.length;
 
    for(; i < len; i++)  {
        if(transformArr[i] in divStyle) {
            // 找到之后立即返回,结束函数
            return transform = transformArr[i];
        }
    }
 
    // 如果没有找到,就直接返回空字符串
    return transform;
}

该方法用于获取浏览器扶植的transform属性。要是回到的为空字符串,则代表前段时间浏览器并不扶植transform,此时大家就必要使用left,top值来改形成分的岗位。假设帮助,就改成transform的值。

3、 如何拿到元素的始发地点

我们第黄金时代须求获得到对象成分的最早地方,因而这里大家须要一个极其用来获取成分样式的法力函数。

但是获取成分样式在IE浏览器与任何浏览器有部分莫衷一是,因而我们要求四个包容性的写法。

JavaScript

function getStyle(elem, property) { //
ie通过currentStyle来拿到成分的样式,其余浏览器通过getComputedStyle来得到return document.defaultView.getComputedStyle ?
document.defaultView.getComputedStyle(elem, false)[property] :
elem.currentStyle[property]; }

1
2
3
4
function getStyle(elem, property) {
    // ie通过currentStyle来获取元素的样式,其他浏览器通过getComputedStyle来获取
    return document.defaultView.getComputedStyle ? document.defaultView.getComputedStyle(elem, false)[property] : elem.currentStyle[property];
}

有了这些艺术之后,就足以起来动手写获取指标元素起先地点的点子了。

JavaScript

function getTargetPos(elem) { var pos = {x: 0, y: 0}; var transform =
getTransform(); if(transform) { var transformValue = getStyle(elem,
transform); if(transformValue == ‘none’) { elem.style[transform] =
‘translate(0, 0)’; return pos; } else { var temp =
transformValue.match(/-?\d+/g); return pos = { x:
parseInt(temp[4].trim()), y: parseInt(temp[5].trim()) } } } else {
if(getStyle(elem, ‘position’) == ‘static’) { elem.style.position =
‘relative’; return pos; } else { var x = parseInt(getStyle(elem, ‘left’)
? getStyle(elem, ‘left’) : 0); var y = parseInt(getStyle(elem, ‘top’) ?
getStyle(elem, ‘top’) : 0); return pos = { x: x, y: y } } } }

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
function getTargetPos(elem) {
    var pos = {x: 0, y: 0};
    var transform = getTransform();
    if(transform) {
        var transformValue = getStyle(elem, transform);
        if(transformValue == ‘none’) {
            elem.style[transform] = ‘translate(0, 0)’;
            return pos;
        } else {
            var temp = transformValue.match(/-?\d+/g);
            return pos = {
                x: parseInt(temp[4].trim()),
                y: parseInt(temp[5].trim())
            }
        }
    } else {
        if(getStyle(elem, ‘position’) == ‘static’) {
            elem.style.position = ‘relative’;
            return pos;
        } else {
            var x = parseInt(getStyle(elem, ‘left’) ? getStyle(elem, ‘left’) : 0);
            var y = parseInt(getStyle(elem, ‘top’) ? getStyle(elem, ‘top’) : 0);
            return pos = {
                x: x,
                y: y
            }
        }
    }
}

在拖拽进度中,我们要求不停的装置目的成分的新岗位,那样它才会活动起来,由此大家要求三个装置指标成分地点的点子。

JavaScript

// pos = { x: 200, y: 100 } function setTargetPos(elem, pos) { var
transform = getTransform(); if(transform) { elem.style[transform] =
‘translate(‘+ pos.x +’px, ‘+ pos.y +’px)’; } else { elem.style.left =
pos.x + ‘px’; elem.style.top = pos.y + ‘px’; } return elem; }

1
2
3
4
5
6
7
8
9
10
11
// pos = { x: 200, y: 100 }
function setTargetPos(elem, pos) {
    var transform = getTransform();
    if(transform) {
        elem.style[transform] = ‘translate(‘+ pos.x +’px, ‘+ pos.y +’px)’;
    } else {
        elem.style.left = pos.x + ‘px’;
        elem.style.top = pos.y + ‘px’;
    }
    return elem;
}
5、大家供给用到怎么事件?

在pc上的浏览器中,结合mousedown、mousemove、mouseup那多个事件能够帮忙大家落实拖拽。

  • mousedown 鼠标按下时接触
  • mousemove 鼠标按下后拖动时接触
  • mouseup 鼠标松手时触发

而在移动端,分别与之对应的则是touchstart、touchmove、touchend

当大家将成分绑定那一个事件时,有一个事件指标将会作为参数字传送递给回调函数,通过事件指标,大家得以博得到当下鼠标的正确地点,鼠标地方新闻是贯彻拖拽的第后生可畏。

事件目的特别首要,个中包涵了要命多的有效性的消息,这里自个儿就不扩大了,大家能够在函数准将事件目的打字与印刷出来查看里面包车型客车现实性性质,这一个主意对于记不清事件指标首要性质的童鞋相当平价。

6、拖拽的规律

当事件触发时,大家可以通过事件指标得到到鼠标的精切地点。那是完毕拖拽的主要。当鼠标按下(mousedown触发)时,大家要求记住鼠标的开始地点与目的成分的起来地方,大家的靶子正是得以完毕当鼠标移动时,指标成分也随后移动,依照原理我们得以吸取如下事关:

移动后的鼠标地点 – 鼠标伊始地点 = 移动后的靶子成分地点 –
目的成分的启幕地方

1
移动后的鼠标位置 – 鼠标初始位置 = 移动后的目标元素位置 – 目标元素的初始位置

假设鼠标地点的差值大家用dis来表示,那么目的成分之处就等于:

活动后目的成分的岗位 = dis + 目的成分的始发地方

1
移动后目标元素的位置 = dis + 目标元素的初始位置

透过事件目的,大家得以规范的知晓鼠标的一时一刻地点,因而当鼠标拖动(mousemove)时,大家得以不停的计量出鼠标移动的差值,以此来求出目的成分的当下地点。那个进度,就完结了拖拽。

而在鼠标松手(mouseup)结束拖拽时,大家供给管理部分收场专门的学业。实际情况见代码。

7、 作者又来推荐思维导图匡助写代码了

常常有新人朋友跑来问我,假使逻辑思维技巧不强,能还是不可能写代码做前端。笔者的答案是:能。因为依赖思维导图,能够很自在的弥补逻辑的短板。何况比在和谐头脑中脑补逻辑更是清晰明了,不易出错。

地方第六点作者介绍了规律,因而怎么着做就展现不是那么难了,而现实的步子,则在底下的想一想导图中鲜明给出,大家只须求依据那些手续来写代码就可以,试试看,一定相当的轻巧。

金沙网址 2

运用思维导图清晰的表明出总体拖拽进度我们须求干的事情

8、代码达成

part1、筹划干活

JavaScript

// 获取目的成分对象 var oElem = document.getElementById(‘target’); //
证明2个变量用来保存鼠标开首地方的x,y坐标 var startX = 0; var startY =
0; // 申明2个变量用来保存目的成分初阶地点的x,y坐标 var sourceX = 0; var
sourceY = 0;

1
2
3
4
5
6
7
8
9
10
// 获取目标元素对象
var oElem = document.getElementById(‘target’);
 
// 声明2个变量用来保存鼠标初始位置的x,y坐标
var startX = 0;
var startY = 0;
 
// 声明2个变量用来保存目标元素初始位置的x,y坐标
var sourceX = 0;
var sourceY = 0;

part2、作用函数

因为前边已经贴过代码,就不再重复

JavaScript

// 获取当前浏览器协助的transform包容写法 function getTransform() {} //
获取元素属性 function getStyle(elem, property) {} // 获取元素的上马地方function getTargetPos(elem) {} // 设置元素的初叶地点 function
setTargetPos(elem, potions) {}

1
2
3
4
5
6
7
8
9
10
11
// 获取当前浏览器支持的transform兼容写法
function getTransform() {}
 
// 获取元素属性
function getStyle(elem, property) {}
 
// 获取元素的初始位置
function getTargetPos(elem) {}
 
// 设置元素的初始位置
function setTargetPos(elem, potions) {}

part3、注解多少个事件的回调函数

那八个主意正是兑现拖拽的骨干所在,小编将严苛依据上边思维导图中的步骤来成功大家的代码。

JavaScript

// 绑定在mousedown上的回调,event为流传的风云目标 function start(event)
{ // 获取鼠标开头地点 startX = event.pageX; startY = event.pageY; //
获取成分开头地方 var pos = getTargetPos(oElem); sourceX = pos.x; sourceY
= pos.y; // 绑定 document.add伊夫ntListener(‘mousemove’, move, false);
document.add伊夫ntListener(‘mouseup’, end, false); } function move(event)
{ // 获取鼠标当前地方 var currentX = event.pageX; var currentY =
event.pageY; // 总计差值 var distanceX = currentX – startX; var
distanceY = currentY – startY; // 总括并安装成分当前地点setTargetPos(oElem, { x: (sourceX + distanceX).toFixed(), y: (sourceY +
distanceY).toFixed() }) } function end(event) {
document.remove伊夫ntListener(‘mousemove’, move);
document.removeEventListener(‘mouseup’, end); // do other things }

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
// 绑定在mousedown上的回调,event为传入的事件对象
function start(event) {
    // 获取鼠标初始位置
    startX = event.pageX;
    startY = event.pageY;
 
    // 获取元素初始位置
    var pos = getTargetPos(oElem);
 
    sourceX = pos.x;
    sourceY = pos.y;
 
    // 绑定
    document.addEventListener(‘mousemove’, move, false);
    document.addEventListener(‘mouseup’, end, false);
}
 
function move(event) {
    // 获取鼠标当前位置
    var currentX = event.pageX;
    var currentY = event.pageY;
 
    // 计算差值
    var distanceX = currentX – startX;
    var distanceY = currentY – startY;
 
    // 计算并设置元素当前位置
    setTargetPos(oElem, {
        x: (sourceX + distanceX).toFixed(),
        y: (sourceY + distanceY).toFixed()
    })
}
 
function end(event) {
    document.removeEventListener(‘mousemove’, move);
    document.removeEventListener(‘mouseup’, end);
    // do other things
}

OK,叁个简便的拖拽,就这么欢畅的兑现了。点击下边包车型的士链接,可以在线查看该例子的demo。

采纳原生js实现拖拽

9、封装拖拽对象

在近日大器晚成章作者给大家大饱眼福了面向对象如何落实,基于那多少个基本功知识,我们来将地方落成的拖拽封装为一个拖拽对象。我们的目的是,只要大家声惠氏个拖拽实例,那么传入的对象成分将活动具有能够被拖拽的效果。

在骨子里开采中,三个目的大家平常会独自放在两个js文件中,这些js文件将独立作为一个模块,利用种种模块的点子组织起来使用。当然这里没有复杂的模块人机联作,因为那些事例,大家只要求三个模块就可以。

为了制止变量污染,大家须求将模块放置于三个函数自进行措施模拟的块级效能域中。

JavaScript

; (function() { … })();

1
2
3
4
;
(function() {
    …
})();

在普通的模块组织中,大家只是风姿罗曼蒂克味的将广大js文件收缩成为多个js文件,因而这里的率先个子公司则是为了防止万黄金时代上多个模块的结尾不用分号招致报错。必不可缺。当然在经过require只怕ES6模块等措施就不会冒出那样的场所。

咱俩精通,在包装三个对象的时候,大家得以将质量与办法放置于构造函数恐怕原型中,而在加码了自施行函数之后,大家又能够将质量和办法幸免与模块的里边作用域。那是闭包的学识。

那么大家面没错挑衅就在于,如何合理的管理属性与形式的职责。

自然,每贰个对象的意况都不均等,不能够比量齐观,大家供给鲜明的接头这三种职位的特色技艺做出最适合的支配。

  • 构造函数中:
    属性与措施为眼下实例单独具有,只好被日前实例访谈,何况每声贝因美个实例,此中的点子都会被再度创建叁遍。
  • 原型中:
    属性与艺术为富有实例协同全数,能够被抱有实例访谈,新证明实例不会再一次创制方法。
  • 模块功能域中:属性和章程不能够被别的实例访问,不过能被里面方法访问,新注解的实例,不会另行创立相符的点子。

对于艺术的论断比较容易。

因为在构造函数中的方法总会在声圣元个新的实例时被另行创制,由此咱们注脚的秘籍都尽量防止出今后构造函数中。

而生机勃勃旦你的法子中需求用到构造函数中的变量,可能想要公开,那就供给放在原型中。

假使艺术必要个人不被外边访谈,那么就放置在模块成效域中。

对于属性放置于怎么着职位有个别时候很难做出精确的判断,由此小编很难交付一个标准的定义告诉您如何性质一定要放在什么岗位,那亟需在实质上付出中连连的计算阅世。可是由此可知,照旧要结成那多少个岗位的特征来做出最合适的推断。

要是属性值只可以被实例单独具有,举例person对象的name,只好归属某一个person实例,又比如说这里拖拽对象中,某二个因素的发轫地点,也仅仅只是这一个因素的此时此刻职责,那特天性,则符合放在构造函数中。

而风华正茂旦贰性情能仅仅供内部方法访问,那本性情就适合放在模块成效域中。

至于面向对象,上面的几点思索本身认为是这篇小说最值得认真想一想的精髓。假若在封装时没有思量清楚,非常的大概会蒙受超多您意想不到的bug,所以提议我们结合自身的支出经验,多多构思,计算出团结的视角。

听说这几个思量,我们能够和谐尝试封装一下。然后与自家的做一些对照,看看我们的主张有怎样分歧,在底下例子的笺注中,我将协和的主张表明出来。

点击查看已经封装好的demo

js 源码

JavaScript

; (function() { // 那是二个私亲属性,无需被实例访谈 var transform =
getTransform(); function Drag(selector) { //
放在构造函数中的属性,都是归于每三个实例单独具备 this.elem = typeof
selector == ‘Object’ ? selector : document.getElementById(selector);
this.startX = 0; this.startY = 0; this.sourceX = 0; this.sourceY = 0;
this.init(); } // 原型 Drag.prototype = { constructor: Drag, init:
function() { // 初阶时索要做些什么业务 this.setDrag(); }, //
稍作退换,仅用于获取当前成分的本性,相似于getName getStyle:
function(property) { return document.defaultView.getComputedStyle ?
document.defaultView.getComputedStyle(this.elem, false)[property] :
this.elem.currentStyle[property]; }, //
用来获得当前成分的地方音信,注意与事先的不相同之处 getPosition: function()
{ var pos = {x: 0, y: 0}; if(transform) { var transformValue =
this.getStyle(transform); if(transformValue == ‘none’) {
this.elem.style[transform] = ‘translate(0, 0)’; } else { var temp =
transformValue.match(/-?\d+/g); pos = { x: parseInt(temp[4].trim()),
y: parseInt(temp[5].trim()) } } } else { if(this.getStyle(‘position’)
== ‘static’) { this.elem.style.position = ‘relative’; } else { pos = {
x: parseInt(this.getStyle(‘left’) ? this.getStyle(‘left’) : 0), y:
parseInt(this.getStyle(‘top’) ? this.getStyle(‘top’) : 0) } } } return
pos; }, // 用来安装当前成分的地方 setPostion: function(pos) {
if(transform) { this.elem.style[transform] = ‘translate(‘+ pos.x +’px,
‘+ pos.y +’px)’; } else { this.elem.style.left = pos.x + ‘px’;
this.elem.style.top = pos.y + ‘px’; } }, // 该形式用来绑定事件 setDrag:
function() { var self = this; this.elem.add伊芙ntListener(‘mousedown’,
start, false); function start(event) { self.startX = event.pageX;
self.startY = event.pageY; var pos = self.getPosition(); self.sourceX =
pos.x; self.sourceY = pos.y; document.add伊芙ntListener(‘mousemove’,
move, false); document.addEventListener(‘mouseup’, end, false); }
function move(event) { var currentX = event.pageX; var currentY =
event.pageY; var distanceX = currentX – self.startX; var distanceY =
currentY – self.startY; self.setPostion({ x: (self.sourceX +
distanceX).toFixed(), y: (self.sourceY + distanceY).toFixed() }) }
function end(event) { document.remove伊夫ntListener(‘mousemove’, move);
document.remove伊芙ntListener(‘mouseup’, end); // do other things } } }
// 私有方法,仅仅用来得到transform的相配写法 function getTransform() {
var transform = ”, divStyle = document.createElement(‘div’).style,
transformArr = [‘transform’, ‘webkitTransform’, ‘MozTransform’,
‘msTransform’, ‘OTransform’], i = 0, len = transformArr.length; for(; i
< len; i++) { if(transformArr[i] in divStyle) { return transform =
transformArr[i]; } } return transform; } // 豆蔻梢头种对外暴光的方式window.Drag = Drag; })(); // 使用:注脚2个拖拽实例 new Drag(‘target’);
new Drag(‘target2’);

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
;
(function() {
    // 这是一个私有属性,不需要被实例访问
    var transform = getTransform();
 
    function Drag(selector) {
        // 放在构造函数中的属性,都是属于每一个实例单独拥有
        this.elem = typeof selector == ‘Object’ ? selector : document.getElementById(selector);
        this.startX = 0;
        this.startY = 0;
        this.sourceX = 0;
        this.sourceY = 0;
 
        this.init();
    }
 
 
    // 原型
    Drag.prototype = {
        constructor: Drag,
 
        init: function() {
            // 初始时需要做些什么事情
            this.setDrag();
        },
 
        // 稍作改造,仅用于获取当前元素的属性,类似于getName
        getStyle: function(property) {
            return document.defaultView.getComputedStyle ? document.defaultView.getComputedStyle(this.elem, false)[property] : this.elem.currentStyle[property];
        },
 
        // 用来获取当前元素的位置信息,注意与之前的不同之处
        getPosition: function() {
            var pos = {x: 0, y: 0};
            if(transform) {
                var transformValue = this.getStyle(transform);
                if(transformValue == ‘none’) {
                    this.elem.style[transform] = ‘translate(0, 0)’;
                } else {
                    var temp = transformValue.match(/-?\d+/g);
                    pos = {
                        x: parseInt(temp[4].trim()),
                        y: parseInt(temp[5].trim())
                    }
                }
            } else {
                if(this.getStyle(‘position’) == ‘static’) {
                    this.elem.style.position = ‘relative’;
                } else {
                    pos = {
                        x: parseInt(this.getStyle(‘left’) ? this.getStyle(‘left’) : 0),
                        y: parseInt(this.getStyle(‘top’) ? this.getStyle(‘top’) : 0)
                    }
                }
            }
 
            return pos;
        },
 
        // 用来设置当前元素的位置
        setPostion: function(pos) {
            if(transform) {
                this.elem.style[transform] = ‘translate(‘+ pos.x +’px, ‘+ pos.y +’px)’;
            } else {
                this.elem.style.left = pos.x + ‘px’;
                this.elem.style.top = pos.y + ‘px’;
            }
        },
 
        // 该方法用来绑定事件
        setDrag: function() {
            var self = this;
            this.elem.addEventListener(‘mousedown’, start, false);
            function start(event) {
                self.startX = event.pageX;
                self.startY = event.pageY;
 
                var pos = self.getPosition();
 
                self.sourceX = pos.x;
                self.sourceY = pos.y;
 
                document.addEventListener(‘mousemove’, move, false);
                document.addEventListener(‘mouseup’, end, false);
            }
 
            function move(event) {
                var currentX = event.pageX;
                var currentY = event.pageY;
 
                var distanceX = currentX – self.startX;
                var distanceY = currentY – self.startY;
 
                self.setPostion({
                    x: (self.sourceX + distanceX).toFixed(),
                    y: (self.sourceY + distanceY).toFixed()
                })
            }
 
            function end(event) {
                document.removeEventListener(‘mousemove’, move);
                document.removeEventListener(‘mouseup’, end);
                // do other things
            }
        }
    }
 
    // 私有方法,仅仅用来获取transform的兼容写法
    function getTransform() {
        var transform = ”,
            divStyle = document.createElement(‘div’).style,
            transformArr = [‘transform’, ‘webkitTransform’, ‘MozTransform’, ‘msTransform’, ‘OTransform’],
 
            i = 0,
            len = transformArr.length;
 
        for(; i < len; i++)  {
            if(transformArr[i] in divStyle) {
                return transform = transformArr[i];
            }
        }
 
        return transform;
    }
 
    // 一种对外暴露的方式
    window.Drag = Drag;
})();
 
// 使用:声明2个拖拽实例
new Drag(‘target’);
new Drag(‘target2’);

如此一个拖拽对象就封装达成了。

提出大家依照本身提供的寻思方法,多多尝试封装一些零件。譬喻封装叁个弹窗,封装一个循环轮播等。练得多了,面向对象就不再是主题素材了。这种理念方法,在以后任何时候都是力所能致接纳的。

下大器晚成章深入分析jQuery对象的落到实处,与什么将我们这里封装的拖拽对象扩充为jQuery插件。

2 赞 1 收藏
评论

金沙网址 3

相关文章