vue半圆形悬浮菜单
我们使用的悬浮菜单按钮一般是固定布局要不就是伸缩布局,固定菜单不用多说很简单,里主要介绍伸缩菜单的写法。
# 固定方向伸缩菜单
固定方向是指在水平或竖直方向伸缩,水平方向控制width变化,竖直方向控制高度变化即可,可以将menu提到外层与列表按钮分开更好控制一点。代码仅做参考如需使用悬浮菜单请参考Fab悬浮按钮
效果如下:

代码:
<template>
<div class="container">
<!-- 向下menu -->
<!-- <ul :style="{width: open ? '135px' : '45px'}" style="display: flex; flex-direction: row;"> -->
<ul :style="{height: open ? '135px' : '45px'}">
<li class="menu" @click="open = !open"></li>
<li class="item" v-show="open">
<img src="../../assets/item.png" alt="">
<div>item1</div>
</li>
<li class="item" v-show="open">
<img src="../../assets/item.png" alt="">
<div>item2</div>
</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
open: false
}
}
}
</script>
<style scoped>
.container {
position: fixed;
z-index: 10000;
height: 100vh;
}
ul {
position: absolute;
top: 60%;
left: 10px;
width: 45px;
padding: 0;
height: 45px;
list-style-type: none;
border-radius: 45px;
background-color: #007AFF;
transition: all .3s;
}
ul .menu {
width: 45px;
height: 45px;
background-color: #FFFFFF;
border-radius: 50%;
background-image: url(../../assets/plus.png);
background-size: 25px 25px;
background-position: center;
background-repeat: no-repeat;
box-shadow: 0 0 5px rgba(1,1,1,.4);
}
ul .item {
width: 45px;
height: 45px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-around;
}
ul .item img {
width: 20px;
height: 20px;
}
ul .item div {
font-size: 10px;
color: #FFFFFF;
}
</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
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
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
# 旋转菜单
旋转菜单是指菜单项以旋转方式分布,效果如下:

有两种实现方式,一种是对菜单项进行absolute定位并计算在圆弧位置上的left和top,二是让菜单项以菜单按钮进行不同角度旋转旋转。
使用absolute方法,假设菜单按钮在圆中心点,半径r,那么对应角度的位置可以计算得到:left=sin(a)*r,top=cos(a)*r,这里有一点要注意:top是以父物体为起点计算出的值恰好跟真实值相反,所有真正的top等于-cos(a)*r。

旋转方法,旋转方法比较简单,以菜单按钮为中心固定半径和角度旋转即可,这里主要利用css的两个属性:
transform: rotateZ():以z轴方向旋转transform-origin:设置物体中心点,物体以中心点旋转

完整代码:
<template>
<div class="container">
<!-- 固定left top -->
<!-- <div class="menu" @click="open = !open">
<div class="item" v-for="(item, index) in 4" :key="index" :style="{left: open ? leftDirPos(index) : 0, top: open ? topDirPos(index) : 0, display: open ? 'flex' : 'none'}">
<img src="../../assets/item.png" alt="">
<div>item{{index}}</div>
</div>
</div> -->
<!-- 弧形item -->
<div class="menu" @click="clickMenu" :style="{overflow: hidden ? 'hidden' : 'visible'}">
<div class="item rotateItem" v-for="(item, index) in 4" :key="index" :style="[rotateStyle(index)]">
<div class="item" :style="{transform: 'rotateZ(' + -index * angle + 'deg)'}">
<img src="../../assets/item.png" alt="">
<div>idx{{index}}</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
hidden: false, // 解决display切换transition不触发
open: true, // 是否显示item
r: 60, // 半径
initial: 10, // 初始旋转角度
angle: 60, // item间隔角度
}
},
computed: {
topDirPos() {
return (index) => {
// 注意角度要先转弧度 转换关系:角度*2*pi/360
let c = Math.cos(2 * Math.PI * (this.angle * index + this.initial) / 360) * this.r ;
return -c + 'px';
}
},
leftDirPos() {
return (index) => {
let c = Math.sin(2 * Math.PI * (this.angle * index + this.initial) / 360) * this.r;
return c + 'px';
}
},
rotateStyle() {
return (index) => {
if(!this.open) {
return {
transform: 'rotateZ(0deg)',
}
}else {
return {
transform: 'rotateZ(' + index * this.angle + 'deg)',
}
}
}
}
},
methods: {
clickMenu() {
if(this.open){
this.open = false;
setTimeout(() => {
this.hidden = true;
}, 300);
}else {
this.hidden = false;
setTimeout(() => {
this.open = true;
}, 50);
console.log(this.hidden);
}
}
},
created() {
}
}
</script>
<style scoped>
.container {
position: fixed;
z-index: 10000;
height: 100vh;
}
.menu {
position: absolute;
top: 60%;
left: 10px;
width: 45px;
height: 45px;
border-radius: 50%;
background-color: #FFFFFF;
background-image: url(../../assets/plus.png);
background-size: 25px 25px;
background-position: center;
background-repeat: no-repeat;
box-shadow: 0 0 5px rgba(1,1,1,.4);
}
.menu .item{
position: absolute;
left: 0;
top: 0;
width: 40px;
height: 40px;
display: flex;
align-items: center;
flex-direction: column;
justify-content: space-around;
border-radius: 40px;
background-color: #0074D9;
transition: all .3s;
}
.menu .rotateItem{
top: -60px;
transform-origin: center 80px; /* 以父物体中心点旋转 top + item.height/2 */
}
.menu .item img {
width: 15px;
height: 15px;
}
.menu .item div {
font-size: 10px;
color: #FFFFFF;
}
</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
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
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
上次更新: 2025/09/05, 8:09:00