vue实现扇形菜单
Vue 实现扇形菜单
使用 CSS 和 Vue 动态计算位置
通过 Vue 的动态数据绑定和 CSS 的 transform 属性,可以实现扇形菜单的布局。核心是利用三角函数计算每个菜单项的位置。
<template>
<div class="fan-menu">
<div
v-for="(item, index) in items"
:key="index"
class="menu-item"
:style="getItemStyle(index)"
>
{{ item.label }}
</div>
</div>
</template>
<script>
export default {
data() {
return {
items: [
{ label: '选项1' },
{ label: '选项2' },
{ label: '选项3' },
{ label: '选项4' },
{ label: '选项5' },
],
radius: 100, // 扇形半径
angle: 60, // 扇形展开角度
};
},
methods: {
getItemStyle(index) {
const count = this.items.length;
const startAngle = -this.angle / 2;
const stepAngle = this.angle / (count - 1);
const angle = startAngle + index * stepAngle;
const radian = angle * Math.PI / 180;
return {
transform: `translate(
${this.radius * Math.cos(radian)}px,
${this.radius * Math.sin(radian)}px
)`,
};
},
},
};
</script>
<style>
.fan-menu {
position: relative;
width: 300px;
height: 300px;
}
.menu-item {
position: absolute;
width: 50px;
height: 50px;
background: #42b983;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: white;
left: 50%;
top: 50%;
transform-origin: 0 0;
}
</style>
添加动画效果
通过 Vue 的过渡效果或 CSS 动画,可以为扇形菜单添加展开和收起的动画。

<template>
<div class="fan-menu">
<transition-group name="fan">
<div
v-for="(item, index) in items"
v-show="isOpen"
:key="index"
class="menu-item"
:style="getItemStyle(index)"
>
{{ item.label }}
</div>
</transition-group>
<button @click="isOpen = !isOpen">切换菜单</button>
</div>
</template>
<script>
export default {
data() {
return {
isOpen: false,
// 其他数据同上
};
},
// 其他代码同上
};
</script>
<style>
.fan-enter-active, .fan-leave-active {
transition: all 0.5s ease;
}
.fan-enter, .fan-leave-to {
opacity: 0;
transform: translate(0, 0) !important;
}
</style>
使用第三方库
如果需要更复杂的扇形菜单效果,可以借助第三方库如 vue-simple-context-menu 或手动集成 GSAP 动画库。
安装 GSAP:

npm install gsap
示例代码:
<template>
<div class="fan-menu">
<div ref="items" v-for="(item, index) in items" :key="index" class="menu-item">
{{ item.label }}
</div>
<button @click="toggleMenu">切换菜单</button>
</div>
</template>
<script>
import { gsap } from 'gsap';
export default {
data() {
return {
isOpen: false,
items: [
{ label: '选项1' },
{ label: '选项2' },
{ label: '选项3' },
{ label: '选项4' },
{ label: '选项5' },
],
radius: 100,
angle: 60,
};
},
methods: {
toggleMenu() {
this.isOpen = !this.isOpen;
this.animateMenu();
},
animateMenu() {
const items = this.$refs.items;
const count = this.items.length;
const startAngle = -this.angle / 2;
const stepAngle = this.angle / (count - 1);
items.forEach((item, index) => {
const angle = startAngle + index * stepAngle;
const radian = angle * Math.PI / 180;
const x = this.radius * Math.cos(radian);
const y = this.radius * Math.sin(radian);
gsap.to(item, {
x: this.isOpen ? x : 0,
y: this.isOpen ? y : 0,
duration: 0.5,
ease: 'back.out',
});
});
},
},
};
</script>
响应式设计
通过监听窗口大小变化或使用 CSS 媒体查询,可以确保扇形菜单在不同设备上正常显示。
mounted() {
window.addEventListener('resize', this.handleResize);
},
beforeDestroy() {
window.removeEventListener('resize', this.handleResize);
},
methods: {
handleResize() {
this.radius = window.innerWidth < 768 ? 80 : 100;
},
},






