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

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-dev

1.2 获取高德地图API密钥

  1. 访问高德开放平台并注册开发者账号
  2. 进入控制台创建新应用
  3. 在"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.FC<FlightMapProps> = ({ origin, destination }) => { const mapContainerRef = useRef<HTMLDivElement>(null); const [mapInstance, setMapInstance] = useState<AMap.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?v=2.0&key=你的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 className="control-panel"> <label> 动画速度: <input type="range" min="0.5" max="3" step="0.1" value={animationSpeed} onChange={(e) => setAnimationSpeed(parseFloat(e.target.value))} /> {animationSpeed}x </label> </div>

4.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.FC<FlightMapProps> = ({ origin, destination }) => { const mapContainerRef = useRef<HTMLDivElement>(null); const [mapInstance, setMapInstance] = useState<AMap.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?v=2.0&key=你的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 className="map-container" ref={mapContainerRef}> <div className="control-panel"> <button onClick={() => setIsDarkMode(!isDarkMode)}> {isDarkMode ? '浅色模式' : '深色模式'} </button> <button onClick={() => setShowRoute(!showRoute)}> {showRoute ? '隐藏路线' : '显示路线'} </button> <label> 动画速度: <input type="range" min="0.5" max="3" step="0.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 class="marker start-marker">起点</div>', offset: new AMap.Pixel(-15, -15), map }); // 终点标记 new AMap.Marker({ position: destination, content: '<div class="marker end-marker">终点</div>', offset: new AMap.Pixel(-15, -15), map }); };
http://www.jsqmd.com/news/493423/

相关文章:

  • ZXPInstaller:跨平台Adobe插件安装利器,让创意工作流无缝衔接
  • 【实战】Godot VSCode联调:从零搭建高效脚本工作流
  • Chatbot Arena 评价标准解析:如何构建高效自动化评估体系
  • Asian Beauty Z-Image Turbo 模型压缩与加速:在边缘设备部署的探索
  • 春联生成模型-中文-base问题解决:部署常见错误与解决方法汇总
  • 从零开始:在Qt项目中优雅地使用系统图标(QIcon::fromTheme详解)
  • Janus-Pro-7B在工业物联网(IIoT)的应用:设备仪表盘图像智能诊断
  • 实战指南:基于OpenCV与RTSP协议,轻松接入海康萤石网络摄像头视频流
  • 使用Git-RSCLIP优化MobaXterm远程工作体验
  • 利用SmolVLA自动化生成技术文档:UML图转文字说明
  • internlm2-chat-1.8b效果实测:中文成语接龙+文化背景解释趣味能力展示
  • Nacos Docker 安装文档 (MacBook Pro M2)
  • BEYOND REALITY Z-Image进阶技巧:两个核心参数如何调出最佳效果?
  • 实测造相-Z-Image:RTX 4090加持,4步快速生成高清写实图像效果惊艳
  • 色彩管理与显示优化:让你的NVIDIA显卡呈现真实色彩
  • 松下A6BE伺服电机增益调整与振动抑制:如何通过自动调整功能提升系统稳定性
  • 紫光同创PDS在线仿真:从Bit流生成到防优化实战
  • 解决6818开发板 syntax error: unexpected word的问题
  • Android Studio汉化包安装指南:从下载到重启的完整流程
  • 【统计检验】F检验与F分布
  • 告别环境配置烦恼!PyTorch 2.7 一键部署教程,新手5分钟搞定GPU环境
  • Spring Boot实战:5种HTTP客户端连接池配置对比(附完整代码)
  • YOLOv11优化全景图:从模块革新到部署实战,200+顶会方案融合与工程化指南
  • Blender高效渲染实战:HDR环境光与立方盒反射烘焙技巧
  • 人脸重建开源项目实测:cv_resnet50_face-reconstruction在国产昇腾NPU适配可能性探讨
  • DataV实战:如何用dv-scroll-board打造带分数预警的排名轮播表(附完整CSS代码)
  • 零基础上手PP-DocLayoutV3:3步完成文档版面分析,小白也能轻松搞定
  • Qwen2.5-72B-Instruct-GPTQ-Int4部署:vLLM量化精度损失实测分析
  • Vue3知识点总结
  • 树莓派4B安装Miniconda踩坑实录:从下载到配置Python3.6环境的完整指南