React如何集成高德地图实现动态路线绘制、控件交互与深色模式切换
在前端开发中,地图功能的实现十分常见,而React与高德地图的结合能够打造出丰富且交互性强的地图应用。本文将详细介绍如何利用React实现高德地图的基础展示、深色模式切换、缩放控件的显示与隐藏,以及动态路线的绘制和飞行路径动画效果,帮助大家快速掌握相关技术要点。
一、搭建基础地图
在开始构建复杂的地图功能前,需要先实现地图的基本展示。这是后续所有功能的基础。
(一)组件定义与状态初始化
const mapContainerRef = useRef<HTMLDivElement>(null); const [isDarkMode, setIsDarkMode] = useState<boolean>(false);
上述代码使用useRef
创建了一个mapContainerRef
引用,这个引用用来获取地图容器对应的DOM元素。同时,通过useState
创建了isDarkMode
状态变量,它用于记录当前地图是否处于深色模式,初始值为false
,也就是默认是浅色模式。
(二)初始化地图函数
const initMap = () => { if (mapContainerRef.current) { const map = new window.AMap.Map(mapContainerRef.current, { zoom: 5, center: [110, 35], mapStyle: isDarkMode ? "amap://styles/dark" : "", }); map.setFitView(); } };
initMap
函数的作用是初始化地图实例。首先,它会检查mapContainerRef.current
是否存在,只有当这个引用对应的DOM元素已经挂载到页面上时,才会继续执行后续操作。接着,创建一个新的高德地图实例,设置地图的缩放级别为5
,中心点坐标为[110, 35]
,并且根据isDarkMode
的值来选择地图样式,如果是深色模式,就使用"amap://styles/dark"
样式,否则不设置特殊样式。最后,调用map.setFitView()
方法,这个方法会自动调整地图视图,让地图上的所有覆盖物都能完整显示在当前视图中,虽然此时还没有添加覆盖物,但这是一个通用的设置。
(三)使用useEffect处理副作用
useEffect(() => { window._AMapSecurityConfig = { securityJsCode: "api", }; const script = document.createElement("script"); script.src = "https://webapi.amap.com/maps?v=1.4.15&key=api"; script.async = true; document.body.appendChild(script); script.onload = initMap; return () => { document.body.removeChild(script); }; }, [isDarkMode]);
在React组件中,useEffect
用于处理副作用操作。这里,当组件挂载到页面上时,会执行一系列操作:
- 配置高德地图的安全密钥,设置
window._AMapSecurityConfig
对象中的securityJsCode
属性,这一步很关键,它确保了地图API的安全调用。 - 创建一个
<script>
标签,通过设置src
属性来引入高德地图的JS API,版本号为1.4.15
,同时别忘了将key
替换为你自己申请的API Key。async
属性设置为true
,表示这个脚本会在后台加载,不会阻塞页面的渲染。 - 将创建好的脚本标签添加到
document.body
中,这样就开始加载地图API了。 - 当脚本加载完成后,会触发
onload
事件,此时调用initMap
函数来初始化地图。 - 最后返回一个清理函数,当组件从页面上卸载时,这个函数会被执行,它的作用是从
document.body
中移除之前添加的脚本标签,防止内存泄漏。
这里[isDarkMode]
作为useEffect
的依赖数组,意味着只要isDarkMode
状态发生变化,useEffect
中的代码就会重新执行,这样就能实现当地图模式切换时,重新初始化地图并应用新的样式。
(四)渲染组件
return ( <div className="box"> <h2>重庆到成都的路线</h2> <button onClick={() => setIsDarkMode(!isDarkMode)}> {isDarkMode ? "切换到浅色模式" : "切换到深色模式"} </button> <div ref={mapContainerRef} style={{ width: "100%", height: "500px" }}></div> </div> );
在组件的返回部分,首先展示了一个标题重庆到成都的路线
,用于说明地图的主要内容。接着是一个按钮,点击这个按钮会调用setIsDarkMode(!isDarkMode)
函数,实现深色模式和浅色模式的切换,并且按钮上的文字会根据当前的模式状态进行相应的变化。最后,通过ref
属性将mapContainerRef
绑定到一个<div>
元素上,这个<div>
就是地图的显示容器,设置它的宽度为100%
,高度为500px
,这样地图才能正常显示在页面上。需要注意的是,一定要给这个<div>
设置宽度和高度,否则地图将无法显示。
二、添加缩放控件
在地图上添加缩放控件,可以让用户更方便地操作地图,放大或缩小地图视图。下面来看看如何实现这一功能。
(一)按钮控制显示/隐藏缩放控件
<button onClick={() => setToolbarVisible(!toolbarVisible)}> {toolbarVisible ? "隐藏缩放控件" : "显示缩放控件"} </button>
这段代码创建了一个按钮,当用户点击按钮时,会触发onClick
事件。在事件处理函数中,调用setToolbarVisible(!toolbarVisible)
,这行代码的作用是取反toolbarVisible
的当前值,从而实现缩放控件显示和隐藏状态的切换。同时,按钮上显示的文本也会根据toolbarVisible
的值进行变化,如果toolbarVisible
为true
,表示缩放控件当前是显示状态,按钮文本就显示为“隐藏缩放控件”;反之,如果toolbarVisible
为false
,按钮文本则显示为“显示缩放控件”。
(二)添加控件函数
const addControls = (map, toolbarVisible) => { if (!toolbarVisible) return; window.AMap.plugin(["AMap.ToolBar", "AMap.Scale"], () => { const controls = [ new window.AMap.ToolBar(), new window.AMap.Scale(), ]; controls.forEach((control) => { map.addControl(control); }); }); };
addControls
函数用于向地图添加缩放控件和比例尺。它接受两个参数,map
是地图实例,toolbarVisible
是一个布尔值,用于判断是否要显示这些控件。如果toolbarVisible
为false
,函数会直接返回,不执行后续添加控件的操作。
当toolbarVisible
为true
时,通过window.AMap.plugin
方法加载高德地图的AMap.ToolBar
和AMap.Scale
插件。这两个插件分别提供了缩放工具条和比例尺的功能。插件加载完成后,会执行回调函数。在回调函数中,创建一个包含缩放工具条和比例尺实例的数组controls
,然后使用forEach
方法遍历这个数组,将每个控件通过map.addControl(control)
添加到地图上。
三、定义坐标并创建折线
为了在地图上展示重庆到成都的路线,需要定义两个城市的坐标,并创建一条折线来连接它们。
(一)定义坐标
const beijing: [number, number] = [106.5516, 29.563]; const chengdu: [number, number] = [104.06579, 30.570462];
这里定义了两个常量beijing
和chengdu
,分别存储了重庆和成都的地理坐标。坐标由经度和纬度组成,这是在地图上定位一个地点的关键信息。
(二)创建折线
if (showPolyline) { const path = [beijing, chengdu]; const polyline = new window.AMap.Polyline({ path: path, strokeColor: "#FF0000", strokeWeight: 5, strokeOpacity: 0.8, map: map, }); map.setFitView([polyline]); addFlightAnimation(map, polyline); }
当showPolyline
为true
时,会执行创建折线的操作。首先,创建一个包含重庆和成都坐标的路径数组path
。然后,使用window.AMap.Polyline
创建一个折线对象polyline
,并设置它的多个属性:
path
:指定折线的路径,也就是前面创建的path
数组。strokeColor
:设置折线的颜色为红色(#FF0000
)。strokeWeight
:设置折线的宽度为5
像素。strokeOpacity
:设置折线的透明度为0.8
,取值范围是[0, 1]
,数值越接近1
越不透明。map
:将创建好的折线添加到地图实例中。
创建好折线后,调用map.setFitView([polyline])
方法,让地图自动调整视图,使整个折线都能完整显示在地图上。最后,调用addFlightAnimation(map, polyline)
函数,为这条折线添加飞行路径动画效果。
(三)添加飞行路径动画
const addFlightAnimation = (map: any, polyline: any) => { const marker = new window.AMap.Marker({ icon: "https://webapi.amap.com/images/car.png", size: new window.AMap.Size(32, 32), offset: new window.AMap.Pixel(-16, -16), autoRotation: true, angle: -90, }); marker.setMap(map); const path = polyline.getPath(); if (path.length < 2) { console.error("路径点不足,无法进行动画"); return; } let count = 0; const length = path.length; const animateMarker = () => { count = (count + 1) % length; const lnglat = path[count]; marker.setPosition(lnglat); if (count < length - 1) { const nextLnglat = path[count + 1]; const angle = getAngle(lnglat, nextLnglat); marker.setRotation(angle); } else { console.log("动画完成,可以在这里处理结束逻辑"); count = 0; } window.requestAnimationFrame(animateMarker); }; window.requestAnimationFrame(animateMarker); }; const getAngle = (start: [number, number], end: [number, number]) => { const lat1 = start[1], lng1 = start[0]; const lat2 = end[1], lng2 = end[0]; const y = Math.sin(((lng2 - lng1) * Math.PI) / 180) * Math.cos((lat2 * Math.PI) / 180); const x = Math.cos((lat1 * Math.PI) / 180) * Math.sin((lat2 * Math.PI) / 180) - Math.sin((lat1 * Math.PI) / 180) * Math.cos((lat2 * Math.PI) / 180) * Math.cos(((lng2 - lng1) * Math.PI) / 180); return ((Math.atan2(y, x) * 180) / Math.PI + 360) % 360; };
addFlightAnimation
函数用于为折线添加飞行路径动画效果。首先,创建一个标记marker
,这个标记代表飞行的物体,这里使用了一个汽车图标(https://webapi.amap.com/images/car.png
),并设置了标记的大小、偏移量、自动旋转属性以及初始角度。然后将这个标记添加到地图上。
接着获取折线的路径点数组path
,如果路径点不足2个,就无法进行动画,会在控制台输出错误信息并返回。之后定义一个计数器count
和路径点的长度length
。
animateMarker
函数是动画的核心逻辑,它会在每次调用时更新标记的位置。通过count
的递增来获取路径数组中的下一个坐标点,并将标记移动到该位置。同时,计算下一个点的方向并设置标记的旋转角度,让标记的方向与飞行方向一致。当标记到达路径的最后一个点时,可以在这里处理动画结束的逻辑,比如重新开始动画。最后,使用window.requestAnimationFrame
方法来实现平滑的动画效果,它会在浏览器下次重绘之前调用传入的函数。
getAngle
函数用于计算两个坐标点之间的方向角,通过一系列三角函数的计算,返回一个表示方向的角度值,这个值会用于设置标记的旋转角度。
四、完整代码示例
import React, { useEffect, useRef, useState } from "react"; import "./index.scss"; // 定义 AMap 相关类型 declare global { interface Window { AMap: any; _AMapSecurityConfig: { serviceHost: string; }; } } interface MapPageProps {} const MapPage: React.FC<MapPageProps> = () => { const mapContainerRef = useRef<HTMLDivElement>(null); const [showPolyline, setShowPolyline] = useState<boolean>(true); const [isDarkMode, setIsDarkMode] = useState<boolean>(false); const [toolbarVisible, setToolbarVisible] = useState<boolean>(true); const initMap = () => { if (mapContainerRef.current) { const map = new window.AMap.Map(mapContainerRef.current, { zoom: 5, center: [110, 35], mapStyle: isDarkMode ? "amap://styles/dark" : "", }); const beijing: [number, number] = [106.5516, 29.563]; const chengdu: [number, number] = [104.06579, 30.570462]; if (showPolyline) { const path = [beijing, chengdu]; const polyline = new window.AMap.Polyline({ path: path, strokeColor: "#FF0000", strokeWeight: 5, strokeOpacity: 0.8, map: map, }); map.setFitView([polyline]); addFlightAnimation(map, polyline); } addControls(map, toolbarVisible); } }; const addControls = (map: any, toolbarVisible: boolean) => { if (!toolbarVisible) return; window.AMap.plugin(["AMap.ToolBar", "AMap.Scale"], () => { const controls = [ new window.AMap.ToolBar(), new window.AMap.Scale(), ]; controls.forEach((control) => { map.addControl(control); }); }); }; const addFlightAnimation = (map: any, polyline: any) => { const marker = new window.AMap.Marker({ icon: "https://webapi.amap.com/images/car.png", size: new window.AMap.Size(32, 32), offset: new window.AMap.Pixel(-16, -16), autoRotation: true, angle: -90, }); marker.setMap(map); const path = polyline.getPath(); if (path.length < 2) { console.error("路径点不足,无法进行动画"); return; } let count = 0; const length = path.length; const animateMarker = () => { count = (count + 1) % length; const lnglat = path[count]; marker.setPosition(lnglat); if (count < length - 1) { const nextLnglat = path[count + 1]; const angle = getAngle(lnglat, nextLnglat); marker.setRotation(angle); } else { console.log("动画完成,可以在这里处理结束逻辑"); count = 0; } window.requestAnimationFrame(animateMarker); }; window.requestAnimationFrame(animateMarker); }; const getAngle = (start: [number, number], end: [number, number]) => { const lat1 = start[1], lng1 = start[0]; const lat2 = end[1], lng2 = end[0]; const y = Math.sin(((lng2 - lng1) * Math.PI) / 180) * Math.cos((lat2 * Math.PI) / 180); const x = Math.cos((lat1 * Math.PI) / 180) * Math.sin((lat2 * Math.PI) / 18
以上就是React如何集成高德地图实现动态路线绘制、控件交互与深色模式切换,希望对你有帮助!