React如何集成高德地图实现动态路线绘制、控件交互与深色模式切换

前端 潘老师 来源:lbh 2个月前 (02-26) 58 ℃ (0) 扫码查看

在前端开发中,地图功能的实现十分常见,而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用于处理副作用操作。这里,当组件挂载到页面上时,会执行一系列操作:

  1. 配置高德地图的安全密钥,设置window._AMapSecurityConfig对象中的securityJsCode属性,这一步很关键,它确保了地图API的安全调用。
  2. 创建一个<script>标签,通过设置src属性来引入高德地图的JS API,版本号为1.4.15,同时别忘了将key替换为你自己申请的API Key。async属性设置为true,表示这个脚本会在后台加载,不会阻塞页面的渲染。
  3. 将创建好的脚本标签添加到document.body中,这样就开始加载地图API了。
  4. 当脚本加载完成后,会触发onload事件,此时调用initMap函数来初始化地图。
  5. 最后返回一个清理函数,当组件从页面上卸载时,这个函数会被执行,它的作用是从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的值进行变化,如果toolbarVisibletrue,表示缩放控件当前是显示状态,按钮文本就显示为“隐藏缩放控件”;反之,如果toolbarVisiblefalse,按钮文本则显示为“显示缩放控件”。

(二)添加控件函数

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是一个布尔值,用于判断是否要显示这些控件。如果toolbarVisiblefalse,函数会直接返回,不执行后续添加控件的操作。

toolbarVisibletrue时,通过window.AMap.plugin方法加载高德地图的AMap.ToolBarAMap.Scale插件。这两个插件分别提供了缩放工具条和比例尺的功能。插件加载完成后,会执行回调函数。在回调函数中,创建一个包含缩放工具条和比例尺实例的数组controls,然后使用forEach方法遍历这个数组,将每个控件通过map.addControl(control)添加到地图上。

三、定义坐标并创建折线

为了在地图上展示重庆到成都的路线,需要定义两个城市的坐标,并创建一条折线来连接它们。

(一)定义坐标

const beijing: [number, number] = [106.5516, 29.563]; 
const chengdu: [number, number] = [104.06579, 30.570462]; 

这里定义了两个常量beijingchengdu,分别存储了重庆和成都的地理坐标。坐标由经度和纬度组成,这是在地图上定位一个地点的关键信息。

(二)创建折线

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);
}

showPolylinetrue时,会执行创建折线的操作。首先,创建一个包含重庆和成都坐标的路径数组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如何集成高德地图实现动态路线绘制、控件交互与深色模式切换,希望对你有帮助!


版权声明:本站文章,如无说明,均为本站原创,转载请注明文章来源。如有侵权,请联系博主删除。
本文链接:https://www.panziye.com/front/14958.html
喜欢 (0)
请潘老师喝杯Coffee吧!】
分享 (0)
用户头像
发表我的评论
取消评论
表情 贴图 签到 代码

Hi,您需要填写昵称和邮箱!

  • 昵称【必填】
  • 邮箱【必填】
  • 网址【可选】