当前位置: 首页 > news >正文

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>

在这里插入图片描述

http://www.dtcms.com/a/350145.html

相关文章:

  • 基于MATLAB长时间序列遥感数据处理及在全球变化、物候提取、植被变绿与固碳分析等领域中的应用
  • 权限越权概念
  • centos7 安装coze
  • 【计算星座】2022-10-24
  • 普蓝超强承重越野移动机器人底盘轻松应对复杂路段
  • 《C++进阶:引用补充、内联函数与nullptr 核心用法》
  • 3 系统设计面试的框架
  • Odoo 企业版用户手册[新版]-前言 00.1-手册说明与使用指南
  • EasyClick 生成唯一设备码
  • SP95N65CTO:一款高性能650V SiC MOSFET的全面解析
  • 数据赋能(409)——大数据——合规性和伦理性原则
  • 强化学习基础总结
  • 《分布式系统跨服务数据一致性Bug深度复盘:从现象到本质的排查与破局》
  • 2025年优化算法:雪雁优化算法(Snow Geese Algorithm,SGA)
  • 2025 JVM 并发革命:虚拟线程与结构化并发,引领性能飞跃(35)
  • 京东前端社招面经
  • Pandas 高效数据处理:apply、向量化与分组
  • Qt——多媒体(音频、视频播放)
  • 艾利特石油管道巡检机器人:工业安全的智能守护者
  • 系统数据库
  • WRC2025 | 澳鹏亮相2025世界机器人大会,以数据之力赋能具身智能新纪元
  • 2025年9月计算机二级C++语言程序设计——选择题打卡Day6
  • 神经网络模型搭建及手写数字识别案例
  • 关于“程序=数据结构+算法”的深层认识
  • Java图形图像处理【双缓冲技术与游戏】【九】
  • Java全栈开发面试实战:从基础到微服务的深度解析
  • IDEA2022开启新版UI
  • 系统架构设计师备考第4天——计算机软件概述操作系统
  • Aligning Effective Tokens with Video Anomaly in Large Language Models
  • 储能防逆流:智能守护电网稳定的核心技术