目的
绘制弧线,菜单沿弧线分布,根据公式计算位置
公式:二次贝塞尔曲线
效果图
源码
弧线组件
<template>
<div class="svg">
<svg :width="s.width.value" :height="s.height.value">
<defs>
<linearGradient id="color">
<stop
offset="50%"
:style="`stop-color: ${s.color.value}; stop-opacity: 0.1`"
/>
<stop
offset="100%"
:style="`stop-color: ${s.color.value}; stop-opacity: 0.8`"
/>
</linearGradient>
</defs>
<path
:d="`
M ${f(s.p1)}
Q ${f(s.cp1)} ${f(s.p2)}
T ${f(s.p2)}
Q ${f(s.cp2)} ${f(s.p1)} Z`"
fill="url(#color)"
></path>
</svg>
</div>
</template>
<script>
import _ from "lodash";
import { computed, reactive, ref } from "vue";
/**
* 二次贝塞尔曲线
* ————————————————
* 版权声明:本文为CSDN博主「愚舜」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
* 原文链接:https://blog.csdn.net/first_shun/article/details/107346329
*/
function twoOrderBezier(t, p1, cp, p2) {
//参数分别是t,起始点,控制点和终点
var [x1, y1] = p1,
[cx, cy] = cp,
[x2, y2] = p2;
var x = (1 - t) * (1 - t) * x1 + 2 * t * (1 - t) * cx + t * t * x2,
y = (1 - t) * (1 - t) * y1 + 2 * t * (1 - t) * cy + t * t * y2;
return [x, y];
}
export default {
props: ["width", "height", "color", "borderWidth"],
setup(props) {
let s = {
width: computed(() => {
if (props.width) return props.width;
if (props.height) return props.height / 10;
return 20;
}),
height: computed(() => {
if (props.height) return props.height;
if (props.width) return props.width * 10;
return 200;
}),
color: computed(() => props.color || "blue"),
borderWidth: computed(() => props.borderWidth || (s.width.value / 10)),
p1: ref([0, 0]),
cp1: computed(() => [s.width.value * 2, s.height.value / 2]),
cp2: computed(() => [s.width.value * 2 - s.borderWidth.value, s.height.value / 2]),
cp3: computed(() => [s.width.value * 2 - s.borderWidth.value / 2, s.height.value / 2]),
p2: computed(() => [0, s.height.value]),
};
return {
s,
getCoordinate: (t) => {
return twoOrderBezier(t, s.p1.value, s.cp3.value, s.p2.value);
},
f: (arr) => {
return _(arr.value).join(",");
},
};
},
};
</script>
<style>
.svg {
display: inline-block;
}
</style>
菜单打印组件
<template>
<div class="menu-box" :class="{ 'is-left': isLeft }" ref="box">
<my-svg class="menu-bg" ref="bg" v-bind="bgStyle"></my-svg>
<nav class="menu">
<li
class="menu-item"
v-for="item in menuList"
:key="item.id"
:style="{
top: `${item.y}px`,
[isLeft ? 'left' : 'right']: `${item.x}px`,
}"
>
<div class="menu-item-title">
{{ item.name }}
<div
class="menu-item-title-dot"
:style="{ background: fontColor }"
></div>
</div>
</li>
</nav>
</div>
</template>
<script setup>
import { computed, defineProps, ref, onMounted } from "vue";
// import mySvg from "/@/components/svg.vue";
import mySvg from "./svg.vue";
import _ from "lodash";
const box = ref(null);
const bg = ref(null);
const isNotInited = ref(true);
const props = defineProps({
list: {
type: Array,
default() {
return [];
},
},
isLeft: {
type: Boolean,
default: false,
},
});
const fontColor = computed(() => {
if (isNotInited.value) return "blue";
let color = box.value.computedStyleMap().get("color").toString();
return color;
});
const menuList = computed(() => {
if (isNotInited.value) return [];
const base = 1 / (props.list.length + 1);
return props.list.map((x, i) => {
let bgW = bg.value.s.width.value;
let coordinate = bg.value.getCoordinate(base * (i + 1));
return { ...x, x: bgW - coordinate[0], y: coordinate[1] };
});
});
const bgStyle = computed(() => {
let s = { color: fontColor.value };
if (!isNotInited.value) {
const boxH = box.value.offsetHeight;
_.assign(s, { height: boxH });
}
return s;
});
onMounted(() => {
isNotInited.value = false;
});
</script>
<style scoped>
.menu-box {
display: flex;
position: relative;
height: 500px;
width: 500px;
}
.menu {
flex: 1;
}
.menu-bg {
position: absolute;
top: 0;
right: 0px;
height: 100%;
}
.menu-box.is-left .menu-bg {
left: 0px;
right: initial;
transform: scale(-1, 1);
}
.menu-item {
position: absolute;
list-style: none;
transform: translateY(-50%);
cursor: pointer;
}
.menu-item-title-dot {
display: inline-block;
width: 1em;
height: 1em;
padding: 0.2em;
border-radius: 50%;
margin-left: 1em;
margin-right: -0.5em;
vertical-align: middle;
float: right;
}
.menu-box.is-left .menu-item-title-dot{
float: left;
margin-left: -0.5em;
margin-right: 1em;
}
.menu-item-title:hover {
color: orange;
}
.menu-item-title:hover .menu-item-title-dot {
background: radial-gradient(
orange 40%,
transparent 45%,
transparent 50%,
orange 60%,
orange 100%
) !important;
}
</style>
结语
不知道怎么封装更好,谁要是能给封装好,记得给贴个链接,如果有现成的组件,也请推荐一下,没有头绪,心不够静,😔
用的是vue3,如果不能用,可以参照下思路,能帮到谁那我就太高兴了。
更新2022-10-25
menu组件添加isLeft
属性