背景:实现一个环形导航需求,没有找到合适的插件,决定手写一个,以下为环形文字和背景的主要逻辑,环形导航要配置多个即可
实现方案
文字旋转:用绝对定位 + transform来实现
背景圆弧:用css的clip-path来实现
文字旋转
首先确定圆弧的半径r和文字开始的角度startDeg,然后循环遍历出文字所在位置和旋转角度
// str为要显示的文字
// clockwise是否为顺时针
// startDeg为开始角度
// textDeg 为每个文字所占的角度
for (let i = 0; i < str.length; i++) {
const span = document.createElement('span');
span.innerText = str[i];
// 旋转角度
let deg = 0;
if (clockwise) {
deg = startDeg - i * textDeg;
} else {
deg = startDeg + i * textDeg;
}
// 旋转角度值,需要用到Math.PI进行计算
const angle = getAngle(deg);
// 计算元素定位位置
const left = `${Math.round(r + r * Math.cos(angle))}px`;
const top = `${Math.round(r - r * Math.sin(angle))}px`;
// 设置元素样式
span.setAttribute('style', `
width: ${fontSizeNum}px;
height: ${fontSizeNum}px;
line-height: ${fontSizeNum}px;
text-align: center;
font-size: ${fontSizeNum}px;
color: ${color};
position: absolute;
left: ${left};
top: ${top};
transform-origin: right top;
transform: rotate(${90 - deg - (i === 0 ? 0 : oneTextDeg)}deg);
z-index: 30000;
pointer-events: none; // 设置元素对鼠标事件不作反应
`);
}
圆弧背景
用到了css的clip-path 的path实现,参数跟svg的path一致,如下图:
需判断圆弧角度是否大于180度,大于180度large-arc-flag需设置为1 否则设置为0,不然切割出的来图形会不符合预期
// r圆弧半径
// with圆环的宽度
// startDeg 圆弧开始角度
// endDeg圆弧结束角度
const innerR = r - width;
// 外圆弧旋转角度值
const startAngle = getAngle(startDeg);
const endAngle = getAngle(endDeg);
// 计算外圆弧位置
const startLeft = `${Math.round(r + r * Math.cos(startAngle))}`;
const startTop = `${Math.round(r - r * Math.sin(startAngle))}`;
const endLeft = `${Math.round(r + r * Math.cos(endAngle))}`;
const endTop = `${Math.round(r - r * Math.sin(endAngle))}`;
// 计算内圆弧位置
const startLeftInner = `${Math.round(r + innerR * Math.cos(startAngle))}`;
const startTopInner = `${Math.round(r - innerR * Math.sin(startAngle))}`;
const endLeftInner = `${Math.round(r + innerR * Math.cos(endAngle))}`;
const endTopInner = `${Math.round(r - innerR * Math.sin(endAngle))}`;
// 创建dom元素
const div = document.createElement('div');
// 添加class属性
div.setAttribute('class', className);
const rotationDeg = Math.abs(endDeg - startDeg);
// 添加clip-path属性
const clipPathList = [
`M ${startLeft} ${startTop}`,
`A ${r} ${r} ${rotationDeg} ${rotationDeg > 180 ? 1 : 0} ${clockwise ? 1 : 0} ${endLeft} ${endTop}`,
`L ${endLeftInner} ${endTopInner}`,
`A ${innerR} ${innerR} ${rotationDeg} ${rotationDeg > 180 ? 1 : 0} ${clockwise ? 0 : 1} ${startLeftInner} ${startTopInner}`,
]
div.setAttribute('style', `
clip-path: path('${clipPathList.join(' ')}');
width: 100%;
height: 100%;
position: absolute;
z-index: 20000;
background-color: ${backgroundColor}`
);
// 设置背景颜色
div.style.backgroundColor = backgroundColor;
只有文字的效果
文字+背景图的效果
实现代码
如有其他实现方案或对代码的改进,欢迎各位大佬留言指导。