You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用L.canvas在Leaflet中绘制波动扩散的动态圆形

实现Leaflet动态水波扩散圆形(基于L.canvas)

下面直接提供可运行的示例代码,并拆解关键逻辑:

完整可运行示例

<!DOCTYPE html>
<html>
<head>
    <title>Leaflet Water Wave Animation</title>
    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
    <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
    <style>
        #map { height: 600px; width: 100%; }
    </style>
</head>
<body>
    <div id="map"></div>

    <script>
        // 初始化地图
        const map = L.map('map').setView([39.9042, 116.4074], 13);
        L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(map);

        // 自定义Canvas图层实现水波动画
        class WaveLayer extends L.Canvas {
            constructor(options) {
                super(options);
                this.waves = []; // 存储所有活跃水波的状态
            }

            // 添加新水波:参数为圆心经纬度、初始半径、最大半径、动画持续时间
            addWave(latlng, startRadius = 0, maxRadius = 200, duration = 3000) {
                const wave = {
                    latlng: latlng,
                    startTime: Date.now(),
                    startRadius,
                    maxRadius,
                    duration
                };
                this.waves.push(wave);
                this.redraw(); // 触发首次重绘
            }

            // 核心重绘逻辑
            _drawLayer() {
                if (this.waves.length === 0) return;

                const ctx = this._ctx;
                const now = Date.now();
                // 清空画布准备重绘
                ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);

                // 遍历并更新所有水波,过滤掉已结束的波
                this.waves = this.waves.filter(wave => {
                    const elapsed = now - wave.startTime;
                    if (elapsed > wave.duration) return false;

                    // 计算当前进度:0到1的数值
                    const progress = elapsed / wave.duration;
                    // 半径随进度从初始值增长到最大值
                    const currentRadius = wave.startRadius + (wave.maxRadius - wave.startRadius) * progress;
                    // 透明度随进度从1衰减到0
                    const opacity = 1 - progress;

                    // 把经纬度转成Canvas画布上的像素坐标
                    const point = this._map.latLngToContainerPoint(wave.latlng);

                    // 绘制水波圆形
                    ctx.beginPath();
                    ctx.arc(point.x, point.y, currentRadius, 0, Math.PI * 2);
                    ctx.fillStyle = `rgba(0, 150, 255, ${opacity})`;
                    ctx.fill();
                    ctx.strokeStyle = `rgba(0, 150, 255, ${opacity})`;
                    ctx.lineWidth = 2;
                    ctx.stroke();

                    return true; // 保留未结束的波
                });

                // 请求下一帧动画,形成连续效果
                requestAnimationFrame(() => this.redraw());
            }
        }

        // 创建图层并添加到地图
        const waveLayer = new WaveLayer();
        waveLayer.addTo(map);

        // 示例:每2秒在地图中心生成一个新水波(可自行替换为指定经纬度)
        setInterval(() => {
            waveLayer.addWave(map.getCenter(), 0, 300, 4000);
        }, 2000);
    </script>
</body>
</html>

关键逻辑说明

  • 自定义Canvas图层:继承Leaflet的L.Canvas类,重写_drawLayer实现自定义绘制逻辑,这是Leaflet中用Canvas做自定义可视化的标准方式。
  • 帧动画驱动:用requestAnimationFrame替代setInterval,保证动画与浏览器刷新帧率同步,避免卡顿。
  • 状态管理:用数组存储所有活跃水波的时间、半径、坐标等状态,每帧更新后自动移除已结束的波,避免内存浪费。
  • 坐标转换:通过latLngToContainerPoint把经纬度转换成画布像素坐标,确保水波精准落在地图指定位置。

实用优化建议

  • 自定义样式:扩展addWave方法,支持传入颜色、线宽、填充/描边开关等参数,适配不同视觉需求。
  • 范围控制:如果需要限制水波在指定区域内显示,可以在添加水波前判断圆心是否在目标范围内,或者用Canvas的clip()方法裁剪绘制区域。
  • 性能控制:同时生成大量水波时,限制最大活跃波数量;地图缩放/拖拽时暂时暂停动画,减少不必要的计算。

内容的提问来源于stack exchange,提问作者Redamancy-qian

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 03:35:21