经纬志
首页 / 实战手册 / 瓦片

缩放到 17 级直接跳到 19 级,缺的两级全是灰块

先看结论

滚轮滚一下,级别跳过 18 直接到 19,中间两级的地图从来没出现过。

最后验证 2026-11-03 · 多技术栈 · 更新 2026-10-29 · 引用本文

现象

配置 minZoom: 10, maxZoom: 19,但滚轮每滚一格都跨两级: 从 17 直接到 19,控制台打印的 zoomend 也是 19。 17、18 级的瓦片请求从未发出,该区域一直是灰块。

触屏双指缩放更明显,一次手势能从 15 跳到 18。

原因

zoomSnap 决定缩放级别的最小步长,默认 1; zoomDelta 决定滚轮一次的增量,默认 1。 但两者被外部代码改过,或者被 zoomSnap: 0.5 与 zoomDelta: 2 混用后,滚轮一次走两级。

另一个常见原因是 maxNativeZoom: 19 而 maxZoom: 21,18、19 级虽然存在但被跳过, 因为 zoomDelta 被设成了 2。

排查过程

  1. 在 zoomend 里打印 map.getZoom(),看每次变化的跨度。
  2. 打印 map.options.zoomSnap 与 map.options.zoomDelta。
  3. 检查是否有代码在运行时修改了 map.options。
  4. 用 map.setZoom(18) 手动设置,看是否能停在 18 级。
map.on('zoomend', () => console.log(map.getZoom()));
// 15 -> 17 -> 19  每次 +2

console.log(map.options.zoomSnap, map.options.zoomDelta);
// 0.5 2   <- zoomDelta 为 2,滚轮一次跨两级

解决方案

zoomDelta 与 zoomSnap 要保持合理关系: zoomDelta 不应小于 zoomSnap,且通常等于 1。

const map = L.map('map', {
  center: [39.909, 116.397],
  zoom: 15,
  minZoom: 10,
  maxZoom: 19,
  zoomSnap: 0.5,      // 允许半级缩放
  zoomDelta: 1,       // 滚轮一次一级
  wheelPxPerZoomLevel: 120
});

// 瓦片只到 17 级时,用 maxNativeZoom 复用,不用提高 maxZoom
L.tileLayer('/tiles/{z}/{x}/{y}.png', {
  minZoom: 10,
  maxNativeZoom: 17,
  maxZoom: 19
}).addTo(map);

连续平滑缩放需关闭 zoomSnap,此时要同时设置 wheelDebounceTime,否则一次滚轮会触发多次 zoom。

L.map('map', {
  zoomSnap: 0,
  zoomDelta: 0.25,
  wheelDebounceTime: 40,
  wheelPxPerZoomLevel: 60
});

验证

连续滚轮 5 次,zoomend 输出的级别序列为 15→16→17→18→19,逐级递增; 17、18 级瓦片都有请求记录,灰块消失。

const levels = [];
map.on('zoomend', () => levels.push(map.getZoom()));
// 滚动 5 次后
console.assert(levels.every((z, i) => i === 0 || Math.abs(z - levels[i - 1]) <= 1), '仍跨级');
console.assert(levels.at(-1) === 19, '未到最大级别');

适用版本

库版本默认值
Leaflet1.9.xzoomSnap: 1、zoomDelta: 1
Leaflet1.7 / 1.8同
OpenLayers9.x用 View 的 constrainResolution 控制
引用本文

经纬志(2026)。《缩放到 17 级直接跳到 19 级,缺的两级全是灰块》。https://www.jingweizhi.com/cookbook/minzoom-maxzoom-jump.html(访问日期 2026-11-03)。