React + 高德地图:5分钟实现动态路线飞行动画(附完整代码)

React + 高德地图:5分钟实现动态路线飞行动画(附完整代码) React 高德地图5分钟实现动态路线飞行动画附完整代码在当今数据可视化和位置服务应用中动态路线展示已成为提升用户体验的关键要素。无论是物流追踪、出行导航还是旅游路线规划流畅的飞行动画效果都能让用户更直观地理解路径信息。本文将带你快速掌握如何在React项目中集成高德地图并实现专业级的动态路线飞行动画效果。1. 环境准备与基础配置1.1 创建React项目与安装依赖首先确保你的开发环境已经准备好Node.js建议版本16和npm/yarn。创建一个新的React项目npx create-react-app amap-flight-demo --template typescript cd amap-flight-demo安装高德地图JavaScript API所需的类型声明文件TypeScript项目需要npm install types/amap-js-api --save-dev1.2 获取高德地图API密钥访问高德开放平台并注册开发者账号进入控制台创建新应用在Web端(JS API)服务中获取API Key提示开发环境下可以使用测试密钥但生产环境务必申请正式密钥并设置安全域名限制2. 地图基础集成与初始化2.1 组件结构与状态管理创建一个新的地图组件FlightMap.tsx设置基础状态import React, { useEffect, useRef, useState } from react; interface FlightMapProps { origin: [number, number]; destination: [number, number]; } const FlightMap: React.FCFlightMapProps ({ origin, destination }) { const mapContainerRef useRefHTMLDivElement(null); const [mapInstance, setMapInstance] useStateAMap.Map | null(null); const [isDarkMode, setIsDarkMode] useState(false); const [animationSpeed, setAnimationSpeed] useState(1); // 后续代码将在这里添加 };2.2 动态加载高德地图API使用动态脚本加载技术确保地图API按需加载useEffect(() { if (!window.AMap) { window._AMapSecurityConfig { securityJsCode: 你的安全密钥 }; const script document.createElement(script); script.src https://webapi.amap.com/maps?v2.0key你的API密钥; script.async true; script.onload initMap; document.head.appendChild(script); return () { document.head.removeChild(script); }; } else { initMap(); } }, []);2.3 地图实例初始化实现initMap函数创建地图实例const initMap () { if (!mapContainerRef.current) return; const map new AMap.Map(mapContainerRef.current, { viewMode: 3D, zoom: 10, center: origin, mapStyle: isDarkMode ? amap://styles/dark : amap://styles/normal }); setMapInstance(map); addBasicControls(map); };3. 动态路线与飞行动画实现3.1 绘制路线折线创建绘制路径的函数支持自定义样式const drawRoute (map: AMap.Map) { const path [origin, destination]; return new AMap.Polyline({ path, strokeColor: #1890ff, strokeWeight: 4, strokeOpacity: 0.8, strokeDasharray: [10, 5], map }); };3.2 实现飞机标记动画创建飞机标记并实现平滑移动效果const createFlightAnimation (map: AMap.Map, path: AMap.LngLat[]) { const marker new AMap.Marker({ position: path[0], icon: new AMap.Icon({ image: https://webapi.amap.com/images/car.png, size: new AMap.Size(36, 36), imageOffset: new AMap.Pixel(0, 0) }), offset: new AMap.Pixel(-18, -18), autoRotation: true }); marker.setMap(map); let currentIndex 0; const totalPoints path.length; const animationDuration 5000; // 5秒完成动画 const animate () { if (currentIndex totalPoints - 1) { currentIndex 0; marker.setPosition(path[0]); } const startPoint path[currentIndex]; const endPoint path[currentIndex 1]; // 计算两点之间的插值 const steps 100; const latStep (endPoint.lat - startPoint.lat) / steps; const lngStep (endPoint.lng - startPoint.lng) / steps; let step 0; const move () { if (step steps) { const lat startPoint.lat latStep * step; const lng startPoint.lng lngStep * step; marker.setPosition([lng, lat]); // 计算方向角度 if (step steps) { const angle calculateAngle( [lng, lat], [startPoint.lng lngStep * (step 1), startPoint.lat latStep * (step 1)] ); marker.setRotation(angle); } step; setTimeout(move, animationDuration / steps / animationSpeed); } else { currentIndex; animate(); } }; move(); }; animate(); };3.3 方向角度计算实现精确的方向角度计算函数const calculateAngle (start: [number, number], end: [number, number]): number { const lat1 (start[1] * Math.PI) / 180; const lng1 (start[0] * Math.PI) / 180; const lat2 (end[1] * Math.PI) / 180; const lng2 (end[0] * Math.PI) / 180; const y Math.sin(lng2 - lng1) * Math.cos(lat2); const x Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1) * Math.cos(lat2) * Math.cos(lng2 - lng1); let angle (Math.atan2(y, x) * 180) / Math.PI; return (angle 360) % 360; };4. 高级功能与性能优化4.1 动画速度控制添加速度控制滑块提升交互体验div classNamecontrol-panel label 动画速度: input typerange min0.5 max3 step0.1 value{animationSpeed} onChange{(e) setAnimationSpeed(parseFloat(e.target.value))} / {animationSpeed}x /label /div4.2 内存管理与性能优化确保组件卸载时清理地图资源useEffect(() { return () { if (mapInstance) { mapInstance.destroy(); } }; }, [mapInstance]);4.3 响应式设计适配添加CSS确保地图容器适应不同屏幕尺寸.map-container { width: 100%; height: 60vh; min-height: 400px; position: relative; } .control-panel { position: absolute; top: 20px; right: 20px; z-index: 10; background: rgba(255, 255, 255, 0.8); padding: 10px; border-radius: 4px; box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1); }5. 完整实现与效果增强5.1 完整组件代码整合所有功能的完整实现import React, { useEffect, useRef, useState } from react; import ./FlightMap.css; interface FlightMapProps { origin: [number, number]; destination: [number, number]; } const FlightMap: React.FCFlightMapProps ({ origin, destination }) { const mapContainerRef useRefHTMLDivElement(null); const [mapInstance, setMapInstance] useStateAMap.Map | null(null); const [isDarkMode, setIsDarkMode] useState(false); const [animationSpeed, setAnimationSpeed] useState(1); const [showRoute, setShowRoute] useState(true); // 初始化地图 const initMap () { if (!mapContainerRef.current || mapInstance) return; const map new AMap.Map(mapContainerRef.current, { viewMode: 3D, zoom: 10, center: origin, mapStyle: isDarkMode ? amap://styles/dark : amap://styles/normal }); setMapInstance(map); addBasicControls(map); if (showRoute) { const polyline drawRoute(map); createFlightAnimation(map, polyline.getPath()); } }; // 绘制路线 const drawRoute (map: AMap.Map) { const path [origin, destination]; return new AMap.Polyline({ path, strokeColor: #1890ff, strokeWeight: 4, strokeOpacity: 0.8, strokeDasharray: [10, 5], map }); }; // 创建飞行动画 const createFlightAnimation (map: AMap.Map, path: AMap.LngLat[]) { // ...前面实现的动画代码... }; // 添加基础控件 const addBasicControls (map: AMap.Map) { AMap.plugin([AMap.ToolBar, AMap.Scale], () { map.addControl(new AMap.ToolBar()); map.addControl(new AMap.Scale()); }); }; // 加载地图API useEffect(() { if (!window.AMap) { window._AMapSecurityConfig { securityJsCode: 你的安全密钥 }; const script document.createElement(script); script.src https://webapi.amap.com/maps?v2.0key你的API密钥; script.async true; script.onload initMap; document.head.appendChild(script); return () { document.head.removeChild(script); }; } else { initMap(); } }, []); // 响应式更新 useEffect(() { if (!mapInstance) return; mapInstance.setMapStyle(isDarkMode ? amap://styles/dark : amap://styles/normal); if (showRoute) { const polyline drawRoute(mapInstance); createFlightAnimation(mapInstance, polyline.getPath()); } else { mapInstance.clearMap(); addBasicControls(mapInstance); } }, [isDarkMode, showRoute, animationSpeed]); return ( div classNamemap-container ref{mapContainerRef} div classNamecontrol-panel button onClick{() setIsDarkMode(!isDarkMode)} {isDarkMode ? 浅色模式 : 深色模式} /button button onClick{() setShowRoute(!showRoute)} {showRoute ? 隐藏路线 : 显示路线} /button label 动画速度: input typerange min0.5 max3 step0.1 value{animationSpeed} onChange{(e) setAnimationSpeed(parseFloat(e.target.value))} / {animationSpeed}x /label /div /div ); }; export default FlightMap;5.2 使用自定义飞机图标替换默认图标为自定义飞机图标const planeIcon new AMap.Icon({ image: path/to/your/plane-icon.png, size: new AMap.Size(40, 40), imageSize: new AMap.Size(40, 40) }); const marker new AMap.Marker({ position: path[0], icon: planeIcon, offset: new AMap.Pixel(-20, -20), autoRotation: true, rotation: 0 });5.3 添加路径标记点在起点和终点添加特殊标记const addMarkers (map: AMap.Map) { // 起点标记 new AMap.Marker({ position: origin, content: div classmarker start-marker起点/div, offset: new AMap.Pixel(-15, -15), map }); // 终点标记 new AMap.Marker({ position: destination, content: div classmarker end-marker终点/div, offset: new AMap.Pixel(-15, -15), map }); };