陪你写一个Vue全局拖拽组件

前言

大佬们,都是手摸手带你们学习,而我就摸不了你们的小手了。因为可能我分享的内容,是一些大家早已知悉的内容,只不过这个需求,挺有意思的,我实现完之后。写这个文章为了分享一些能帮助到别人的内容,也是为了归档一下自己的知识点。有写的不好的地方望指正。

需求

大家在浏览电商的 app 的时候,大多数在详情页的右下角上方,会有一个悬浮的按钮。用来分享商品详情、或者回到首页等功能的实现。我们的项目呢是,基于小程序的一款金融平台,应用了小程序原声+webview 的方案。因为 webview 里面路由跳转是 vue 的跳转,小程序并没有 navigateto,在不同系统,机型上,小程序的 webview 实现方式有所不同,而且页面层级增加之后。就会导致用户回退的时候,可能会点很多次才能回到小程序的原生页面。 所以我们也采用了这一方案。

实现

组件好多的内容,很多大神都有文章来写的用法概念什么,我就不一一赘述了。首先这种组件在我们项目里面是 webview 里面所有页面都要用的,肯定得是一个全局组件,如果一个个引入那真滴是太 low 了。我就直接给大家上代码了:

vue 组件

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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198

<template>
<div class="yht-float-btn pos-r" v-show="showBtn" :style="{'width':itemWidth+'px','height':itemHeight+'px','left':left+'px','top':top-70+'px'}"
ref="floatDiv">
<div class="top-icon pos-a">
<transition name="slide-fade">
<div class="float-box text-center hide-box" v-show="show" @click="handleClickItem(0)">
<!-- 问题 -->
<img src="@/assets/img/yht/icon_question.png" alt="">
</div>
</transition>
<transition name="slide-fade">
<div class="float-box text-center hide-box" v-show="show" @click="handleClickItem(1)">
<img src="@/assets/img/yht/icon_home.png" alt="">
<!-- 首页 -->
</div>
</transition>
</div>
<div class="float-box text-center pos-a bottom-icon" @click="handleClickMenu">
<div>
<img v-if="show" src="@/assets/img/yht/icon_close.png" alt="">
<img v-else src="@/assets/img/yht/icon_shortcut.png" alt="">
</div>
</div>
</div>
</template>

<script>
import Util from "@/utils/common";
import router from "../../router";
export default {
name: "drag",
components: {},
props: {
text: {
type: String,
default: "默认文字"
},
itemWidth: {
type: Number,
default: 66
},
itemHeight: {
type: Number,
default: 66
},
gapWidth: {
type: Number,
default: 10
},
coefficientHeight: {
type: Number,
default: 0.8
},
showBtn: {
// 是否显示此toast
default: false
}
},
data() {
return {
timer: null,
currentTop: 0,
clientWidth: 0,
clientHeight: 0,
left: 0,
top: 0,
show: false
};
},
created() {
this.clientWidth = document.documentElement.clientWidth;
this.clientHeight = document.documentElement.clientHeight;
this.left = this.clientWidth - this.itemWidth - this.gapWidth;
this.top = this.clientHeight * this.coefficientHeight;
},
mounted() {
window.addEventListener("scroll", this.handleScrollStart);
this.$nextTick(() => {
const div = this.$refs.floatDiv;
div.addEventListener("touchstart", e => {
div.style.transition = "none";
});
div.addEventListener("touchmove", e => {
e.preventDefault();
if (e.targetTouches.length === 1) {
let touch = event.targetTouches[0];
this.left = touch.clientX - this.itemWidth / 2;
this.top = touch.clientY - this.itemHeight / 2;
}
});
div.addEventListener("touchend", e => {
div.style.transition = "all 0.3s";
if (this.left > this.clientWidth / 2) {
// 浮动靠右边
this.left = this.clientWidth - this.itemWidth - this.gapWidth;
console.log("这里是右边");
} else {
// 靠左边
this.left = this.gapWidth;
console.log("这里是左边");
}
if (this.top < 0) {
// 防止滑到上方被隐藏
this.top = this.clientHeight * this.coefficientHeight;
}
});
});
},
beforeDestroy() {
window.removeEventListener("scroll", this.handleScrollStart);
},
methods: {
handleScrollStart() {
this.timer && clearTimeout(this.timer);
this.timer = setTimeout(() => {
this.handleScrollEnd();
}, 300);
this.currentTop =
document.documentElement.scrollTop || document.body.scrollTop;
if (this.left > this.clientWidth / 2) {
this.left = this.clientWidth - this.itemWidth / 2;
} else {
this.left = -this.itemWidth / 2;
}
},
handleScrollEnd() {
let scrollTop =
document.documentElement.scrollTop || document.body.scrollTop;
if (scrollTop === this.currentTop) {
if (this.left > this.clientWidth / 2) {
this.left = this.clientWidth - this.itemWidth - this.gapWidth;
} else {
this.left = this.gapWidth;
}
clearTimeout(this.timer);
}
},
handleClickMenu() {
this.show = !this.show;
},
handleClickItem(type) {
if (type === 0) {
router.push({ path: "/question/question" });
} else {
if (window.sessionStorage.getItem("isMiniEnvironment") !== "0") {
Util.toMiniIndex(); // 这里是回到小程序首页
} else {
router.replace({ path: "/yht/index" });
}
}
}
}
};
</script>

<style lang="less" scoped>
.yht-float-btn {
z-index: 99999;
transition: all 0.3s;
position: fixed;
bottom: 20vw;

img {
width: 66px;
height: 66px;
object-fit: contain;
margin-bottom: 3px;
}

p {
font-size: 7px;
}
}
.float-box {
width: 66px;
height: 66px;
}
.slide-fade-enter-active {
transition: all 1s ease;
}
.slide-fade-leave-active {
transition: all 0.8s cubic-bezier(1, 0.5, 0.8, 1);
}
.slide-fade-enter, .slide-fade-leave-to
/* .slide-fade-leave-active for below version 2.1.8 */ {
transform: translateY(10px);
opacity: 0;
}
.top-icon {
left: 0;
bottom: 66px;
}
.bottom-icon {
bottom: 0;
left: 0;
}
</style>

这个组件写好之后 可以先 引入到一个页面里面调一下样式。

实现动态加载组件

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
import Float from './floatBtn.vue';
let dom;
const FloatBtn = {}; // 定义插件对象
FloatBtn.install = function(Vue, options) {
// vue的install方法,用于定义vue插件
// 如果toast还在,则不再执行
if (document.getElementsByClassName('yht-float-btn').length) {
return;
}
let Instance = Vue.extend(Float);
const initInstance = () => {
// 实例化vue实例
dom = new Instance();
dom.vm = dom.$mount();
let boxEl = dom.$mount().$el;
document.body.appendChild(boxEl);
};
Vue.prototype.$floatBtn = {
showFloat(options) {
if (!dom) {
initInstance();
}
dom.vm.showBtn = true; // 显示toast
},
hideFloat(options = 0) {
if (!dom) {
initInstance();
}
dom.vm.showBtn = false; // 显示整体
}
};
};
export default FloatBtn;

在全局中的使用

需求是,当用户进入页面栈两层之后才给用户显示这个浮动组件。 所以在路由守卫中先定义了一个计数器,在路由跳转的时候计数器增加。其实本来是想在路由守卫中来调用这个浮动组件的,但是笔者在路由守卫中,尝试调用组件的时候,发现访问不到这个组件。因为当时需求比较着急,就换了一种方式。也希望有大佬能指点一下这块的知识点~

1
2
3
4
5
6
7
8
9
10
11
12
let count = 2; // 悬浮按钮计数器
count++;
window.sessionStorage.setItem('floatCount', count);
// 然后在router-view页面里
this.floatCount = this.$storage.get('floatCount'); // 获取悬浮按钮计数器
if (this.floatCount > 3) {
this.$floatBtn.showFloat();
}
// 当然如果没有特别的需求的话,这个组件就可以在任意一个vue页面里面去调用 this.$floatBtn.showFloat() 这个方法。
// 还要在mainjs里面
import FloatBtn from '@/components/floatBtn/floatBtn';
Vue.use(FloatBtn); // 增加 悬浮按钮

效果如下图:

到这里就陪你写完了一个 vue 全局的组件,奖励自己一朵小发发~
最后再附上github 地址

本文标题:陪你写一个Vue全局拖拽组件

文章作者:Jonathon

发布时间:2019年06月04日 - 15:06

最后更新:2019年07月21日 - 15:07

原始链接:https://www.jonathon.cn/vue-components.html

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

苟富贵,勿相忘!