1:需求是这样的,需要在openlayers地图加载的管道上添加双向燕尾水流动画效果如下
openlayers
代码如下:
// 启动水流动画(蓝底带箭尾效果)startFlowAnimation(){if(this.flowPathFeatures.length===0)return;// 清理之前的动画if(this.flowAnimationFrame){cancelAnimationFrame(this.flowAnimationFrame);this.flowAnimationFrame=null;}if(this.flowAnimationInterval){clearInterval(this.flowAnimationInterval);this.flowAnimationInterval=null;}// 获取第一个管道特征的起始偏移量constfirstFeature=this.flowPathFeatures[0];conststartOffset=firstFeature?firstFeature.get("startOffset")||0:0;this.flowPx=0;// 优化:使用固定时间间隔更新,降低CPU使用率constanimate=()=>{// 每帧移动1.2像素,形成可见的水流流动效果(约14px/s)this.flowPx=this.flowPx+1.2;constallFlowFeatures=[];// 为每条管道路径创建蓝底带箭尾的流动效果this.flowPathFeatures.forEach((feature)=>{constcoords=feature.get("flowCoords");consttotalLength=feature.get("totalLength");constsegments=feature.get("segments");if(!coords||coords.length<2||totalLength<=0)return;constisBranch=feature.get("isBranch");constpipeColor=isBranch?this.pipeColors.branchPipe:this.pipeColors.mainPipe;constpipeColorRgba=pipeColor.replace(")",", 0.8)").replace("rgb","rgba");constpipeColorLight=isBranch?this.pipeColors.branchPipe:this.pipeColors.mainPipe;// 创建底线条(等宽)constbaseLineFeature=newFeature({geometry:newLineString(coords),});baseLineFeature.setStyle([// 底色(等宽)newStyle({stroke:newStroke({color:pipeColorRgba,width:isBranch?3:4,// 保持等宽lineCap:"round",lineJoin:"round",}),}),// 边框newStyle({stroke:newStroke({color:pipeColorLight,width:isBranch?3:4,lineCap:"round",lineJoin:"round",}),}),]);allFlowFeatures.push(baseLineFeature);// 获取该管道的起始偏移量constfeatureStartOffset=feature.get("startOffset")||0;constisYibiaoduan=feature.get("isYibiaoduan");constflowStartOffset=isYibiaoduan?0:featureStartOffset;// 使用自定义渲染器沿管道连续绘制交替颜色的燕尾箭头(头尾相连无空隙)constpipeFeature=newFeature({geometry:newLineString(coords),});// 燕尾箭头尺寸参数(像素单位)constheadLen=isBranch?12:12;// 箭头总长度(一个完整周期,也是步进间距)constbodyLen=isBranch?8:10;// 矩形主体长度consttipLen=isBranch?12:14;// 箭头尖端位置(= headLen)consthalfWidth=isBranch?3:3;// 半宽度constnotchDepth=isBranch?3:3;// 尾部V型缺口深度pipeFeature.setStyle(newStyle({renderer:(pixelCoordinates,state)=>{constctx=state.context;constpixelCoords=pixelCoordinates;if(!pixelCoords||pixelCoords.length<2)return;// 计算每个像素段的长度、方向和累积距离constsegs=[];constcumDist=[0];lettotalLen=0;for(leti=0;i<pixelCoords.length-1;i++){constdx=pixelCoords[i+1][0]-pixelCoords[i][0];constdy=pixelCoords[i+1][1]-pixelCoords[i][1];constlen=Math.sqrt(dx*dx+dy*dy);segs.push({x1:pixelCoords[i][0],y1:pixelCoords[i][1],x2:pixelCoords[i+1][0],y2:pixelCoords[i+1][1],len,dx,dy,});totalLen+=len;cumDist.push(totalLen);}if(totalLen<=0)return;// 流动偏移量(像素),箭头沿管道方向从起点向终点移动constflowShift=(((flowStartOffset*headLen+this.flowPx)%headLen)+headLen)%headLen;// 沿管道按箭头长度步进,绘制离散燕尾箭头(头尾相接)letpos=0;letarrowIdx=0;while(pos<totalLen){constdrawPos=pos+flowShift;if(drawPos>=totalLen)break;// 找到该位置所在的线段letsi=0;for(lets=0;s<segs.length;s++){if(cumDist[s+1]>=drawPos){si=s;break;}}constseg=segs[si];constt=seg.len>0?(drawPos-cumDist[si])/seg.len:0;constcx=seg.x1+seg.dx*t;constcy=seg.y1+seg.dy*t;// 方向角度(管道走向)constangle=Math.atan2(seg.dy,seg.dx);// 箭头必须完全在当前线段内,否则跳过(防止在管道转角处跑出管道)constarrowHalfLen=tipLen/2;constdistToSegEnd=cumDist[si+1]-drawPos;if(distToSegEnd<headLen/2||drawPos<headLen/2){pos+=headLen;arrowIdx++;continue;}// 交替颜色:使用管道主色和白色constisMainColor=arrowIdx%2===0;// 将管道颜色从 hex 转换为 rgbconsthexToRgb=(hex)=>{constresult=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);returnresult?{r:parseInt(result[1],16),g:parseInt(result[2],16),b:parseInt(result[3],16)}:{r:100,g:200,b:255};};constmainRgb=hexToRgb(pipeColor);constr=isMainColor?mainRgb.r:255;constg=isMainColor?mainRgb.g:255;constb=isMainColor?mainRgb.b:255;// 绘制燕尾箭头(6个顶点,tip指向管道方向)// 形状:[tip]──[body]──[notch]// tip尖端在前方,notch V型缺口在后方ctx.save();ctx.translate(cx,cy);// 角度基于像素坐标计算(Y轴向下),直接旋转即可对齐管道方向ctx.rotate(angle);ctx.beginPath();ctx.moveTo(tipLen,0);// 箭头尖端(最前方)ctx.lineTo(bodyLen,-halfWidth);// 右上外角ctx.lineTo(0,-halfWidth);// 左上角(尾部上沿)ctx.lineTo(notchDepth,0);// V型缺口尖端(向内凹陷)ctx.lineTo(0,halfWidth);// 左下角(尾部下沿)ctx.lineTo(bodyLen,halfWidth);// 右下外角ctx.closePath();ctx.fillStyle=`rgba(${r},${g},${b}, 0.9)`;ctx.fill();// 浅蓝色边框ctx.strokeStyle="rgba(173, 216, 230, 0.8)";ctx.lineWidth=1;ctx.stroke();ctx.restore();pos+=headLen;arrowIdx++;}},}));allFlowFeatures.push(pipeFeature);});// 更新水流动画图层this.flowAnimationLayer.getSource().clear();this.flowAnimationLayer.getSource().addFeatures(allFlowFeatures);};// 使用setInterval控制帧率(约12fps)this.flowAnimationInterval=setInterval(animate,85);},