característica:
- Puede establecer el ancho y el alto de la pista de elipse.
- Se puede configurar el ángulo de rotación de la pista de elipse y se puede corregir automáticamente el estado horizontal del texto del menú.
- Puede configurar el paso de movimiento de las coordenadas de la trayectoria del movimiento.
- Puede configurar la pista de movimiento para cambiar la frecuencia.
- Se puede configurar para que gire en el sentido de las agujas del reloj o en el sentido contrario a las agujas del reloj.
- Puede configurar si el botón de entrada detiene el botón de movimiento circular
Código fuente de sgOvalMenu
<template>
<div :class="$options.name" :border-animate="borderAnimate" :style="style">
<div class="ovalMenuBtn" v-for="(a, i) in menubtns" :key="i" :style="a.style" @click="$emit(`click`, a);"
@mouseover="mouseover(a)" @mouseout="mouseout(a)">
<slot :data="a"></slot>
</div>
</div>
</template>
<script>
export default {
name: 'sgOvalMenu',
data() {
return {
style: {},
coordinates: [],
step_: 0,//按钮在椭圆轨道上面移动的步长
time_: 0,//按钮坐标变化的时间间隔
rotate_: 0,//椭圆旋转角度
oval_step: 1,//椭圆动画步长
interval1: null,
interval2: null,
menubtns: [],
isHoverBtn: false,
borderAnimate: true,
}
},
props: [
"width",//椭圆的长直径
"height",//椭圆的短直径
"rotate",//椭圆旋转角度
"step",//按钮在椭圆轨道上面移动的步长
"time",//按钮坐标变化的时间间隔
"clockwise",//顺时针运动(boolean)
"hoverButtonPause",//移入按钮暂停运动(boolean)
"data",//椭圆上面的按钮数据
],
watch: {
width: {
handler(d) {
this.style.width = `${d || 800}px`;
}, deep: true, immediate: true,
},
height: {
handler(d) {
this.style.height = `${d || 400}px`;
}, deep: true, immediate: true,
},
rotate_: {
handler(d) {
this.style.rotate = `${d || 0}deg`;
this.setProperty();
}, deep: true, immediate: true,
},
rotate: {
handler(d) {
this.rotate_ = d;
}, deep: true, immediate: true,
},
step: {
handler(d) {
this.step_ = d || 2;
}, deep: true, immediate: true,
},
time: {
handler(d) {
this.time_ = d || 200;
}, deep: true, immediate: true,
},
data: {
handler(d) {
if (d) {
this.menubtns = JSON.parse(JSON.stringify(d));
this.getCoordinates(d => {
this.coordinates = d;
this.initAnimate();
});
}
}, deep: true, immediate: true,
},
isHoverBtn(newValue, oldValue) {
if (this.hoverButtonPause || this.hoverButtonPause === '') {
newValue ? this.clearIntervalAll() : this.initAnimate();
this.borderAnimate = !newValue;
}
},
},
destroyed() {
this.clearIntervalAll();
},
mounted() {
this.setProperty();
},
methods: {
mouseover(d) {
this.isHoverBtn = true;
this.$emit(`mouseover`, d);
},
mouseout(d) {
this.isHoverBtn = false;
this.$emit(`mouseout`, d);
},
setProperty() {
this.$el && this.$el.style.setProperty("--rotate", `${-1 * parseFloat(this.style.rotate || 0)}deg`); //js往css传递局部参数
},
clearIntervalAll(d) {
clearInterval(this.interval1);
clearInterval(this.interval2);
},
initAnimate(d) {
this.initAnimateBtn();
this.initAnmiateOval()
},
// 按钮旋转动画
initAnimateBtn() {
clearInterval(this.interval1);
this.interval1 = setInterval(() => {
this.setStyles();
}, this.time_);
this.setStyles();
},
// 椭圆旋转动画
initAnmiateOval(d) {
clearInterval(this.interval2);
this.interval2 = setInterval(() => {
this.rotate_ = this.rotate_ + this.oval_step;
this.rotate_ > this.rotate && (this.oval_step = -1);
this.rotate_ < -1 * this.rotate && (this.oval_step = 1);
}, 382);
},
setStyles() {
let coordinateStep = this.coordinates.length / this.menubtns.length;
let arr = this.coordinates, N = this.step_;
if (this.clockwise || this.clockwise === '') {
//前面N个元素挪到最后
arr.splice(arr.length - 1, 0, ...arr.splice(0, N));
} else {
//最后N个元素挪到前面
arr.splice(0, 0, ...arr.splice(arr.length - N));
}
this.coordinates = arr;
this.menubtns.forEach((v, i) => {
let coordinate = this.coordinates[i * coordinateStep];
this.$set(v, "style", {
left: `${coordinate.x}px`,
top: `${coordinate.y}px`,
});
});
},
getCoordinates(cb) {
let a = parseFloat(this.style.width) / 2;
let b = parseFloat(this.style.height) / 2;
this.getCPoint(a, b, 1, a, b, cb);
},
// a 长半径, b 短半径, p 节点的间隔 , cx, cy 圆心,
getCPoint(a, b, p = 1, cx = 0, cy = 0, cb) {
const data = []
for (let index = 0; index < 360; index = index + p) {
let x = a * Math.cos(Math.PI * 2 * index / 360)
let y = b * Math.sin(Math.PI * 2 * index / 360)
data.push({ x: x + cx, y: y + cy })
}
cb && cb(data);
},
}
};
</script>
<style lang="scss" scoped>
$rotate: var(--rotate);
.sgOvalMenu {
border: 2px dashed transparent;
border-color: #409EFF66 #409EFFAA #409EFF #409EFF;
border-radius: 100%;
width: 100%;
height: 100%;
transform-origin: center;
transition: .382s linear;
.ovalMenuBtn {
transition: .382s linear;
user-select: none;
white-space: nowrap;
position: absolute;
width: max-content;
height: max-content;
left: 0;
top: 0;
transform: translate(-50%, -50%) rotate($rotate);
transform-origin: center;
pointer-events: auto;
color: white;
cursor: pointer;
&:hover {
z-index: 1;
font-weight: bold;
color: #409EFF;
text-shadow: 0px 0px 5px #409EFF;
filter: brightness(1.1);
}
}
/*边框虚线滚动动画特效*/
&[border-animate] {
background: linear-gradient(90deg, #409EFF 60%, transparent 60%) repeat-x left top/10px 1px,
linear-gradient(0deg, #409EFF 60%, transparent 60%) repeat-y right top/1px 10px,
linear-gradient(90deg, #409EFF 60%, transparent 60%) repeat-x right bottom/10px 1px,
linear-gradient(0deg, #409EFF 60%, transparent 60%) repeat-y left bottom/1px 10px;
animation: border-animate .382s infinite linear;
@keyframes border-animate {
0% {
background-position: left top, right top, right bottom, left bottom;
}
100% {
background-position: left 10px top, right top 10px, right 10px bottom, left bottom 10px;
}
}
}
}
</style>
solicitud
<template>
<div :class="$options.name">
<!-- 椭圆菜单 -->
<sgOvalMenu :data="ovalMenus" @click="clickOvalMenu" :width="700" :height="200" :rotate="30" clockwise
hoverButtonPause>
<template v-slot="{ data }">
<div class="btn">
{
{ data.label }}
</div>
</template>
</sgOvalMenu>
</div>
</template>
<script>
import sgOvalMenu from "./sgOvalMenu";
export default {
name: 'sgBody',
components: { sgOvalMenu },
data() {
return {
ovalMenus: [
{ value: '1', label: '显示文本1', },
{ value: '2', label: '显示文本2', },
{ value: '3', label: '显示文本3', },
{ value: '4', label: '显示文本4', },
{ value: '5', label: '显示文本5', },
],
}
},
methods: {
clickOvalMenu(d) {
// console.log(`获取点击信息:`, JSON.stringify(d, null, 2));
},
}
};
</script>
<style lang="scss" scoped>
.sgBody {
display: flex;
justify-content: center;
align-items: center;
background-color: black;
.btn {
box-sizing: border-box;
padding: 10px 20px;
border-radius: 88px;
box-sizing: border-box;
border: 1px solid #409EFF;
box-shadow: 0 10px 30px #409EFFAA, 0 10px 30px #409EFF99 inset;
color: #409EFF;
&:hover {
box-shadow: 0 10px 30px #409EFFAA, 0 10px 30px #409EFF99 inset;
background-color: #409EFF;
color: black;
filter: brightness(1.3);
}
}
}
</style>