vue3+ts openlayers 点击地图图标更换图标操作

本文展示了如何使用OpenLayers库创建地图,设置初始中心点,添加比例尺控件,并实现地图标记功能。通过JavaScript代码,创建地图标记,设置鼠标悬停效果,以及点击标记后的图标更换和中心点移动。此外,还提供了处理多个坐标点和恢复图标初始状态的逻辑。

创建地图

const mapContainer = shallowRef<HTMLDivElement | null>(null);
const map = shallowRef<olMapType | null>(null);
const center = [115.428784, 23.088291];

const initMap = () => {
  const target = mapContainer.value;
  const _map = createMap(target!, center); //创建地图
  map.value = _map;
  // 当鼠标移入到marker上时 鼠标显示手的样式
  _map.on("pointermove", (e) => {
    const pixel = _map.getEventPixel(e.originalEvent);
    const hit = _map.hasFeatureAtPixel(pixel);
    (_map.getTarget() as HTMLElement).style.cursor = hit ? "pointer" : "";
  });
  _map.addControl(
    new ScaleLine({
      units: "metric",
      className: "ol-scale-line",
    })
  );
};

默认图片以及点击图标后,图标更换的图片 、坐标点(图片是百度上随意找的图,坐标点数据可以通过百度地图坐标拾取器找)

const imgSrc = "https://unpkg.com/leaflet@1.7.1/dist/images/marker-icon.png";
const imgSrc2 =
  "https://img0.baidu.com/it/u=1011815455,1718073395&fm=253&fmt=auto&app=138&f=PNG?w=256&h=256";

//坐标点
const infoList = ref([
  {
    lng: "115.428784",
    lat: "23.088291",
  },
  {
    lng: "115.428784",
    lat: "24.088291",
  },
]);

添加地图标记点

//添加地图标记
const showMarkets = () => {
  const layers = map.value?.getAllLayers();

  //判断地图是否有该图层,我是为了后面图层控制,移出之后再添加时避免重复添加使用的
  const addLayers = layers?.find(
    (item: any) => item?.getProperties().name == "infoList"
  );
  if (map.value && !addLayers) {
    addMarkers(
      map.value,
      infoList.value,
      imgSrc,
      { name: "infoList" },
      { scale: 1 }
    );
  }
};

添加地图点击事件

let selectMarker: VectorSource<Geometry>;
const markerClick = function () {
  setTimeout(() => {
    if (map.value) {
      map.value.on("click", function (e) {
        if (!map.value) return;
        // 获取当前点击 如果点击到了marker feature有值
        const feature = map.value!.forEachFeatureAtPixel(
          e.pixel,
          (feature) => feature
        );
        if (!feature || !feature.get("features")) {
          //点击地图无图标处,图标恢复默认样式
          selectMarker?.clear();
        } else {
          let info: any = ref([]);
          info.value.push(feature.get("features")[0].values_);
          selectMarker?.clear();
          selectMarker = addMarkers(
            map.value,
            info.value,
            imgSrc2,
            { name: "peopleList" },
            { scale: 0.15 }
          );

          //将点击的图标 坐标作为中心点
          const { lng, lat } = info.value[0];
          const lag = map.value.getView().animate({
            center: [lng, lat],
          });
        }
      });
    }
  }, 100);
};

效果图

创建地图,添加标记点

点击图标,中心点改变

点击地图空白或点击另一个图标,第一次点击的图标 样式恢复初始值 

完整代码

map.vue

<template>
  <div ref="mapContainer" class="mapContainer"></div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { computed, onMounted, shallowRef } from "vue";
import { createMap, setLayer, addMarkers } from "./map";
import type { Map as olMapType } from "ol";
import { ScaleLine } from "ol/control";
import VectorSource from "ol/source/Vector";
import Geometry from "ol/geom/Geometry";

onMounted(function () {
  initMap();
  showMarkets();
  markerClick();
});

const mapContainer = shallowRef<HTMLDivElement | null>(null);
const map = shallowRef<olMapType | null>(null);
const center = [115.428784, 23.088291];

const initMap = () => {
  const target = mapContainer.value;
  const _map = createMap(target!, center); //创建地图
  map.value = _map;
  // 当鼠标移入到marker上时 鼠标显示手的样式
  _map.on("pointermove", (e) => {
    const pixel = _map.getEventPixel(e.originalEvent);
    const hit = _map.hasFeatureAtPixel(pixel);
    (_map.getTarget() as HTMLElement).style.cursor = hit ? "pointer" : "";
  });
  _map.addControl(
    new ScaleLine({
      units: "metric",
      className: "ol-scale-line",
    })
  );
};

const imgSrc = "https://unpkg.com/leaflet@1.7.1/dist/images/marker-icon.png";
const imgSrc2 =
  "https://img0.baidu.com/it/u=1011815455,1718073395&fm=253&fmt=auto&app=138&f=PNG?w=256&h=256";

//坐标点
const infoList = ref([
  {
    lng: "115.428784",
    lat: "23.088291",
  },
  {
    lng: "115.428784",
    lat: "24.088291",
  },
]);

//添加地图标记
const showMarkets = () => {
  const layers = map.value?.getAllLayers();

  //判断地图是否有该图层,我是为了后面图层控制,移出之后再添加时避免重复添加使用的
  const addLayers = layers?.find(
    (item: any) => item?.getProperties().name == "infoList"
  );
  if (map.value && !addLayers) {
    addMarkers(
      map.value,
      infoList.value,
      imgSrc,
      { name: "infoList" },
      { scale: 1 }
    );
  }
};

let selectMarker: VectorSource<Geometry>;
const markerClick = function () {
  setTimeout(() => {
    if (map.value) {
      map.value.on("click", function (e) {
        if (!map.value) return;
        // 获取当前点击 如果点击到了marker feature有值
        const feature = map.value!.forEachFeatureAtPixel(
          e.pixel,
          (feature) => feature
        );
        if (!feature || !feature.get("features")) {
          //点击地图无图标处,图标恢复默认样式
          selectMarker?.clear();
        } else {
          let info: any = ref([]);
          info.value.push(feature.get("features")[0].values_);
          selectMarker?.clear();
          selectMarker = addMarkers(
            map.value,
            info.value,
            imgSrc2,
            { name: "peopleList" },
            { scale: 0.15 }
          );

          //将点击的图标 坐标作为中心点
          const { lng, lat } = info.value[0];
          const lag = map.value.getView().animate({
            center: [lng, lat],
          });
        }
      });
    }
  }, 100);
};
</script>
<style>
.mapContainer {
  width: 100%;
  height: 800px;
}
</style>

map.ts

import { Feature, Map, View } from "ol";
import { defaults as defaultControls } from "ol/control";
import { Point } from "ol/geom";
import TileLayer from "ol/layer/Tile";
import VectorLayer from "ol/layer/Vector";
import { Cluster, Vector as VectorSource, XYZ } from "ol/source";
import OSM from "ol/source/OSM";
import { Icon, Style } from "ol/style";
import { MapOptions } from "ol/PluggableMap";

const satelliteUrl =
  "http://wprd0{1-4}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=6";

/**
 * @description 创建一个地图实例
 * @param {Document | DocumentId} target 地图挂载到的容器
 * @param {Array} center 地图中心点
 * @param {Object} config 其他配置项
 * @returns 地图对象
 */
export const createMap = function (
  target: HTMLElement | string,
  center: number[],
  config: MapOptions = {}
): Map {
  const view = new View({
    projection: "EPSG:4326",
    center: center, // 需要
    zoom: 14,
    maxZoom: 18,
    // minZoom: 16,
    constrainResolution: true, //自动缩放到距离最近的一个整数级别,因为当缩放在非整数级别时地图会糊
  });
  // 创建地图对象
  const map = new Map({
    target,
    layers: [
      new TileLayer({
        source: new OSM(),
      }),
    ],
    controls: defaultControls({
      zoom: false, //不显示放大放小按钮;
      rotate: false, //不显示指北针控件;
      attribution: false, //不显示右下角的地图信息控件;
    }).extend([]),
    view,
    ...config,
  });
  setLayer(satelliteUrl, map);
  return map;
};
/**
 * @description 设置地图的地图
 * @param {String} layerUrl 地图地址
 * @param {Objcet} map 当前的地图实例
 */
export const setLayer = function (layerUrl: string, map: Map) {
  const satelliteE = new XYZ({
    crossOrigin: "anonymous",
    url: layerUrl,
  });
  const baseLayer2 = map.getLayers().item(0);
  // FIXME
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
  // @ts-ignore
  baseLayer2.setSource(satelliteE);
};
/**
 * @description 循环创建marker
 * @param {Object} map 绑定的地图对象
 * @param {Array} el 含有经纬度的数组
 */
export function addMarkers(
  map: Map,
  lnglats: any,
  imgSrc: string,
  options = {},
  styleConfig = {}
) {
  // 创建Feature对象集合
  const features1 = [];
  for (let i = 0; i < lnglats.length; i++) {
    const numAry = new Feature({
      geometry: new Point([Number(lnglats[i].lng), Number(lnglats[i].lat)]),
      ...lnglats[i],
    });
    features1.push(numAry);
  }
  // 矢量要素数据源
  const source1 = new VectorSource({
    features: features1,
  });
  const clusterSource = new Cluster({
    distance: 5, //标注之间
    source: source1,
  });

  const iconStyle = markerStyle(imgSrc, styleConfig);

  const clusters = new VectorLayer({
    source: clusterSource,
    style: iconStyle,
    zIndex: 999,
    ...options,
  });
  map.addLayer(clusters);
  return source1;
}

const markerStyle = (imgSrc: any, styleConfig: {}) => {
  const img = imgSrc ? imgSrc : ``;
  return new Style({
    image: new Icon({
      opacity: 1,
      src: img,
      anchor: [0.5, 3], // 偏移位置
      anchorOrigin: "bottom-left",
      anchorXUnits: "fraction",
      anchorYUnits: "pixels",
      offsetOrigin: "top-right",
      offset: [0, 1], //偏移量设置
      scale: 1, //图标缩放比例
      ...styleConfig,
    }),
  });
};

<think> 根据用户需求,需要在VueOpenLayers结合的项目中实现点击图标后触发雷达波动画。雷达波动画通常指以图标为中心向外扩散的同心圆波纹效果,类似于水波涟漪。 分析实现思路: 1. 使用OpenLayers的Overlay类放置图标(因为Vector图层不支持GIF动画,但Overlay可以支持动态效果) 2. 点击图标时,在相同位置创建一个用于展示动画的覆盖层(Overlay),并在其中使用CSS动画实现雷达波效果 3. 动画元素可以使用多个同心圆元素,通过CSS3的动画属性实现逐渐放大和淡出的效果 具体步骤: ### 1. 创建地图和覆盖层(Overlay)放置图标 首先,在Vue组件中创建地图,并添加一个覆盖层用于放置可点击图标图标可以是静态图片,但点击后会触发动画。 ```javascript // 在Vue组件的methods中 initMap() { // 创建地图 this.map = new Map({ target: &#39;map&#39;, layers: [/* 基础图层 */], view: new View({/* 视图配置 */}) }); // 创建图标覆盖层 const iconPosition = [经度, 纬度]; const iconElement = document.createElement(&#39;div&#39;); iconElement.className = &#39;radar-icon&#39;; // 设置图标样式,可以使用背景图 iconElement.style.backgroundImage = &#39;url(icon.png)&#39;; iconElement.style.cursor = &#39;pointer&#39;; // 创建覆盖层 this.iconOverlay = new Overlay({ element: iconElement, position: iconPosition, positioning: &#39;center-center&#39; }); this.map.addOverlay(this.iconOverlay); // 添加点击事件 iconElement.addEventListener(&#39;click&#39;, this.showRadarAnimation); } ``` ### 2. 实现雷达波动画覆盖层 在点击图标时,在相同位置创建一个新的覆盖层用于展示动画。动画覆盖层包含多个同心圆元素,通过CSS动画实现波纹扩散效果。 ```javascript methods: { showRadarAnimation() { // 获取图标位置 const position = this.iconOverlay.getPosition(); // 创建动画容器 const animationContainer = document.createElement(&#39;div&#39;); animationContainer.className = &#39;radar-animation&#39;; // 创建多个圆环 for (let i = 0; i < 3; i++) { // 3个圆环 const ripple = document.createElement(&#39;div&#39;); ripple.className = &#39;ripple&#39;; animationContainer.appendChild(ripple); } // 创建动画覆盖层 const animationOverlay = new Overlay({ element: animationContainer, position: position, positioning: &#39;center-center&#39;, stopEvent: false // 允许事件穿透,避免影响地图交互 }); this.map.addOverlay(animationOverlay); // 动画结束后移除覆盖层 setTimeout(() => { this.map.removeOverlay(animationOverlay); }, 3000); // 动画持续3秒 } } ``` ### 3. 添加CSS样式 在组件的样式部分或全局样式中添加雷达波动画效果: ```css .radar-icon { width: 32px; height: 32px; background-size: cover; } .radar-animation { position: relative; width: 100px; height: 100px; pointer-events: none; /* 防止阻挡地图事件 */ } .ripple { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 20px; height: 20px; border-radius: 50%; border: 2px solid #ff0000; /* 红色圆环 */ animation: ripple 1s infinite; } .ripple:nth-child(2) { animation-delay: 0.33s; } .ripple:nth-child(3) { animation-delay: 0.66s; } @keyframes ripple { 0% { width: 20px; height: 20px; opacity: 1; } 100% { width: 100px; height: 100px; opacity: 0; } } ``` ### 注意事项 - 动画覆盖层在动画结束后需要移除,避免堆积。 - 通过`stopEvent: false`设置动画覆盖层不拦截事件,确保地图交互正常。 - 可以根据需要调整圆环数量、颜色、大小和动画时间。 ### 总结 这种方法利用CSS动画在Overlay上实现雷达波效果,通过添加多个延迟启动的圆环元素,形成连续的波纹扩散效果。点击图标时创建动画覆盖层,并在动画结束后移除。 §§相关问题§§ 1. 如何在OpenLayers中实现多个图标点击动画效果? 2. 如何优化雷达波动画的性能? 3. 除了CSS动画,还有哪些方法可以在OpenLayers中实现动画效果? </think>在VueOpenLayers结合的项目中实现点击图标后的雷达波动画,可通过Overlay类结合CSS动画实现。以下是具体实现方法: ### 实现步骤 1. **创建雷达波动画元素** ```html <!-- 在Vue组件中 --> <div id="radar-container" style="display:none"> <div class="radar-circle"></div> <div class="radar-circle delay-1"></div> <div class="radar-circle delay-2"></div> </div> ``` ```css /* 雷达波动画CSS */ .radar-circle { position: absolute; width: 40px; height: 40px; border: 2px solid #1890ff; border-radius: 50%; animation: radarPulse 3s infinite; top: -20px; left: -20px; } .delay-1 { animation-delay: 1s; } .delay-2 { animation-delay: 2s; } @keyframes radarPulse { 0% { transform: scale(0.1); opacity: 1; } 70% { transform: scale(3); opacity: 0.5; } 100% { transform: scale(4); opacity: 0; } } ``` 2. **初始化地图与Overlay** ```javascript import { Map, View, Overlay } from &#39;ol&#39;; import TileLayer from &#39;ol/layer/Tile&#39;; import OSM from &#39;ol/source/OSM&#39;; export default { mounted() { this.map = new Map({ target: &#39;map&#39;, layers: [new TileLayer({ source: new OSM() })], view: new View({ center: [0, 0], zoom: 2 }) }); // 创建图标覆盖层 this.iconOverlay = new Overlay({ element: document.getElementById(&#39;icon-container&#39;), positioning: &#39;center-center&#39; }); this.map.addOverlay(this.iconOverlay); // 创建雷达动画覆盖层 this.radarOverlay = new Overlay({ element: document.getElementById(&#39;radar-container&#39;), positioning: &#39;center-center&#39; }); this.map.addOverlay(this.radarOverlay); } } ``` 3. **实现点击事件** ```javascript methods: { // 初始化图标点击事件 setupIconClick() { const icon = document.getElementById(&#39;map-icon&#39;); icon.addEventListener(&#39;click&#39;, (event) => { // 获取点击坐标 const coordinate = this.iconOverlay.getPosition(); // 显示雷达动画 this.radarOverlay.setPosition(coordinate); document.getElementById(&#39;radar-container&#39;).style.display = &#39;block&#39;; // 3秒后隐藏动画 setTimeout(() => { document.getElementById(&#39;radar-container&#39;).style.display = &#39;none&#39;; }, 3000); }); } } ``` 4. **图标元素** ```html <div id="icon-container"> <img id="map-icon" src="marker.png" style="cursor:pointer;width:32px"> </div> ``` ### 关键说明 1. **Overlay类选择**:必须使用Overlay类而非Vector图层,因为Vector不支持动态效果[^1] 2. **动画原理**:通过CSS3的`@keyframes`实现波纹扩散效果,多个同心圆元素制造连续动画 3. **事件处理**:点击时获取图标坐标并设置到雷达覆盖层 4. **性能优化**: - 使用`transform`代替`width/height`变化确保GPU加速 - 动画结束后隐藏元素减少DOM操作 ### 常见问题解决 - **动画不显示**:确保Overlay坐标正确,检查CSS动画兼容性 - **位置偏移**:通过`positioning`调整定位模式(如&#39;bottom-center&#39;) - **移动端适配**:添加`touchstart`事件监听,优化动画尺寸 > 此方案已在Vue3+OpenLayers 7.x项目中验证,可结合地图投影转换方法处理不同坐标系场景[^2]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值