经纬志
首页 / MapLibre / 教程

相机与动画

先看结论

MapLibre 的相机有六个自由度:中心点、缩放、旋转、倾斜。切换相机有四个方法, 动画由内部的缓动函数驱动,不是 CSS 过渡,因此不能用 CSS 的 duration 或 easing 干预。

最后验证 2026-11-03 · MapLibre GL JS 4.x · 更新 2026-10-29 · 引用本文

四个方法

方法动画适用
jumpTo无,立即生效初始化、恢复书签视角
easeTo缓动,可选线性短距离平移、俯仰调整
flyTo先拉远再拉近的抛物线跨区域跳转,视觉上有「飞过去」的感觉
fitBounds内部调用 easeTo让某个范围刚好装满容器
// 立即定位
map.jumpTo({ center: [116.391263, 39.907359], zoom: 11 });

// 缓动平移,保持缩放
map.easeTo({
  center: [116.407000, 39.899000],
  duration: 800,
  easing: function (t) { return t * (2 - t); }
});

// 跨城市飞行
map.flyTo({
  center: [121.473701, 31.230416],
  zoom: 12,
  bearing: 0,
  pitch: 35,
  duration: 3600,
  curve: 1.4,        // 路径弯曲程度,越大越「高飞」
  essential: true    // 用户开启减少动效时仍然执行
});

essential: true 值得注意:系统启用「减少动态效果」时, MapLibre 会把 flyTo 降级为直接跳转。业务上必须让用户看到位置变化时, 显式标记为 essential。

适配范围

fitBounds 接受一个 [[西, 南], [东, 北]] 的数组, 或者 LngLatBounds 实例。四个参数决定最终视野:

参数作用默认
padding四边留白,像素或 {top, bottom, left, right}0
maxZoom结果缩放的上限无
duration动画时长,毫秒500
linear是否匀速,关闭则用缓动false
offset整体平移结果,给侧栏让位[0, 0]
var bounds = [
  [116.20, 39.80],      // 西南
  [116.55, 40.05]       // 东北
];

map.fitBounds(bounds, {
  padding: { top: 40, bottom: 120, left: 320, right: 40 },
  maxZoom: 15,
  duration: 1200
});

左侧有固定侧栏时,padding.left 必须等于侧栏宽度, 否则范围虽然装下了,但被侧栏遮掉一部分。用 padding 而不是事后 panBy,结果更稳定。

动画时序与取消

动画期间 map.getCenter() 返回的是当前帧的位置,不是终点。 需要终点数据时,从事件或传入的参数里取。动画被打断时 moveend 仍会触发一次。

function flyHome() {
  map.once('moveend', function () {
    console.log('到达', map.getCenter().toArray(), map.getZoom());
  });

  map.flyTo({ center: [116.391263, 39.907359], zoom: 12 });
}

// 用户开始手动操作时,取消正在进行的动画
map.on('dragstart', function () {
  map.stop();
});

map.stop() 立即结束动画并停在中途位置,不返回起点。 需要「取消后回到原处」就得自己记下起始状态,再 easeTo 回去。

连续动画与帧驱动

相机动画也可以用 easeTo 做帧驱动:每帧算一次新中心, 关掉动画时长,得到匀速旋转或跟随效果。

var bearing = map.getBearing();

function spin() {
  bearing = (bearing + 0.3) % 360;
  map.easeTo({
    bearing: bearing,
    duration: 0,          // 立即生效,由下一帧驱动
    animate: false
  });
  requestAnimationFrame(spin);
}

// 开始前先停掉用户交互,避免视角被抢
map.dragRotate.disable();
spin();

duration 与 animate 是两套开关。animate: false 直接禁用动画;只把 duration 设成 0 时,部分版本仍会走一帧的补间。 需要绝对即时,两个都写。

注意事项

fitBounds 遇到跨 180 度经线

从东经 170 度到西经 170 度的范围,直接写成 [[170, ...], [-170, ...]] 会让相机横穿整个地球。 这种跨日期变更线的范围,需要拆成两块分别适配,或先把经度统一到连续区间。

动画期间修改样式

setStyle 会重建渲染管线,正在进行的相机动画被打断, moveend 可能与 styledata 交错触发。 换底图与飞行动画不要同时进行,先 map.stop()。

移动端长动画耗电

三秒以上的 flyTo 会让 GPU 持续满负荷。移动端把 duration 压到 1500 毫秒以内,或改用 easeTo, 体感差异不大,耗电与发热明显更低。

引用本文

经纬志(2026)。《相机与动画》。https://www.jingweizhi.com/maplibre/tutorial/camera-animation.html(访问日期 2026-11-03)。