主要原因:对于前端开发来说,兼容性是我们必须要考虑的问题之一。我们的项目不需要兼容低版本浏览器。项目本身也是一个数据驱动型的。加之,Vue本身具有以下主要特性:
这就是我们为什么选择Vue框架的一些原因。
主要目的是可提高代码的复用性和可维护性。
可维护性:组件化后,组件内部的逻辑只对组件负责,外部的逻辑只通过配置参数适配,所以提高了代码的逻辑清晰度,可以快速定位代码出现问题的地方。
组件化搭建页面图示:
上图可看出,在Vue中,所谓组件化搭建页面,简单来说,页面实际上是由一个个功能独立的组件搭建而成。这些组件之间可以组合、嵌套,最终形成了我们的页面。
下面是一个完成的组件构成:
1
2
3
4
5
6
7
8
9
|
// 组件内模板
// 组件内逻辑代码
&
lt
;
script
type
=
"text/javascript"
&
gt
;
&
lt
;
/
script
&
gt
;
// 组件内封装的样式
&
lt
;
style
lang
=
"scss"
scoped
&
gt
;
&
lt
;
/
style
&
gt
;
|
手指在移动的过程中,实时改变元素的位置即top和left值,使元素随着手指的移动而移动。
clientX,clientY
;获取元素距离视图上侧和左侧的距离 initTop
,initLeft
;计算接触点距离元素上侧和左侧的距离 elTop = clientY - initTop
, elLeft = clientX - initLeft
;currTop = clientY - elTop
, currLeft = clientX - elLeft
实时获取元素距离视图上侧和左侧的距离值,并赋值给元素,使元素跟着手指的移动而动起来;使用Vue,最大的不同之处是我们几乎不去操作DOM,要充分利用Vue的数据驱动来实现拖拽功能。本例中,我们只需在垂直方向上拖动元素,所以只需考虑垂直方向的移动即可。
上图中,通过data中的dragList渲染拖拽区域列表,代码如下:
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
|
template
:
&
lt
;
div
class
=
"drag-title"
&
gt
;拖拽可调整顺序
&
lt
;
/
div
&
gt
;
&
lt
;
ul
class
=
"drag-list"
&
gt
;
&
lt
;
li
class
=
"drag-item"
&
gt
;
{
{
item
.
txt
}
}
&
lt
;
/
li
&
gt
;
&
lt
;
/
ul
&
gt
;
script
:
export
default
{
data
(
)
{
return
{
dragList
:
null
}
}
,
created
(
)
{
this
.
dragList
=
[
{
isDrag
:
false
,
txt
:
'列表1'
,
isShow
:
false
}
.
.
.
]
}
,
}
|
假设我们将元素从位置1拖至位置3,本质上是数组的顺序发生了改变。这就有必要提一下Vue的最大特性:数据驱动。
所谓的数据驱动就是当数据发生变化时,通过修改数据状态,使用户界面发生相应的改变,开发者不需要手动的去修改DOM。
Vue的数据驱动是通过MVVM这种框架来实现的,MVVM框架主要包含3个部分:Model、View、Viewmodel。
– Model
:数据部分;
– View
:视图部分;
– Viewmodel
:连接视图与数据的中间件。
顺着这个思路走下去,我们知道:
– oldIndex
:元素在数组中的初始索引index;
– elHeight
:单个元素块的高;
– currTop = clientY - elTop
:元素在拖动过程中距离可视区上侧距离;
– currTop - initTop > 0
:得知元素是向上拖拽;
– currTop - initTop < 0
:得知元素是向下拖拽。
我们以向下拖拽来说:
– 首先,我们要在拖拽结束事件touchend中判断元素从拖动开始到拖动结束时拖动的距离。若小于某个设定的值,则什么也不做;
– 然后,在touchmove事件中判断,若(currTop - initTop) % elHeight>= elHeight/2
成立,即当元素拖至另一个元素块等于或超过1/2的位置时,即可将元素插入到最新的位置为newIndex = (currTop - initTop) / elHeight + oldIndex
。
– 最后,若手指离开元素,那么我们在touchend事件中,通过this.dragList.splice(oldIndex, 1)
,this.dragList.splice(newIndex, 0, item)
重新调整数组顺序。页面会根据最新的dragList渲染列表。
写到这里,我们俨然已经用Vue实现了移动端的拖拽功能。但是拖拽体验并不好,接下来,我们对它进行优化。
优化点:我们希望,在元素即将可能落到的位置,提前留出一个可以放得下元素的区域,让用户更好的感知拖拽的灵活性。
方案:(方案已被验证是可行的)将li的结构做一下修改,代码如下:
1
2
3
4
5
6
7
8
9
|
&
lt
;
ul
&
gt
;
&
lt
;
li
class
=
"drag-item"
&
gt
;
&
lt
;
div
class
=
"leave-block"
&
gt
;
&
lt
;
/
div
&
gt
;
// 向上拖拽时留空
&
lt
;
div
class
=
""
&
gt
;
{
{
item
.
txt
}
}
&
lt
;
/
div
&
gt
;
&
lt
;
div
class
=
"leave-block"
&
gt
;
&
lt
;
/
div
&
gt
;
// 向下拖拽时留空</li>
&
lt
;
/
ul
&
gt
;
|
拖拽过程中:将元素即将落入新位置的那个li下div的item.isShow设置为true,其他li下div的item.isShow均设置为false;
拖拽结束:将所有li下div的item.isShow 均设置为false,将元素定位方式由absolute设置为static。
贴一段伪代码:
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
|
touchStart
(
e
)
{
// 获取元素距离视口顶部的初始距离
initTop
=
e
.
currentTarget
.
offsetTop
;
// 开始拖动时,获取鼠标距离视口顶部的距离
initClientY
=
e
.
touches
[
0
]
.
clientY
;
// 计算出接触点距离元素顶部的距离
elTop
=
e
.
touches
[
0
]
.
clientY
-
initTop
;
}
,
touchMove
(
index
,
item
,
e
)
{
// 将拖拽结束时,给元素设置的static定位方式移除,防止元素二次拖拽无效
e
.
target
.
classList
.
remove
(
'static'
)
;
// 给拖拽的元素设置绝对定位方式
e
.
target
.
classList
.
add
(
'ab'
)
;
// 获取元素在拖拽过程中距离视口顶部距离
currTop
=
e
.
touches
[
0
]
.
clientY
-
elTop
;
// 元素在拖拽过程中距离视口顶部距离赋给元素
e
.
target
.
style
.
top
=
currTop
;
// 获取元素初始位置
oldIndex
=
index
;
// 获取拖拽元素
currItem
=
item
;
// 若元素已经拖至区域外
if
(
e
.
touches
[
0
]
.
clientY
&
gt
;
(
this
.
dragList
.
length
)
*
elHeight
)
{
// 将元素距离上侧的距离设置为拖动区视图的高
currTop
=
(
this
.
dragList
.
length
)
*
elHeight
;
return
;
}
// 向下拖拽
if
(
currTop
&
gt
;
initTop
)
{
// 若拖拽到大于等于元素的一半时,即可将元素插入到最新的位置
if
(
(
currTop
-
initTop
)
%
elHeight
&
gt
;
=
elHeight
/
2
)
{
// 计算出元素拖到的最新位置
newIndex
=
Math
.
round
(
(
currTop
-
initTop
)
/
elHeight
)
+
index
;
// 确保新元素的索引不能大于等于列表的长度
if
(
newIndex
&
lt
;
this
.
dragList
.
length
)
{
// 将所有列表留空处隐藏
for
(
var
i
=
0
;
i
&
lt
;
this
.
dragList
.
length
;
i
++
)
{
this
.
dragList
[
i
]
.
isShow
=
false
;
}
// 将元素即将拖到的新位置的留空展示
this
.
dragList
[
newIndex
]
.
isShow
=
true
;
}
else
{
return
;
}
}
}
// 向上拖拽,原理同上
if
(
currTop
&
lt
;
initTop
)
{
.
.
.
}
}
,
touchEnd
(
e
)
{
// 若拖动距离大于某个设定的值,则按照上述,执行相关代码 if(Math.abs(e.changedTouches[0].clientY - initClientY ) > customVal){
this
.
dragList
.
splice
(
oldIndex
,
1
)
;
this
.
dragList
.
splice
(
newIndex
,
0
,
currItem
)
;
for
(
var
i
=
0
;
i
&
lt
;
this
.
dragList
.
length
;
i
++
)
{
this
.
dragList
[
i
]
.
isShow
=
false
;
this
.
dragList
[
i
]
.
isShowUp
=
false
;
}
}
e
.
target
.
classList
.
remove
(
'ab'
)
;
e
.
target
.
classList
.
add
(
'static'
)
;
}
|
优化后,如下图所示:
以上便是用Vue实现移动端拖拽组件的过程。我们知道,有些项目是需要在PC端用Vue实现此功能。这里简单提一下PC与移动端的区别如下:
draggable
,dragstart,drag,dragend
;第二种:mousedown,mousemove,mouseup
;PC端获取鼠标坐标是通过e.clientX
,clientY
,区别于移动端的e.touches[0].clientX
,e.touches[0].clientY
。
本文从Vue拖拽组件开发为例,剖析Vue组件的结构、开发思路、Vue的数据驱动等,对Vue组件化的原理,进行了更深入的理解。 并将Vue实现拖拽的方案提供给大家学习研究。
P.S. 牢记一点,切勿在Vue中过多得操作DOM,要能深入理解Vue数据驱动的核心思想。
更多内容请关注我们团队的公众账号“全栈探索”。定期会有好文推送,满满的干货。
https://mp.weixin.qq.com/s/9frV2L4rW7_TGTYM87BSPA
https://jdc.jd.com/archives/212189