192.Vue3 + OpenLayers 实战:点击地图 Feature,列表自动滚动定位

一、最终效果

🎯 实现功能

  • 点击地图上的多边形(Feature)

  • 列表自动滚动到对应位置

  • 当前项高亮显示

  • 鼠标悬浮 Feature 变为 pointer

👉 核心体验:地图 → 列表联动


二、前言

在 WebGIS 项目中,经常会遇到这样一个需求:

👉 点击地图上的某个要素(Feature)

👉 右侧列表自动滚动并定位到对应数据项

这个交互在以下场景非常常见:

  • 无人机巡检系统 ✈️

  • 电子围栏管理 🗺️

  • 设备点位管理系统 📍

  • GIS 可视化大屏

本篇文章带你用 Vue3 + OpenLayers 实现一个完整、丝滑的联动效果。


三、核心实现思路

整个功能其实就三步:

1️⃣ Feature 和列表建立索引关系

复制代码
new Feature({
  geometry: item.area,
  listindex: i,
  name: item.descName,
});

👉 关键点:
给每个 Feature 绑定 listindex


2️⃣ 点击地图获取 Feature

复制代码
const feature = map.value.forEachFeatureAtPixel(
  e.pixel,
  (feature) => feature
);

👉 OpenLayers 核心 API


3️⃣ 滚动到对应 DOM

复制代码
const el = document.getElementById("pos" + i);
el && el.scrollIntoView({ behavior: "smooth", block: "center" });

🔥 重点:

window.location.hash 更优雅!


四、完整代码实现

javascript 复制代码
<!--
 * @Author: 彭麒
 * @Date: 2026/3/19
 * @Email: 1062470959@qq.com
 * @Description: 此源码版权归吉檀迦俐所有,可供学习和借鉴或商用。
 -->
<template>
  <div class="container">
    <div class="w-full flex justify-center flex-wrap">
      <div class="font-bold text-[24px]">
        Vue3+Openlayers:点击某feature,列表滑动,定位到相应的点的列表位置
      </div>
    </div>
    <div class="list">
      <div
          v-for="(item, index) in list"
          :key="index"
          :id="'pos' + index"
          style="margin: 10px 10px 0;"
      >
        <el-link :type="item.show ? 'primary' : 'info'">
          {{ item.descName }}
        </el-link>
      </div>
    </div>

    <div id="vue-openlayers"></div>
  </div>
</template>

<script setup>
import { ref, onMounted } from "vue";

import "ol/ol.css";
import { Map, View } from "ol";
import TileLayer from "ol/layer/Tile";
import VectorLayer from "ol/layer/Vector";
import VectorSource from "ol/source/Vector";
import XYZ from "ol/source/XYZ";
import Feature from "ol/Feature";
import { Style, Fill, Stroke } from "ol/style";
import { Select } from "ol/interaction";
import GeoJSON from "ol/format/GeoJSON";
import fData from "@/assets/map/china.json";
const map = ref(null);
const source = new VectorSource({ wrapX: false });
const select = ref(null);
const list = ref([]);
const drawfeatures = ref([]);

const initload = () => {
  drawfeatures.value = new GeoJSON().readFeatures(fData, {
    dataProjection: "EPSG:4326",
    featureProjection: "EPSG:4326",
  });

  updateList();
  showPolygons();
};

// 更新列表
const updateList = () => {
  list.value = [];

  drawfeatures.value.forEach((feature, index) => {
    const g = feature.getGeometry();

    list.value.push({
      descName: "围栏 " + index,
      show: true,
      area: g,
    });
  });
};

// 显示多边形
const showPolygons = () => {
  const features = list.value.map((item, i) => {
    return new Feature({
      geometry: item.area,
      listindex: i,
      name: item.descName,
    });
  });

  source.addFeatures(features);
};

// 点击 feature → 滚动列表
const clickFeature = () => {
  map.value.on("click", (e) => {
    const feature = map.value.forEachFeatureAtPixel(
        e.pixel,
        (feature) => feature
    );

    // 鼠标样式
    map.value.getTargetElement().style.cursor = feature
        ? "pointer"
        : "auto";

    if (feature) {
      const i = feature.get("listindex");

      // 滚动定位(推荐替代 hash)
      const el = document.getElementById("pos" + i);
      el && el.scrollIntoView({ behavior: "smooth", block: "center" });

      list.value.forEach((item, index) => {
        item.show = index !== i;
      });
    } else {
      list.value.forEach((item) => (item.show = true));
    }
  });
};

// 初始化地图
const initMap = () => {
  const iconStyle = new Style({
    stroke: new Stroke({
      color: "red",
      width: 2,
    }),
    fill: new Fill({
      color: "rgba(255,0,0,0)",
    }),
  });

  const googleLayer = new TileLayer({
    source: new XYZ({
      url: "https://www.google.com/maps/vt?lyrs=m&gl=en&x={x}&y={y}&z={z}",
      crossOrigin: "anonymous",
    }),
  });

  const drawLayer = new VectorLayer({
    source,
    style: iconStyle,
  });

  map.value = new Map({
    target: "vue-openlayers",
    layers: [googleLayer, drawLayer],
    view: new View({
      projection: "EPSG:4326",
      center: [110.4116821, 41.7966156],
      zoom: 3,
    }),
  });

  clickFeature();

  select.value = new Select();
  map.value.addInteraction(select.value);
};

onMounted(() => {
  initMap();
  initload();
});
</script>

<style scoped>
.container {
  width: 840px;
  height: 570px;
  margin: 50px auto;
  border: 1px solid #42B983;
}

#vue-openlayers {
  width: 620px;
  height: 460px;
  margin: 0 auto;
  border: 1px solid #42B983;
  float: left;
}

.list {
  width: 200px;
  height: 180px;
  margin: 0 auto;
  float: left;
  overflow-y: auto;
}
</style>

五、关键优化点(面试加分项🔥)

✅ 1. scrollIntoView 优于 hash

❌ 不推荐:

复制代码
window.location.hash = "#pos" + i;

✅ 推荐:

复制代码
el.scrollIntoView({ behavior: "smooth" });

✅ 2. 数据驱动 UI

复制代码
item.show = index !== i;

👉 Vue 响应式自动更新 UI


✅ 3. Feature 绑定业务数据

复制代码
feature.get("listindex")

👉 地图 → 业务数据的桥梁


✅ 4. 解耦设计(高级)

可以进一步抽成:

  • useMap()

  • useFeatureClick()

  • useListSync()

👉 适合大型项目


六、可扩展功能(建议你写进简历)

🔥 你可以继续升级:

  • 点击列表 → 地图定位(反向联动)

  • Feature 高亮选中(改变样式)

  • hover 提示

  • 海量数据优化(WebGL / 分层加载)

  • 地图聚合 / 分组展示


七、总结

本文实现了一个非常实用的 GIS 交互功能:

✅ 地图点击

✅ 列表联动

✅ 平滑滚动定位

✅ Vue3 响应式驱动

👉 核心一句话总结:

"通过 Feature 绑定索引,实现地图与列表的双向映射"

相关推荐
百锦再10 小时前
Vue不是万能的:前后端不分离开发的优势
前端·javascript·vue.js·前端框架·vue
BUG创建者10 小时前
openlayers上跟据经纬度画出轨迹
开发语言·javascript·vue·html
平行云11 小时前
数字孪生信创云渲染系列(一):混合信创与全国产化架构
unity·ue5·3dsmax·webgl·gpu算力·实时云渲染·像素流送
ん贤12 小时前
首屏优化实践:如何将 Vue3 + Vite 项目的加载速度提升3倍
性能优化·vue·vite
A_nanda12 小时前
一款前端PDF插件
前端·学习·pdf·vue
沐硕13 小时前
校园招聘系统
spring boot·vue·校园招聘
sin°θ_陈13 小时前
CVPR 2026的3DGS卷到什么地步?工程语义上探:BrepGaussian如何打通图像到CAD的最后一公里?(Part III 1-3)
python·深度学习·算法·机器学习·3d·webgl
花姐夫Jun1 天前
WebGL学习-czm_getMaterial详解
学习·webgl
Lsx-codeShare2 天前
前端发版后页面白屏?一套解决用户停留旧页面问题的完整方案
前端·javascript·前端框架·vue·vite