Skip to content

OpenLayers 散点涟漪效果

上次更新 2025年8月20日星期三 3:16:20 字数 0 字 时长 0 分钟

本章将介绍如何在 OpenLayers 中实现散点的涟漪动画效果。

示例

新标签页预览

代码实现

vue
<template>
  <div class="h-screen">
    <div id="map" class="w-full h-full"></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 TileLayer from "ol/layer/Tile";
import XYZ from "ol/source/XYZ";
import { fromLonLat } from "ol/proj";
import Feature from "ol/Feature";
import Point from "ol/geom/Point";
import { Vector as VectorLayer } from "ol/layer";
import { Vector as VectorSource } from "ol/source";
import { Style, Circle, Fill, Stroke } from "ol/style";

onMounted(() => {
  // 初始化地图
  const map = new Map({
    target: "map",
    view: new View({
      center: fromLonLat([116.4074, 39.9042]),
      zoom: 12,
    }),
  });

  // 添加天地图图层
  const tdtLayer = new TileLayer({
    source: new XYZ({
      url: "http://t0.tianditu.gov.cn/vec_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=vec&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=d32c6748c80f81a44acd8633cea41dfd",
    }),
  });
  map.addLayer(tdtLayer);

  // 创建矢量图层
  const vectorSource = new VectorSource();
  const vectorLayer = new VectorLayer({
    source: vectorSource,
  });
  map.addLayer(vectorLayer);

  // 添加点位数据
  const points = [
    [116.4074, 39.9042],
    [116.4174, 39.9142],
    [116.3974, 39.8942],
  ];

  points.forEach((point) => {
    const feature = new Feature({
      geometry: new Point(fromLonLat(point)),
    });

    // 自定义样式
    feature.setStyle(
      new Style({
        image: new Circle({
          radius: 12,
          fill: new Fill({
            color: "rgba(33, 150, 243, 0.8)",
          }),
          stroke: new Stroke({
            color: "rgba(33, 150, 243, 0.6)",
            width: 2,
          }),
        }),
      })
    );

    vectorSource.addFeature(feature);
  });

  // 添加涟漪动画效果
  points.forEach((point) => {
    const rippleFeature = new Feature({
      geometry: new Point(fromLonLat(point)),
    });

    let phase = 0;
    const animate = () => {
      // 确保半径始终为正数
      const radius = Math.max(12 + 30 * Math.sin(phase), 1);
      const opacity = Math.max(0.8 - 0.6 * Math.sin(phase), 0);

      rippleFeature.setStyle(
        new Style({
          image: new Circle({
            radius: radius,
            fill: new Fill({
              color: `rgba(33, 150, 243, ${opacity})`,
            }),
          }),
        })
      );

      phase += 0.1;
      if (phase > Math.PI * 2) {
        phase = 0;
      }
      requestAnimationFrame(animate);
    };

    animate();
    vectorSource.addFeature(rippleFeature);
  });
});
</script>

<style scoped>
.h-screen {
  height: 24rem;
}

.w-full {
  width: 100%;
}

.h-full {
  height: 100%;
}

.ripple-marker {
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: rgba(33, 150, 243, 0.8);
  position: relative;
  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.3), 0 0 10px rgba(33, 150, 243, 0.5);
}

.ripple-marker::before,
.ripple-marker::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 100%;
  height: 100%;
  border-radius: 50%;
  background: rgba(33, 150, 243, 0.6);
  transform: translate(-50%, -50%);
}

.ripple-marker::before {
  animation: ripple 2s infinite ease-out;
}

.ripple-marker::after {
  animation: ripple 2s infinite ease-out 0.5s;
}

@keyframes ripple {
  0% {
    transform: translate(-50%, -50%) scale(1);
    opacity: 0.8;
  }
  50% {
    opacity: 0.4;
  }
  100% {
    transform: translate(-50%, -50%) scale(3.5);
    opacity: 0;
  }
}
</style>
关注公众号