<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>Canvas 内凹弧形导航菜单(顶部内凹)</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background-color: #f0f0f0;
}
.nav-menu {
display: flex;
justify-content: space-around;
position: relative;
height: 100px;
z-index: 5;
color: white;
overflow: hidden;
margin: 0;
padding: 0 140px;
width: 1120px;
box-sizing: border-box;
}
.nav-menu li {
list-style-type: none;
padding:30px 20px 0;
text-align: center;
line-height: 60px;
width: 100%;
cursor: pointer;
transition: all 0.3s ease;
}
.nav-menu li.active a{
color: #000;
}
.nav-menu a {
color: white;
text-decoration: none;
}
canvas {
position: absolute;
top: 0;
left: 0;
pointer-events: none;
z-index: 1;
}
</style>
</head>
<body>
<ul class="nav-menu" id="navMenu">
<li><a href="#">企业使命</a></li>
<li><a href="#">企业愿景</a></li>
<li><a href="#">核心价值观</a></li>
<li><a href="#">企业精神</a></li>
</ul>
<canvas id="arcCanvas"></canvas>
<script>
document.addEventListener('DOMContentLoaded', function () {
const canvas = document.getElementById('arcCanvas');
const ctx = canvas.getContext('2d');
const navMenu = document.getElementById('navMenu');
const items = navMenu.querySelectorAll('li');
// ✅ 所有变量声明在最顶部,避免访问错误
let targetIndex = 0;
let currentLeft = 0;
let currentWidth = 0;
let currentCenterX = 0;
const ease = 0.15; // 缓动系数
let animationId = null;
let resizeTimer = null; // 用于防抖
// 设置 canvas 大小并重绘
function resizeCanvas() {
// 取消正在进行的动画,避免冲突
if (animationId) {
cancelAnimationFrame(animationId);
animationId = null;
}
// 重设 canvas 尺寸(会清空内容)
canvas.width = navMenu.offsetWidth;
canvas.height = navMenu.offsetHeight;
// 重新绘制当前状态
drawArc();
}
// 初始化第一个菜单项的位置
function initFirstItem() {
const first = items[0];
currentLeft = first.offsetLeft;
currentWidth = first.offsetWidth;
currentCenterX = first.offsetLeft + first.offsetWidth / 2;
}
// 绘制顶部内凹弧形 + 椭圆角
function drawArc() {
const depth = 15; // 凹陷深度
const cornerRadius = 30; // 圆角/椭圆角半径
const midWidth = currentWidth / 2; // 中间凹陷宽度
const width = currentWidth;
const left = currentLeft;
const right = left + width;
const centerX = currentCenterX;
const midStartX = centerX - midWidth / 2;
const midEndX = centerX + midWidth / 2;
const ctrlY = depth * 3; // 控制点 Y,决定凹陷弧度
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
// 左上角椭圆角:从 (0, cornerRadius) 开始,用 arcTo 画到 (cornerRadius, 0)
ctx.moveTo(0, cornerRadius);
ctx.arcTo(0, 0, cornerRadius, 0, cornerRadius);
// 连接到菜单项左边缘
ctx.lineTo(left, 0);
// 左侧小凹陷圆角
const cornerEndLeft = left + 2 * cornerRadius;
ctx.quadraticCurveTo(
left + cornerRadius, 0,
cornerEndLeft, depth
);
// 连接到中间凹陷起点
if (cornerEndLeft < midStartX) {
ctx.lineTo(midStartX, depth);
}
// 中间下凹弧形
ctx.quadraticCurveTo(
centerX, ctrlY,
midEndX, depth
);
// 连接到右侧凹陷起点
const cornerStartRight = right - 2 * cornerRadius;
if (midEndX < cornerStartRight) {
ctx.lineTo(cornerStartRight, depth);
}
// 右侧小凹陷圆角
ctx.quadraticCurveTo(
right - cornerRadius, 0,
right, 0
);
// 向右到右上角起始点
ctx.lineTo(canvas.width - cornerRadius, 0);
// 右上角椭圆角
ctx.arcTo(canvas.width, 0, canvas.width, cornerRadius, cornerRadius);
// 向下到右下角
ctx.lineTo(canvas.width, canvas.height);
// 向左到左下角
ctx.lineTo(0, canvas.height);
// 向上闭合到起点
ctx.lineTo(0, cornerRadius);
ctx.closePath();
// 填充颜色
ctx.fillStyle = 'rgba(255, 0, 0, 1)';
ctx.fill();
}
// 动画更新函数(缓动)
function update() {
const targetItem = items[targetIndex];
const targetLeft = targetItem.offsetLeft;
const targetWidth = targetItem.offsetWidth;
const targetCenterX = targetLeft + targetWidth / 2;
let changed = false;
const dxLeft = targetLeft - currentLeft;
if (Math.abs(dxLeft) > 0.5) {
currentLeft += dxLeft * ease;
changed = true;
} else {
currentLeft = targetLeft;
}
const dxWidth = targetWidth - currentWidth;
if (Math.abs(dxWidth) > 0.5) {
currentWidth += dxWidth * ease;
changed = true;
} else {
currentWidth = targetWidth;
}
const dxCenter = targetCenterX - currentCenterX;
if (Math.abs(dxCenter) > 0.5) {
currentCenterX += dxCenter * ease;
changed = true;
} else {
currentCenterX = targetCenterX;
}
drawArc();
if (changed) {
animationId = requestAnimationFrame(update);
}
}
// 设置激活项
function setActive(index) {
// 移除所有 active 类
items.forEach(item => item.classList.remove('active'));
// 添加当前类
items[index].classList.add('active');
if (targetIndex === index) return;
targetIndex = index;
if (animationId) {
cancelAnimationFrame(animationId);
}
animationId = requestAnimationFrame(update);
}
// 鼠标移动检测
navMenu.addEventListener('mousemove', function (e) {
for (let i = 0; i < items.length; i++) {
const rect = items[i].getBoundingClientRect();
if (e.clientX >= rect.left && e.clientX <= rect.right) {
setActive(i);
return;
}
}
});
// 鼠标离开菜单,回到第一个
navMenu.addEventListener('mouseleave', function () {
setActive(0);
});
// 👇 防抖 resize 监听
window.addEventListener('resize', function () {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(resizeCanvas, 100);
});
// 初始化
resizeCanvas(); // 设置 canvas 大小
initFirstItem(); // 初始化位置
drawArc(); // 初始绘制
setActive(0); // 激活第一个
});
</script>
</body>
</html>
Canvas 内凹弧形导航菜单(顶部内凹)
最新推荐文章于 2025-08-25 18:10:02 发布