一、功能概述

点击按钮后,弹出一个窗口,进行图形验证,验证失败进行抖动,成功后返回滑块的x轴。

二、功能制作

新建一个home.vue页面,设置一个按钮,点击后弹出slideCode组件

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
<template>
<view>
<button @click="slideCode_show = true">点击获取图形验证码</button>
<slideCode v-if="slideCode_show" :session_id="session_id[0]"></slideCode>
</view>

</template>

<script>
import slideCode from '../../components/slideCode/slideCode.vue'
export default {
data() {
return{
slideCode_show: false, //图形验证码是否显示
//验证码id
session_id: [Math.floor(Math.random() * 9999999999)],
slideCode_x:''
}
},
components:{
slideCode
},
methods:{
}
}
</script>

新建slideCode组件,设置遮罩层和固定滑动验证码位置

1
2
3
4
<view class="slideCode">
<view class="zhuti">
</view>
</view>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<style lang="scss" scoped>
.slideCode {
background: rgba(0, 0, 0, 0.3);
width: 750rpx;
height: 100vh;
position: fixed;
top: 0;
left: 0;
z-index: 999999;

.zhuti {
width: 600rpx;
background: #FFF;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
padding: 20rpx 25rpx 30rpx 25rpx;
}
}

</style>

给滑动图形验证码设置标题

1
2
3
4
5
6
7
<view class="slideCode">
<view class="zhuti">
<!-- 滑动验证码标题 -->
<view :class="['msg', msgColor]">{{msg}}</view>
<view class="title">拖动下方滑块完成拼图</view>
</view>
</view>
1
2
3
4
5
6
7
8
9
10
11
<script>
export default {
data() {
return {
msgColor: '',
msg: '安全验证',
msgText: '加载中', //验证码文字
}
},
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//标题提示
.msg {
color: #999;
font-size: 24rpx;

&.red {
color: red;
}

&.green {
color: green;
}
}

//标题
.title {
font-size: 28rpx;
line-height: 38rpx;
color: #333;
margin: 0 0 7rpx 0;
}

然后开始制作图形验证码,这里用到了Uni-App的<movable-area>可拖动区域组件

拿到父组件传过来的参数

当图片未加载时,显示一个加载中

当图片加载完成后,显示图片

1
2
3
4
5
6
<movable-area>
<!-- 当主图片未加载时显示 -->
<view id="msg" v-if="zhutuPic === ''">{{msgText}}</view>
<!-- 主图片加载成功 -->
<view id="pic" v-else :style="{'background-image': 'url('+zhutuPic+')'}"></view>
</movable-area>
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
<script>
export default {
//从父元素中拿到session_id
props: {
session_id: {
type: Number,
default: 0
},
},
data() {
return {
zhutuPic: '', //主图
futuPic: '', //缺口图
msgColor: '',
msg: '安全验证',
msgText: '加载中', //验证码文字
}
},
mounted() {
this.shuaxin()
},
methods: {
shuaxin() {
uni.request({
method: 'get',
dataType: 'json',
timeout: 7000,
url: ''//后端接口地址,
data: {
//传给后端的数据
},
success: (res) => {
if (res.data.status == 200) { //生成验证码成功
this.zhutuPic = ''//后端接口图片地址
this.futuPic = ''//后端接口图片地址
}
},
complete: (res) => {
//错误的提示
setTimeout(() => {
if (res.data.status != 200) {
this.msgText = res.message;
}

}, 300);
}
});
},
}
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
movable-area {
width: 100%;
height: auto;

//主图片未加载
#msg {
width: 100%;
line-height: 310rpx;
text-align: center;
font-size: 30rpx;
color: #999;
background: #F8F8F8;
}

//主图片
#pic {
width: 100%;
height: 310rpx;
background-color: #F8F8F8;
background-size: 100% 310rpx;
}
}

制作滑动栏

1
2
3
4
5
6
7
8
<movable-area>
<!-- 当主图片未加载时显示 -->
<view id="msg" v-if="zhutuPic === ''">{{msgText}}</view>
<!-- 主图片加载成功 -->
<view id="pic" v-else :style="{'background-image': 'url('+zhutuPic+')'}">
</view>
<view id="line"></view>
</movable-area>
1
2
3
4
5
6
7
8
9
//滑动栏
#line {
background: #e4e4e4;
width: 100%;
height: 20rpx;
margin: 42rpx 0 15rpx 0;
border-radius: 50rpx;
display: inline-block;
}

然后使用movable-view可拖拽区域,制作一个拖动按钮和滑块

1
2
3
4
<!-- 可拖拽区域  damping用于控制x或y改变时的动画和过界回弹的动画  direction移动方向-->
<movable-view :style="{'background-image': 'url('+futuPic+')'}" direction="horizontal" :damping="100">
<view class="blue"></view>
</movable-view>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
movable-view {
width: 78rpx;
height: 310rpx;
background-size: 100% 310rpx;
background-repeat: no-repeat;

.blue {
box-shadow: rgba(26, 101, 255, 0.52) 0px 0px 10px 1px;
width: 100rpx;
height: 50rpx;
border-radius: 50rpx;
position: absolute;
top: 100%;
left: 0;
background: rgb(26, 101, 255) url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAcAgMAAABuexVFAAAACVBMVEUAAADCwsL9/f1P0DqbAAAAAXRSTlMAQObYZgAAAB1JREFUGNNjCGVgYGANABKhyMwoEHMBkIgaZWIwAdyJJQnaJRg5AAAAAElFTkSuQmCC) no-repeat;
background-size: auto 20rpx;
background-position: 50% 50%;
margin: 25rpx 0 0 -14rpx;
}
}

然后制作一个返回的图标,并在父组件中获取到子组件的传值

1
2
<!-- 关闭 -->
<view class="close iconfont icon-guanbi1" @click="$emit('close')"></view>
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
@font-face {
font-family: "iconfont";
/* Project id 2455983 */
src:
url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAALEAAsAAAAABngAAAJ5AAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHFQGYACCcApweQE2AiQDCAsGAAQgBYRnBzEbtgUR1ZMhZH8r7lgI/uPREFlgoVBNm5PkuTjzBokvYm9bLZ7Q8ql1k55Aj4zHA0PJeeB/P9x9MjPtolU0rW5JQ58nSAudRqXRCFGs4ZnI+eCEm0caBUARHIiSJ0Je/uBN7D5M8JMncnEcb597Vhpwgr8zfqrALG7aKReheBK7c9X1PWkpfRCyJ+xa5EeKSpKryiT9ZEEApKX5fpaTvxt1KX8DeuAFkS/0PYVK65ROBxbA6AOlV0sohJmgx6MguyNQSw7DMwIqzVsWN3eXu6g9l5xFp8wZDuT14dw7KcrliCzTfLmgFN2bhZdKiE/xxIv48+FvAqKSxGnaPdsa8bZnd0ECRaUvXa6DPhZRgoRpZMJhf2ZbEtr4pNKTDdLWLPBD8aFbOLRD+Ouc2mrQTcVnknz5UWsjyOloL0YmjSXW1/YPW4yJ8wNQ0CBGoTk8cxF1ZFyBX8NkS3/87kgP5pT/K75uD9c2/dWjVVAsXYoFBF57v7LKf5TB1zsOFctgPsp36An8HfUEJGYzRszXKBEQVWlSKqLSjVPgBBp9m2o03UUhKNd0L74yXTmScoNU5iZRosoiSpVbQqUpW4erNLmWhFyBUTcEQb1XiGq9QVLvHZW59yjR6idK1fuPSsfBOK/KaBD0mlAyGlA/8Lpk6+kc272ie0VJTckn0uJpHNqqqeb3mJHm2LC8XcdswVJJsBMfwxgLTFRG1FwNzFNf17btLZUuSawJJaMB9QOvS7Yhmcvfd0X3ipJA6pmhxVP70FYNQO2VDPU8yCvL23XMFiyVBDt5FsZYYGqfNaLmapiQmvraJVlUqbbX0t8TBnDMuHKnJpJ7yax8KwQA') format('woff2');
}

.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

.icon-guanbi1:before {
content: "\e696";
}

.close {
position: absolute;
top: 20rpx;
right: 20rpx;
color: #A6A6A6;
font-size: 36rpx;
padding: 10rpx;
}

home.vue

1
2
3
4
<view>
<button @click="slideCode_show = true">点击获取图形验证码</button>
<slideCode v-if="slideCode_show" :session_id="session_id[0]" @close="slideCode_show = false"></slideCode>
</view>

之后就是编写滑动验证码的事件

1
2
3
4
5
<!-- 可拖拽区域  damping用于控制x或y改变时的动画和过界回弹的动画  direction移动方向
@change拖动过程中触发的事件 @touchend触凭到过程中不断触发事件-->
<movable-view :x="x" direction="horizontal" :style="{'background-image': 'url('+futuPic+')'}" :damping="100" @change="onChange" @touchend="touchEnd">
<view class="blue"></view>
</movable-view>

首先是onChange事件,@change拖动过程中触发的事件

e可以获取拖动事件的数据

我们判断滑块是否放置正确,来进行之后滑块位置错误的抖动效果。

并且我们可以实时获取滑块的x轴

1
2
3
4
5
6
onChange(e) {
if (e.detail.source === 'touch') { //滑块位置放置正确时 只有是手动触发的,才执行,
this.huadong = true //判断滑块是否正确
this.x = e.detail.x //滑块x轴
}
},

然后是touchEnd事件,@touchend触凭到过程中不断触发事件

成功后返回给父组件值,失败后进行抖动

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
touchEnd() {
if (this.huadong === true) { //只有拖动过后松开,才会触发,当然这个主要是应对pc端的(pc端是监听body的松开),移动端没这问题
this.msg = '验证中...'
this.msgColor = ''
uni.request({
method: 'get',
dataType: 'json',
timeout: 7000,
url: ''//后端接口地址,
data: {
type: 'yanzheng',
session_id: this.session_id,
x: parseInt(this.x * (679 / uni.upx2px(550)) - 35) //因为x是相对于movable-area容器宽度的,所以需要算出图片实际宽度相 对于 容器宽度的比例
},
success: (res) => {
if (res.data.status == 200) {
//成功后返回给父组件
this.$emit('success', {
x: parseInt(this.x * (679 / uni.upx2px(550)) - 35)
})
this.msg = '验证成功'
this.msgColor = 'green'
setTimeout(() => {
this.$emit('close')
}, 300);
}
},
complete: (res) => {
this.huadong = false; //没有在拖动中了
if (res.data.status != 200) {
this.doudong() //抖动
this.msg = res.data.message
this.msgColor = 'red'
if (res.data.message == '验证码错误次数过多,请重新获取') {
this.msg = '验证失败次数过多,已重新获取'
setTimeout(() => {
this.shuaxin()
}, 500);
}
setTimeout(() => {
this.msg = '安全验证'
this.msgColor = ''
}, 3500);
}
}
});
}
},

抖动

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
doudong() { //抖动 【总耗时 500ms】
setTimeout(() => {
if (this.huadong) { //判断,如果滑块放置正确,则不再执行。否则进行抖动
return
} //如果在拖动中,就不执行了
this.x -= 15;
setTimeout(() => {
if (this.huadong) {
return
}
this.x += 30
setTimeout(() => {
if (this.huadong) {
return
}
this.x -= 30
setTimeout(() => {
if (this.huadong) {
return
}
this.x += 30
setTimeout(() => {
if (this.huadong) {
return
}
this.x = 10
}, 100);
}, 100);
}, 100);
}, 100);
}, 100);
},

父组件home.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
<template>
<view>
<button @click="slideCode_show = true">点击获取图形验证码</button>
<slideCode v-if="slideCode_show" :session_id="session_id[0]" @close="slideCode_show = false"
@success="slideCode_success"></slideCode>
</view>

</template>
<script>
import slideCode from '../../components/slideCode/slideCode.vue'
export default {
data() {
return{
slideCode_show: false, //图形验证码是否显示
session_id: [Math.floor(Math.random() * 9999999999)],
slideCode_x:''
}
},
components:{
slideCode
},
methods:{
slideCode_success: function(x) { //验证通过了
this.slideCode_x = x //偏移值
uni.showToast({
icon:'success',
title:'成功'
})
console.log(this.slideCode_x );//打印输出验证成功后的滑块x轴
},
}
}
</script>

三、结果和代码展示

代码展示

home.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
<template>
<view>
<button @click="slideCode_show = true">点击获取图形验证码</button>
<slideCode v-if="slideCode_show" :session_id="session_id[0]" @close="slideCode_show = false"
@success="slideCode_success"></slideCode>
</view>

</template>

<script>
import slideCode from '../../components/slideCode/slideCode.vue'
export default {
data() {
return{
slideCode_show: false, //图形验证码是否显示
session_id: [Math.floor(Math.random() * 9999999999)],
slideCode_x:''
}
},
components:{
slideCode
},
methods:{
slideCode_success: function(x) { //验证通过了
this.slideCode_x = x //偏移值
uni.showToast({
icon:'success',
title:'成功'
})
console.log(this.slideCode_x );//打印输出验证成功后的滑块x轴
},
}
}
</script>

<style lang="scss" scoped>
</style>

slideCode.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
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
<template>
<view class="slideCode">
<view class="zhuti">
<!-- 滑动验证码标题 -->
<view :class="['msg', msgColor]">{{msg}}</view>
<view class="title">拖动下方滑块完成拼图</view>
<!-- 可拖动区域 -->
<movable-area>
<!-- 当主图片未加载时显示 -->
<view id="msg" v-if="zhutuPic === ''">{{msgText}}</view>
<!-- 主图片加载成功 -->
<view id="pic" v-else :style="{'background-image': 'url('+zhutuPic+')'}"></view>
<!-- 拖动栏 -->
<view id="line"></view>
<!-- 可拖拽区域 damping用于控制x或y改变时的动画和过界回弹的动画 direction移动方向
@change拖动过程中触发的事件 @touchend触凭到过程中不断触发事件-->
<movable-view :x="x" direction="horizontal" :style="{'background-image': 'url('+futuPic+')'}"
:damping="100" @change="onChange" @touchend="touchEnd">
<view class="blue"></view>
</movable-view>
</movable-area>
<!-- 关闭 -->
<!-- <view class="close iconfont icon-guanbi1" @click="$emit('close')"></view> -->
</view>
</view>
</template>
<script>
import config from '../../utils/config.js';
export default {
//重父元素中拿到session_id
props: {
session_id: {
type: Number,
default: 0
},
},
data() {
return {
zhutuPic: '', //主图
futuPic: '', //缺口图
x: 10, //当前的位置
msgColor: '',
msg: '安全验证',
msgText: '加载中', //验证码文字
huadong: false //是否已经滑动
}
},
mounted() {
this.shuaxin()
},
methods: {
//获取/刷新 主图和滑块图,
shuaxin() {
uni.request({
method: 'get',
dataType: 'json',
timeout: 7000,
url: config.URL + 'User/yzm_pic',
data: {
type: 'shengcheng',
session_id: this.session_id
},
success: (res) => {
if (res.data.status == 200) { //生成验证码成功
//time参数作用是让刷新后图片会重新获取
this.zhutuPic = config.imgUpload +
'index.php/api/User/yzm_pic?type=zhutu&session_id=' + this.session_id +
'&time=' + (new Date() * 1)
this.futuPic = config.imgUpload +
'index.php/api/User/yzm_pic?type=futu&session_id=' + this.session_id +
'&time=' + (new Date() * 1)
}
},
complete: (res) => {
//错误的提示
setTimeout(() => {
if (res.data.status != 200) {
this.msgText = res.message;
}

}, 300);
}
});
},
// @change拖动过程中触发的事件
onChange(e) {
if (e.detail.source === 'touch') { //滑块位置放置正确时 只有是手动触发的,才执行,
this.huadong = true //判断滑块是否正确
this.x = e.detail.x //滑块x轴
}
},
// @touchend触屏到过程中不断触发事件
touchEnd() {
if (this.huadong === true) { //只有拖动过后松开,才会触发,当然这个主要是应对pc端的(pc端是监听body的松开),移动端没这问题
this.msg = '验证中...'
this.msgColor = ''
uni.request({
method: 'get',
dataType: 'json',
timeout: 7000,
url: config.URL + 'User/yzm_pic',
data: {
type: 'yanzheng',
session_id: this.session_id,
x: parseInt(this.x * (679 / uni.upx2px(550)) -
35) //因为x是相对于movable-area容器宽度的,所以需要算出图片实际宽度相 对于 容器宽度的比例
},
success: (res) => {
if (res.data.status == 200) {
//成功后返回给父组件
this.$emit('success', {
x: parseInt(this.x * (679 / uni.upx2px(550)) - 35)
})
this.msg = '验证成功'
this.msgColor = 'green'
setTimeout(() => {
this.$emit('close')
}, 300);
}
},
complete: (res) => {
this.huadong = false; //没有在拖动中了
if (res.data.status != 200) {
this.doudong() //抖动
this.msg = res.data.message
this.msgColor = 'red'
if (res.data.message == '验证码错误次数过多,请重新获取') {
this.msg = '验证失败次数过多,已重新获取'
setTimeout(() => {
this.shuaxin()
}, 500);
}
setTimeout(() => {
this.msg = '安全验证'
this.msgColor = ''
}, 3500);
}
}
});
}
},
doudong() { //抖动 【总耗时 500ms】
setTimeout(() => {
if (this.huadong) { //判断,如果滑块放置正确,则不再执行。否则进行抖动
return
} //如果在拖动中,就不执行了
this.x -= 15;
setTimeout(() => {
if (this.huadong) {
return
}
this.x += 30
setTimeout(() => {
if (this.huadong) {
return
}
this.x -= 30
setTimeout(() => {
if (this.huadong) {
return
}
this.x += 30
setTimeout(() => {
if (this.huadong) {
return
}
this.x = 10
}, 100);
}, 100);
}, 100);
}, 100);
}, 100);
},
}
}
</script>
<style lang="scss" scoped>
@font-face {
font-family: "iconfont";
/* Project id 2455983 */
src:
url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAALEAAsAAAAABngAAAJ5AAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHFQGYACCcApweQE2AiQDCAsGAAQgBYRnBzEbtgUR1ZMhZH8r7lgI/uPREFlgoVBNm5PkuTjzBokvYm9bLZ7Q8ql1k55Aj4zHA0PJeeB/P9x9MjPtolU0rW5JQ58nSAudRqXRCFGs4ZnI+eCEm0caBUARHIiSJ0Je/uBN7D5M8JMncnEcb597Vhpwgr8zfqrALG7aKReheBK7c9X1PWkpfRCyJ+xa5EeKSpKryiT9ZEEApKX5fpaTvxt1KX8DeuAFkS/0PYVK65ROBxbA6AOlV0sohJmgx6MguyNQSw7DMwIqzVsWN3eXu6g9l5xFp8wZDuT14dw7KcrliCzTfLmgFN2bhZdKiE/xxIv48+FvAqKSxGnaPdsa8bZnd0ECRaUvXa6DPhZRgoRpZMJhf2ZbEtr4pNKTDdLWLPBD8aFbOLRD+Ouc2mrQTcVnknz5UWsjyOloL0YmjSXW1/YPW4yJ8wNQ0CBGoTk8cxF1ZFyBX8NkS3/87kgP5pT/K75uD9c2/dWjVVAsXYoFBF57v7LKf5TB1zsOFctgPsp36An8HfUEJGYzRszXKBEQVWlSKqLSjVPgBBp9m2o03UUhKNd0L74yXTmScoNU5iZRosoiSpVbQqUpW4erNLmWhFyBUTcEQb1XiGq9QVLvHZW59yjR6idK1fuPSsfBOK/KaBD0mlAyGlA/8Lpk6+kc272ie0VJTckn0uJpHNqqqeb3mJHm2LC8XcdswVJJsBMfwxgLTFRG1FwNzFNf17btLZUuSawJJaMB9QOvS7Yhmcvfd0X3ipJA6pmhxVP70FYNQO2VDPU8yCvL23XMFiyVBDt5FsZYYGqfNaLmapiQmvraJVlUqbbX0t8TBnDMuHKnJpJ7yax8KwQA') format('woff2');
}

.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

.icon-guanbi1:before {
content: "\e696";
}

.slideCode {
background: rgba(0, 0, 0, 0.3);
width: 750rpx;
height: 100vh;
position: fixed;
top: 0;
left: 0;
z-index: 999999;

.zhuti {
width: 600rpx;
background: #FFF;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
padding: 20rpx 25rpx 30rpx 25rpx;

//标题提示
.msg {
color: #999;
font-size: 24rpx;

&.red {
color: red;
}

&.green {
color: green;
}
}

//标题
.title {
font-size: 28rpx;
line-height: 38rpx;
color: #333;
margin: 0 0 7rpx 0;
}

.close {
position: absolute;
top: 20rpx;
right: 20rpx;
color: #A6A6A6;
font-size: 36rpx;
padding: 10rpx;
}

movable-area {
width: 100%;
height: auto;

//主图片未加载
#msg {
width: 100%;
line-height: 310rpx;
text-align: center;
font-size: 30rpx;
color: #999;
background: #F8F8F8;
}

//主图片
#pic {
width: 100%;
height: 310rpx;
background-color: #F8F8F8;
background-size: 100% 310rpx;
}

//滑动栏
#line {
background: #e4e4e4;
width: 100%;
height: 20rpx;
margin: 42rpx 0 15rpx 0;
border-radius: 50rpx;
display: inline-block;
}

movable-view {
width: 78rpx;
height: 310rpx;
background-size: 100% 310rpx;
background-repeat: no-repeat;

.blue {
box-shadow: rgba(26, 101, 255, 0.52) 0px 0px 10px 1px;
width: 100rpx;
height: 50rpx;
border-radius: 50rpx;
position: absolute;
top: 100%;
left: 0;
background: rgb(26, 101, 255) url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAcAgMAAABuexVFAAAACVBMVEUAAADCwsL9/f1P0DqbAAAAAXRSTlMAQObYZgAAAB1JREFUGNNjCGVgYGANABKhyMwoEHMBkIgaZWIwAdyJJQnaJRg5AAAAAElFTkSuQmCC) no-repeat;
background-size: auto 20rpx;
background-position: 50% 50%;
margin: 25rpx 0 0 -14rpx;
}
}
}
}
}
</style>