📌 学习目标
- 掌握动画化线条的实现方法
- 理解相关API的使用
- 能够独立完成类似功能开发
🎯 核心概念
通过在每帧更新GeoJSON数据源来动画化线条。
💻 完 整 代 码
代码示例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>为线添加动画</title>
<meta property="og:description" content="通过在每一帧更新 GeoJSON 源来为线添加动画。" />
<meta property="og:created" content="2023-06-27" />
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="https://unpkg.com/maplibre-gl@5.24.0/dist/maplibre-gl.css" />
<script src="https://unpkg.com/maplibre-gl@5.24.0/dist/maplibre-gl.js"></script>
<style>
body { margin: 0; padding: 0; }
html, body, #map { height: 100%; }
button { position: absolute; top: 20px; left: 20px; }
#pause::after { content: "Pause"; }
#pause.pause::after { content: "Play"; }
</style>
</head>
<body>
<div id="map"></div>
<button id="pause"></button>
<script>
const map = new maplibregl.Map({
container: "map",
style: "https://demotiles.maplibre.org/style.json",
center: [0, 0],
zoom: 0.5,
});
const geojson = {
type: "FeatureCollection",
features: [{ type: "Feature", geometry: { type: "LineString", coordinates: [[0, 0]] } }],
};
const speedFactor = 30;
let animation;
let startTime = 0;
let progress = 0;
let resetTime = false;
const pauseButton = document.getElementById("pause");
map.on("load", () => {
map.addSource("line", { type: "geojson", data: geojson });
map.addLayer({
id: "line-animation",
type: "line",
source: "line",
layout: { "line-cap": "round", "line-join": "round" },
paint: { "line-color": "#ed6498", "line-width": 5, "line-opacity": 0.8 },
});
startTime = performance.now();
animateLine();
pauseButton.addEventListener("click", () => {
pauseButton.classList.toggle("pause");
if (pauseButton.classList.contains("pause")) {
cancelAnimationFrame(animation);
} else {
resetTime = true;
animateLine();
}
});
document.addEventListener("visibilitychange", () => {
resetTime = true;
});
function animateLine(timestamp) {
if (resetTime) {
startTime = performance.now() – progress;
resetTime = false;
} else {
progress = timestamp – startTime;
}
if (progress > speedFactor * 360) {
startTime = timestamp;
geojson.features[0].geometry.coordinates = [];
} else {
const x = progress / speedFactor;
const y = Math.sin((x * Math.PI) / 90) * 40;
geojson.features[0].geometry.coordinates.push([x, y]);
map.getSource("line").setData(geojson);
}
animation = requestAnimationFrame(animateLine);
}
});
</script>
</body>
</html>
🔍 代码解析
初始化地图
使用 new maplibregl.Map() 创建地图实例,配置基本参数。本示例的核心特色是展示如何通过逐帧更新 GeoJSON 数据源来实现线条动画效果。
关键配置项
- container: 地图容器的 DOM 元素 ID
- style: 使用 MapLibre 官方样式 https://demotiles.maplibre.org/style.json
- center: 地图初始中心点 [0, 0]
- zoom: 初始缩放级别为 0.5,显示全球视图
动画变量定义
const speedFactor = 30; // 每经度度数所需的帧数
let animation; // 存储动画帧ID,用于取消
let startTime = 0; // 动画开始时间
let progress = 0; // 动画进度(毫秒)
let resetTime = false; // 是否需要重置时间
动画函数实现
function animateLine(timestamp) {
// 处理暂停后的时间重置
if (resetTime) {
startTime = performance.now() – progress;
resetTime = false;
} else {
progress = timestamp – startTime;
}
// 完成一圈后重置
if (progress > speedFactor * 360) {
startTime = timestamp;
geojson.features[0].geometry.coordinates = [];
} else {
// 计算正弦波坐标
const x = progress / speedFactor;
const y = Math.sin((x * Math.PI) / 90) * 40;
geojson.features[0].geometry.coordinates.push([x, y]);
map.getSource("line").setData(geojson);
}
animation = requestAnimationFrame(animateLine);
}
⚙️ 参数说明
| container | string | 是 | – | 地图容器元素的 ID |
| style | string/object | 是 | – | 地图样式 URL 或内联样式对象 |
| center | [number, number] | 否 | [0, 0] | 初始中心点坐标 |
| zoom | number | 否 | 0 | 初始缩放级别 |
动画参数
| speedFactor | number | 每经度度数所需的帧数,数值越小动画越快 |
| progress | number | 动画进度(毫秒) |
| resetTime | boolean | 是否需要重置开始时间 |
🎨 效果说明

运行代码后,地图上会显示一条动态绘制的正弦波形线条:
- 动画轨迹: 从左向右绘制,呈现正弦波形(Y轴振幅为40度)
- 颜色: 粉色 (#ed6498),半透明效果 (opacity: 0.8)
- 线宽: 5像素,圆角端点和拐角
- 循环播放: 完成一圈(360度经度)后自动重置
- 控制按钮: 点击按钮可暂停/继续动画
动画原理:
💡 常 见 问 题
Q1: 为什么使用 requestAnimationFrame? A: requestAnimationFrame 会与浏览器的刷新率同步,提供更流畅的动画效果,并且在页面隐藏时自动暂停,节省资源。
Q2: 如何控制动画速度? A: 修改 speedFactor 参数,数值越小动画越快,数值越大动画越慢。
Q3: 动画暂停后如何保持进度? A: 通过保存 progress 值,重新开始时计算新的 startTime = performance.now() – progress。
Q4: 页面切换后动画会错乱吗? A: 不会。通过监听 visibilitychange 事件,在页面切换时设置 resetTime = true,重新计算开始时间。
📝 练习任务
🌟 最佳实践
🔗 延伸阅读
-
Map API文档
-
MapLibre GL JS 官方文档
-
[下一课预告]:将继续学习地图图层的基础知识
本文是MapLibre GL JS实践课程系列的一部分,欢迎关注收藏





