Canvas 内凹弧形导航菜单(顶部内凹)

<!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>

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值