mcpField
M
mapbox-web-performance-patterns

mapbox-web-performance-patterns

github.com/mapbox开发832.5k 次安装4 周前更新
代码仓库

关于 mapbox-web-performance-patterns

为 Mapbox 应用开发提供性能优化指导,面向构建交互式地图的开发者。内容按用户体验影响排序,涵盖加载、渲染、数据规模与资源管理。

主要功能

  1. 01并行加载地图与数据
  2. 02优化初始包体和视口
  3. 03按数据量选择标记渲染方式
  4. 04延后非关键地图图层
  5. 05涵盖事件、内存与移动端优化

适用场景

  1. 01缩短地图首次交互等待
  2. 02处理大量地图标记
  3. 03为大型数据集选择加载方式

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/mapbox/mapbox-agent-skills --skill mapbox-web-performance-patterns

安装后在 Claude Code 中输入/mapbox-web-performance-patterns确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

AGENTS.md
evals/
evals.json
references/
data-loading.md
interactions.md
layers-styles.md
memory.md
mobile.md

详细介绍

Mapbox 性能模式技能

此技能提供性能优化指南,帮助构建快速、高效的 Mapbox 应用。模式按对用户体验的影响排序,优先介绍最关键的改进。

性能理念: 这些并非微优化。它们会表现为等待时间、卡顿,以及影响每次用户会话的重复成本。

优先级

性能问题按其对用户体验的影响排序:

  • 🔴 严重(优先修复):直接导致初始加载缓慢或明显卡顿
  • 🟡 高影响:造成明显延迟或资源使用增加
  • 🟢 优化:逐步改进,提升完善度

🔴 严重:消除初始化瀑布流

问题: 顺序加载会造成连锁延迟,每个资源都要等待前一个资源加载完成。

注意: 现代打包工具(Vite、Webpack 等)和 ESM 动态导入会自动处理代码分割和库加载。需要消除的主要瀑布流是数据加载——也就是顺序获取地图数据,而不是与地图初始化并行进行。

反模式:顺序加载数据
// ❌ BAD: Data loads AFTER map initializes
async function initMap() {
  const map = new mapboxgl.Map({
    container: 'map',
    accessToken: MAPBOX_TOKEN,
    style: 'mapbox://styles/mapbox/streets-v12'
  });

  // Wait for map to load, THEN fetch data
  map.on('load', async () => {
    const data = await fetch('/api/data'); // Waterfall!
    map.addSource('data', { type: 'geojson', data: await data.json() });
  });
}

时间线: 地图初始化(0.5s)→ 数据获取(1s)= 总计 1.5s

解决方案:并行加载数据
// ✅ GOOD: Data fetch starts immediately
async function initMap() {
  // Start data fetch immediately (don't wait for map)
  const dataPromise = fetch('/api/data').then((r) => r.json());

  const map = new mapboxgl.Map({
    container: 'map',
    accessToken: MAPBOX_TOKEN,
    style: 'mapbox://styles/mapbox/streets-v12'
  });

  // Data is ready when map loads
  map.on('load', async () => {
    const data = await dataPromise;
    map.addSource('data', { type: 'geojson', data });
    map.addLayer({
      id: 'data-layer',
      type: 'circle',
      source: 'data'
    });
  });
}

时间线: 地图初始化与数据获取的较长耗时 = 总计约 1s

设置精确的初始视口
// ✅ Set exact center/zoom so the map fetches the right tiles immediately
const map = new mapboxgl.Map({
  container: 'map',
  style: 'mapbox://styles/mapbox/streets-v12',
  center: [-122.4194, 37.7749],
  zoom: 13
});

// Use 'idle' to know when the initial viewport is fully rendered
// (all tiles, sprites, and other resources are loaded; no transitions in progress)
map.once('idle', () => {
  console.log('Initial viewport fully rendered');
});

如果你知道用户最先会看到的确切区域,预先设置 center 和 zoom,即可避免地图先显示默认视图,再平移或缩放到目标位置,从而避免浪费瓦片请求。

延迟加载非关键功能
// ✅ Load critical features first, defer others
const map = new mapboxgl.Map({
  /* config */
});

map.on('load', () => {
  // 1. Add critical layers immediately
  addCriticalLayers(map);

  // 2. Defer secondary features
  // Note: Standard style 3D buildings can be toggled via config:
  // map.setConfigProperty('basemap', 'show3dObjects', false);
  requestIdleCallback(
    () => {
      addTerrain(map);
      addCustom3DLayers(map); // For classic styles with custom fill-extrusion layers
    },
    { timeout: 2000 }
  );

  // 3. Defer analytics and non-visual features
  setTimeout(() => {
    initializeAnalytics(map);
  }, 3000);
});

影响: 大幅缩短可交互时间,尤其是在延迟加载地形和 3D 图层时


🔴 严重:优化初始包体积

问题: 包体积过大会延长慢速网络下的可交互时间。

注意: 现代打包工具(Vite、Webpack 等)会自动为基于框架的应用处理代码分割。以下建议主要适用于优化打包内容及其加载时机。

样式 JSON 对包体积的影响
// ❌ BAD: Inline massive style JSON (can be 500+ KB)
const style = {
  version: 8,
  sources: {
    /* 100s of lines */
  },
  layers: [
    /* 100s of layers */
  ]
};

// ✅ GOOD: Reference Mapbox-hosted styles
const map = new mapboxgl.Map({
  style: 'mapbox://styles/mapbox/streets-v12' // Fetched on demand
});

// ✅ OR: Store large custom styles externally
const map = new mapboxgl.Map({
  style: '/styles/custom-style.json' // Loaded separately
});

影响: 将内联样式改为托管样式后,初始包体积可减少 30-50%


🟡 高影响:优化标记数量

问题: 标记过多会导致渲染缓慢、交互延迟。

性能阈值
  • 少于 100 个标记:可以使用 HTML 标记(Marker 类)
  • 100-10,000 个标记:使用符号图层(由 GPU 加速)
  • 10,000 个以上标记:建议使用聚类
  • 100,000 个以上标记:使用矢量瓦片和服务端聚类
反模式:数千个 HTML 标记
// ❌ BAD: 5,000 HTML markers = 5+ second render, janky pan/zoom
restaurants.forEach((restaurant) => {
  const marker = new mapboxgl.Marker()
    .setLngLat([restaurant.lng, restaurant.lat])
    .setPopup(new mapboxgl.Popup().setHTML(restaurant.name))
    .addTo(map);
});

结果: 5,000 个 DOM 元素、交互缓慢、内存占用高

解决方案:使用符号图层(GeoJSON)
// ✅ GOOD: GPU-accelerated rendering, smooth at 10,000+ features
map.addSource('restaurants', {
  type: 'geojson',
  data: {
    type: 'FeatureCollection',
    features: restaurants.map((r) => ({
      type: 'Feature',
      geometry: { type: 'Point', coordinates: [r.lng, r.lat] },
      properties: { name: r.name, type: r.type }
    }))
  }
});

map.addLayer({
  id: 'restaurants',
  type: 'symbol',
  source: 'restaurants',
  layout: {
    'icon-image': 'restaurant',
    'icon-size': 0.8,
    'text-field': ['get', 'name'],
    'text-size': 12,
    'text-offset': [0, 1.5],
    'text-anchor': 'top'
  }
});

// Click handler (one listener for all features)
map.on('click', 'restaurants', (e) => {
  const feature = e.features[0];
  new mapboxgl.Popup().setLngLat(feature.geometry.coordinates).setHTML(feature.properties.name).addTo(map);
});

性能: 10,000 个要素可在 <100ms 内渲染

解决方案:高密度场景使用聚类
// ✅ GOOD: 50,000 markers → ~500 clusters at low zoom
map.addSource('restaurants', {
  type: 'geojson',
  data: restaurantsGeoJSON,
  cluster: true,
  clusterMaxZoom: 14, // Stop clustering at zoom 15
  clusterRadius: 50 // Radius relative to tile dimensions (512 = full tile width)
});

// Cluster circle layer
map.addLayer({
  id: 'clusters',
  type: 'circle',
  source: 'restaurants',
  filter: ['has', 'point_count'],
  paint: {
    'circle-color': ['step', ['get', 'point_count'], '#51bbd6', 100, '#f1f075', 750, '#f28cb1'],
    'circle-radius': ['step', ['get', 'point_count'], 20, 100, 30, 750, 40]
  }
});

// Cluster count label
map.addLayer({
  id: 'cluster-count',
  type: 'symbol',
  source: 'restaurants',
  filter: ['has', 'point_count'],
  layout: {
    'text-field': '{point_count_abbreviated}',
    'text-size': 12
  }
});

// Individual point layer
map.addLayer({
  id: 'unclustered-point',
  type: 'circle',
  source: 'restaurants',
  filter: ['!', ['has', 'point_count']],
  paint: {
    'circle-color': '#11b4da',
    'circle-radius': 6
  }
});

影响: 50,000 个标记可在 60 FPS 下流畅交互


总结:性能检查清单

构建 Mapbox 应用时,请按以下顺序检查这些优化项:

🔴 严重(优先处理)
  • 并行加载地图库和数据(消除瀑布流)
  • 对地图代码使用动态导入(缩减初始包体积)
  • 延迟加载非关键功能(地形、自定义 3D 图层、分析功能)
  • 标记超过 100 个时使用符号图层(而非 HTML 标记)
  • 为大型数据集实现基于视口的数据加载
🟡 高影响
  • 对地图事件处理程序进行防抖/节流(地理编码输入、moveend)
  • 使用图层筛选器和边界框优化 queryRenderedFeatures
  • 小于 5 MB 时使用 GeoJSON,大于 20 MB 时使用矢量瓦片
  • 在 SPA 清理或页面销毁时,始终调用 map.remove()
  • 附加 map.on('error', …)(或显示可见的错误 UI),避免样式、瓦片或令牌故障被静默忽略
  • 重用弹出框实例(不要在每次交互时都创建新实例)
  • 悬停/选择状态使用要素状态,而非动态创建图层
  • 聚类演示:生成足够多的点来测试聚类性能(数千个,而非几百个)
Agent 反模式:仅考虑理想路径

初版 Agent 代码通常只会创建地图,不设置 map.on('error'),也不调用 map.remove(),并且只使用少量点数据,从未测试过 cluster: true。生产级演示需要具备错误可见性、清理机制和真实的数据规模。

🟢 优化
  • 使用数据驱动样式整合多个图层
  • 添加针对移动设备的优化(圆形图层、禁用旋转)
  • 为图层设置 minzoom/maxzoom,避免在无关缩放级别渲染
  • 除非确有需要,否则不要启用 preserveDrawingBuffer 或 antialias
测量
// Measure initial load time
console.time('map-load');
map.on('load', () => {
  console.timeEnd('map-load');
  // isStyleLoaded() returns true when style, sources, tiles, sprites, and models are all loaded
  console.log('Style loaded:', map.isStyleLoaded());
});

// Monitor frame rate
let frameCount = 0;
map.on('render', () => frameCount++);
setInterval(() => {
  console.log('FPS:', frameCount);
  frameCount = 0;
}, 1000);

// Check memory usage (Chrome DevTools -> Performance -> Memory)

目标指标:

  • 交互时间: 在 3G 网络下 < 2 秒
  • 帧率: 平移/缩放期间保持 60 FPS
  • 内存增长: 每小时使用增长 < 10 MB
  • 包大小: 初始 < 500 KB(地图延迟加载)

参考文件

有关特定主题的详细模式,请加载对应的参考文件:

  • references/data-loading.md — GeoJSON 与 Vector Tiles 的决策矩阵、基于视口的加载、渐进式加载、大型数据集使用 Vector Tiles
  • references/interactions.md — 事件防抖/节流、优化要素查询、批量 DOM 更新
  • references/memory.md — 地图清理模式、弹出窗口/标记复用、要素状态与动态图层
  • references/mobile.md — 设备检测、针对移动设备优化的图层、触摸交互、构造函数选项
  • references/layers-styles.md — 使用数据驱动样式整合图层、简化表达式、基于缩放级别的可见性

评论

支持 Markdown