Canvas 绘制曲线并实现鼠标点击高亮效果
使用 Canvas 绘制的曲线也可以实现鼠标点击高亮显示效果。由于 Canvas 是基于像素的绘制方式(不像 SVG 是基于矢量的),我们需要手动检测鼠标点击位置是否在曲线上,并重新绘制高亮效果。
实现方案
基本思路
- 存储所有曲线的路径数据
- 监听鼠标点击事件
- 检测点击位置是否在某条曲线上(使用点是否在路径上的检测算法)
- 重新绘制所有曲线,将选中的曲线高亮显示
完整代码示例
<!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>代码说明
曲线存储:
- 使用数组存储所有曲线信息,包括名称、颜色、高亮颜色和路径点
- 每种曲线类型有对应的路径生成函数
绘制函数:
drawCurves()负责绘制所有曲线,根据选中状态应用不同样式drawGrid()绘制背景网格作为参考
路径检测:
isPointOnPath()检测点是否在路径上(简化版,适用于连续曲线)pointToSegmentDistance()计算点到线段的最短距离
交互处理:
- 监听 canvas 的点击事件
- 检测点击位置是否在某条曲线上
- 更新选中状态并重新绘制
优化建议
性能优化:
- 对于复杂曲线,可以减少路径点数量或使用更高效的检测算法
- 可以使用
requestAnimationFrame优化重绘
功能扩展:
- 添加鼠标悬停效果
- 支持多选曲线
- 添加图例说明
- 支持曲线动态修改
精确检测:
- 对于更精确的检测,可以考虑使用 Canvas 的
isPointInPath()方法(但需要为每条曲线重新创建路径对象) - 或者使用第三方库如
paper.js或fabric.js提供更高级的交互功能
- 对于更精确的检测,可以考虑使用 Canvas 的
这个实现展示了 Canvas 实现曲线点击高亮的基本方法,你可以根据实际需求进一步优化和完善。
