205.Vue3 + OpenLayers:加载动画,采用 CSS 的 @keyframes 方式

本篇文章来分享一个在 Vue3 + OpenLayers 中实现地图加载动画的小案例。

这种效果常用于:

  • GIS 点位高亮
  • 预警点
  • 实时监测
  • 设备在线状态
  • 雷达扫描效果
  • 热点位置提示

一、效果图

最终效果类似于:

  • 地图上出现一个红色圆点
  • 圆点不断扩散
  • 类似雷达波纹动画

整体效果比较轻量,而且性能也不错。


二、核心思路

实现原理其实并不复杂。

主要分为三步:

1、创建 Overlay

OpenLayers 的 Overlay 可以向地图中添加 HTML 元素。

例如:

复制代码
const overlay = new Overlay({
  position: coordinate,
  element: pointDiv,
  positioning: 'center-center',
})

2、动态创建 DOM

这里我们采用:

复制代码
document.createElement('div')

动态生成动画节点。


3、使用 CSS 动画

核心动画:

复制代码
@keyframes myfirst {
  to {
    transform: scale(2);

    background: rgba(255, 0, 0, 0.2);

    box-shadow: inset 0 0 50px rgba(255, 0, 0, 0);
  }
}

通过:

  • scale
  • rgba
  • box-shadow

实现波纹扩散效果。


三、完整代码

javascript 复制代码
<!--
 * @Author: 彭麒
 * @Date: 2026/5/11
 * @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:加载动画,采用css的@keyframes方式
      </div>
    </div>
      <div id="vue-openlayers"></div>
    </div>
  </template>
  
  <script setup>
  import { onMounted, ref } from 'vue'
  
  import 'ol/ol.css'
  import Map from 'ol/Map'
  import View from 'ol/View'
  import Overlay from 'ol/Overlay'
  
  import TileLayer from 'ol/layer/Tile'
  import XYZ from 'ol/source/XYZ'
  
  import { fromLonLat, useGeographic } from 'ol/proj'
  
  // map 实例
  const map = ref(null)
  
  // geojson 数据
  const geojsonData = {
    type: 'FeatureCollection',
    features: [
      {
        type: 'Feature',
        properties: {
          title: 'point1',
        },
        geometry: {
          type: 'Point',
          coordinates: [-95.4, 31.8],
        },
      },
      {
        type: 'Feature',
        properties: {
          title: 'point2',
        },
        geometry: {
          type: 'Point',
          coordinates: [-97.1, 38.7],
        },
      },
    ],
  }
  
  // 获取坐标
  const getCoordinatesByGeojson = (geojsonData) => {
    return geojsonData.features.map(
      (feature) => feature.geometry.coordinates
    )
  }
  
  // 显示动画
  const showAnimation = () => {
    const coordinates = getCoordinatesByGeojson(geojsonData)
  
    coordinates.forEach((coordinate, index) => {
      // 创建 dom
      const pointDiv = document.createElement('div')
  
      pointDiv.className = 'cssAnimation'
      pointDiv.id = `coordinate_${index}`
  
      // 注意:
      // overlay 的 element 不需要 append 到 document
      // OpenLayers 会自动处理
      const overlay = new Overlay({
        position: coordinate,
        element: pointDiv,
        positioning: 'center-center',
      })
  
      map.value.addOverlay(overlay)
    })
  }
  
  // 初始化地图
  const initMap = () => {
    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',
      }),
    })
  
    map.value = new Map({
      target: 'vue-openlayers',
      layers: [googleLayer],
      view: new View({
        projection: 'EPSG:3857',
        center: fromLonLat([-97.1, 38.7]),
        zoom: 4,
      }),
    })
  }
  
  onMounted(() => {
    initMap()
  
    // 核心,必须要有
    useGeographic()
  
    showAnimation()
  })
  </script>
  
  <style scoped>
  .container {
    width: 840px;
    height: 620px;
    margin: 50px auto;
    border: 1px solid #42b983;
  }
  
  #vue-openlayers {
    width: 800px;
    height: 490px;
    margin: 0 auto;
    border: 1px solid #42b983;
    position: relative;
  }
  
  /* 动态创建的 overlay 节点无 data-v-*,scoped 下需用 :deep 才能命中 */
  :deep(.cssAnimation) {
    width: 20px;
    height: 20px;

    border-radius: 50%;

    background: rgba(255, 0, 0, 0.9);

    box-shadow: inset 0 0 8px red;

    transform: scale(0);

    animation: myfirst 3s infinite;
  }

  @keyframes myfirst {
    to {
      transform: scale(2);

      background: rgba(255, 0, 0, 0.2);

      box-shadow: inset 0 0 50px rgba(255, 0, 0, 0);
    }
  }
  </style>

四、为什么 scoped 下必须使用 :deep?

这里其实是很多人容易踩坑的地方。

我们动态创建的节点:

复制代码
document.createElement('div')

并不是 Vue 模板生成的。

所以:

复制代码
<style scoped>

生成的:

复制代码
[data-v-xxxx]

并不会作用到动态创建的 DOM。

因此:

复制代码
.cssAnimation

会失效。


正确写法

必须使用:

复制代码
:deep(.cssAnimation)

这样才能穿透 scoped。

这是本案例中的关键点。


五、为什么 Overlay 不需要 appendChild?

很多人会这样写:

复制代码
document.body.appendChild(pointDiv)

其实没必要。

因为:

复制代码
new Overlay({
  element: pointDiv
})

OpenLayers 内部会自动挂载。

如果手动 append:

  • 会导致 DOM 重复
  • 有概率产生定位问题
  • 甚至导致动画异常

因此:

直接传给 Overlay 即可。


六、为什么 useGeographic() 必须放这里?

这一句非常关键:

复制代码
useGeographic()

因为:

GeoJSON 中的坐标:

复制代码
[-95.4, 31.8]

属于:

复制代码
EPSG:4326

也就是:

复制代码
经纬度坐标

而 OpenLayers 默认:

复制代码
EPSG:3857

如果不使用:

复制代码
useGeographic()

则 Overlay 位置可能会出现偏移。


七、动画还能怎么优化?

目前只是最基础版本。

实际上还能继续增强:


1、多层波纹

例如:

  • 一个大圈
  • 一个中圈
  • 一个小圈

形成雷达效果。


2、不同颜色

例如:

  • 红色:危险
  • 绿色:在线
  • 黄色:预警

3、添加中心点

可以在波纹中心增加:

复制代码
width: 6px;
height: 6px;

形成更明显的定位效果。


4、动态数据

目前数据是写死的:

复制代码
geojsonData

实际项目中:

  • websocket
  • 接口返回
  • 实时定位

都可以动态更新。


八、适用场景

这个方案在 GIS 项目里非常常见。

例如:

场景 用途
无人机巡检 实时设备点
物联网 在线设备状态
车辆监控 车辆当前位置
风险预警 告警点
应急系统 事故位置
气象系统 台风/暴雨点

九、总结

本篇文章主要介绍了:

  • Vue3 + OpenLayers
  • Overlay 的使用
  • CSS 动画实现
  • @keyframes 波纹效果
  • scoped 与 :deep
  • useGeographic 的作用

整体方案:

  • 轻量
  • 易扩展
  • 性能不错
  • 不依赖第三方动画库

非常适合 GIS 项目中的动态点位效果。


十、结尾

如果你也在做:

  • OpenLayers
  • GIS
  • Vue3
  • 智慧城市
  • 数字孪生
  • 地图可视化

这个动画方案还是非常实用的。

感兴趣可以关注一下。

相关推荐
ZC跨境爬虫2 小时前
跟着 MDN 学CSS day_39:(Flexbox 弹性盒子核心机制)
前端·css·ui·html·tensorflow
小陈同学呦2 小时前
前端如何处理订单状态导航的数据竞态问题
前端·javascript
喵个咪2 小时前
GoWind Toolkit 前端代码生成|Vue3(ElementPlus/Vben)、React(AntDesign)全自动一键生成教程
前端·vue.js·react.js
qq_2518364573 小时前
SpringBoot+Vue 共享电池柜管理系统 完整实现 前后端分离项目实战 完整代码
vue.js·spring boot·后端
摆烂大大王3 小时前
玩转 OpenClaw:用 TaskFlow + Heartbeat 打造自动化工作流
前端·人工智能·自动化
zhangxingchao4 小时前
AI 大模型核心六:量化、Workflow 与 Agent、多轮 RAG
前端·人工智能·后端
梦想的颜色4 小时前
TypeScript 完全指南(上):从零开始掌握类型系统
前端·typescript
之歆4 小时前
Day01_ES6+ 专业指南:从基础到实战的现代JavaScript开发(下)
前端·javascript·es6
lichenyang4534 小时前
鸿蒙 MVVM 实战:从 Demo 到工程化,聊聊登录、状态管理与埋点系统设计
前端
IT_陈寒5 小时前
Vite打包时遇到的坑,原来问题出在这里
前端·人工智能·后端