Canvas 绘制曲线并实现鼠标点击高亮效果

📅 2026/7/23 21:32:48 👁️ 阅读次数 📝 编程学习
Canvas 绘制曲线并实现鼠标点击高亮效果

使用 Canvas 绘制的曲线也可以实现鼠标点击高亮显示效果。由于 Canvas 是基于像素的绘制方式(不像 SVG 是基于矢量的),我们需要手动检测鼠标点击位置是否在曲线上,并重新绘制高亮效果。

实现方案

基本思路

  1. 存储所有曲线的路径数据
  2. 监听鼠标点击事件
  3. 检测点击位置是否在某条曲线上(使用点是否在路径上的检测算法)
  4. 重新绘制所有曲线,将选中的曲线高亮显示

完整代码示例

<!DOCTYPEhtml><htmllang="zh-CN"><head><metacharset="UTF-8"><metaname="viewport"content="width=device-width, initial-scale=1.0"><title>Canvas曲线点击高亮示例</title><style>body{font-family:Arial,sans-serif;display:flex;flex-direction:column;align-items:center;padding:20px;}h1{color:#333;}.container{margin-top:20px;border:1px solid #ddd;padding:10px;border-radius:5px;background-color:#f9f9f9;}canvas{border:1px solid #ccc;background-color:white;}.info{margin-top:15px;font-style:italic;color:#666;}</style></head><body><h1>Canvas曲线点击高亮示例</h1><p>点击任意一条曲线可以高亮显示它</p><divclass="container"><canvasid="curveCanvas"width="600"height="400"></canvas></div><divclass="info"id="curveInfo">点击一条曲线查看详细信息</div><script>document.addEventListener('DOMContentLoaded',function(){constcanvas=document.getElementById('curveCanvas');constctx=canvas.getContext('2d');constcurveInfo=document.getElementById('curveInfo');// 存储所有曲线的信息constcurves=[{name:"曲线1: 蓝色正弦波",color:"#3498db",highlightColor:"#ff9900",path:createSineWavePath(ctx,50,200,500,150,2)},{name:"曲线2: 绿色方波",color:"#2ecc71",highlightColor:"#27ae60",path:createSquareWavePath(ctx,50,100,500,200)},{name:"曲线3: 红色三角波",color:"#e74c3c",highlightColor:"#c0392b",path:createTriangleWavePath(ctx,50,300,500,200)}];letselectedCurveIndex=-1;// 当前选中的曲线索引// 绘制所有曲线functiondrawCurves(){// 清空画布ctx.clearRect(0,0,canvas.width,canvas.height);// 绘制背景网格drawGrid();// 绘制所有曲线curves.forEach((curve,index)=>{if(index===selectedCurveIndex){// 高亮显示选中的曲线ctx.strokeStyle=curve.highlightColor;ctx.lineWidth=4;}else{// 普通显示其他曲线ctx.strokeStyle=curve.color;ctx.lineWidth=2;}ctx.beginPath();ctx.moveTo(curve.path[0].x,curve.path[0].y);for(leti=1;i<curve.path.length;i++){ctx.lineTo(curve.path[i].x,curve.path[i].y);}ctx.stroke();});}// 绘制背景网格functiondrawGrid(){ctx.strokeStyle="#eee";ctx.lineWidth=1;// 垂直线for(letx=50;x<=550;x+=100){ctx.beginPath();ctx.moveTo(x,0);ctx.lineTo(x,canvas.height);ctx.stroke();}// 水平线for(lety=50;y<=350;y+=100){ctx.beginPath();ctx.moveTo(0,y);ctx.lineTo(canvas.width,y);ctx.stroke();}}// 创建正弦波路径functioncreateSineWavePath(ctx,startX,startY,width,height,cycles){constpath=[];constpoints=100;for(leti=0;i<=points;i++){constx=startX+(width/points)*i;consty=startY+Math.sin((i/points)*cycles*Math.PI*2)*height/2;path.push({x,y});}returnpath;}// 创建方波路径functioncreateSquareWavePath(ctx,startX,startY,width,height){constpath=[];constsegments=10;constsegmentWidth=width/segments;for(leti=0;i<=segments;i++){constx=startX+i*segmentWidth;lety;if(i%2===0){y=startY-height/2;}else{y=startY+height/2;}path.push({x,y});}returnpath;}// 创建三角波路径functioncreateTriangleWavePath(ctx,startX,startY,width,height){constpath=[];constsegments=10;constsegmentWidth=width/segments;for(leti=0;i<=segments;i++){constx=startX+i*segmentWidth;constprogress=i/segments;lety;if(progress<0.5){y=startY+(progress*2-0.5)*height;}else{y=startY+(1.5-progress*2)*height;}path.push({x,y});}returnpath;}// 检测点是否在路径上(简化版,适用于连续曲线)functionisPointOnPath(point,path,tolerance=5){for(leti=0;i<path.length-1;i++){constp1=path[i];constp2=path[i+1];// 计算点到线段的距离constdistance=pointToSegmentDistance(point,p1,p2);if(distance<=tolerance){returntrue;}}returnfalse;}// 计算点到线段的距离functionpointToSegmentDistance(point,p1,p2){constA=point.x-p1.x;constB=point.y-p1.y;constC=p2.x-p1.x;constD=p2.y-p1.y;constdot=A*C+B*D;constlen_sq=C*C+D*D;letparam=-1;if(len_sq!==0){param=dot/len_sq;}letxx,yy;if(param<0){xx=p1.x;yy=p1.y;}elseif(param>1){xx=p2.x;yy=p2.y;}else{xx=p1.x+param*C;yy=p1.y+param*D;}constdx=point.x-xx;constdy=point.y-yy;returnMath.sqrt(dx*dx+dy*dy);}// 初始化绘制drawCurves();// 鼠标点击事件处理canvas.addEventListener('click',function(e){constrect=canvas.getBoundingClientRect();constmouseX=e.clientX-rect.left;constmouseY=e.clientY-rect.top;letfound=false;// 检查是否点击了某条曲线for(leti=0;i<curves.length;i++){constpoint={x:mouseX,y:mouseY};if(isPointOnPath(point,curves[i].path)){selectedCurveIndex=i;curveInfo.textContent=`已选择:${curves[i].name}`;found=true;break;}}// 如果没有点击任何曲线,取消选择if(!found){selectedCurveIndex=-1;curveInfo.textContent="点击一条曲线查看详细信息";}// 重新绘制drawCurves();});});</script></body></html>

代码说明

  1. 曲线存储:

    • 使用数组存储所有曲线信息,包括名称、颜色、高亮颜色和路径点
    • 每种曲线类型有对应的路径生成函数
  2. 绘制函数:

    • drawCurves()负责绘制所有曲线,根据选中状态应用不同样式
    • drawGrid()绘制背景网格作为参考
  3. 路径检测:

    • isPointOnPath()检测点是否在路径上(简化版,适用于连续曲线)
    • pointToSegmentDistance()计算点到线段的最短距离
  4. 交互处理:

    • 监听 canvas 的点击事件
    • 检测点击位置是否在某条曲线上
    • 更新选中状态并重新绘制

优化建议

  1. 性能优化:

    • 对于复杂曲线,可以减少路径点数量或使用更高效的检测算法
    • 可以使用requestAnimationFrame优化重绘
  2. 功能扩展:

    • 添加鼠标悬停效果
    • 支持多选曲线
    • 添加图例说明
    • 支持曲线动态修改
  3. 精确检测:

    • 对于更精确的检测,可以考虑使用 Canvas 的isPointInPath()方法(但需要为每条曲线重新创建路径对象)
    • 或者使用第三方库如paper.jsfabric.js提供更高级的交互功能

这个实现展示了 Canvas 实现曲线点击高亮的基本方法,你可以根据实际需求进一步优化和完善。