04. 地图控件

4 阅读5分钟

MapLibre 学习指南:文章导航

在线预览地址:env-00jy66xyyn4y-static.normal.cloudstatic.cn/maplibre-ba…

base 代码仓库地址:

如果这个系列对你有帮助,欢迎给仓库点一个 Star,代码仓库可以拉取到完整的学习代码,在docs目录下有规划良好的.md学习文档,希望可以帮助到你(请给个免费的start哦)。你的支持是我持续更新和完善 MapLibre 学习内容的动力,也能帮助更多正在学习 WebGIS / MapLibre 的前端同学找到这份资料。[个人微信: 1576554007 欢迎一起学习交流]

第一阶段:入门基础(第 1-4 节)

节次标题核心内容文档路径
01认识 MapLibre GL JSMapLibre 简介与生态、与 Mapbox 的关系、开源许可、应用场景、与其他地图库(Leaflet/OpenLayers/Cesium)对比src/docs/stage1/01.认识MapLibre.md
02环境搭建与第一张地图Vue3+Vite 项目创建、安装 MapLibre GL JS、创建第一张地图(Map 构造函数参数详解)、地图容器与响应式尺寸src/docs/stage1/02.环境搭建与第一张地图.md
03地图基础操作缩放/平移/旋转/倾斜、flyTo/easeTo/jumpTo 动画方法、fitBounds 自适应范围、地图事件监听(click/move/zoom/load)src/docs/stage1/03.地图基础操作.md
04地图控件NavigationControl、ScaleControl、GeolocateControl、FullscreenControl、AttributionControl、自定义控件(IControl 接口)src/docs/stage1/04.地图控件.md

04. 地图控件

概述

MapLibre GL JS 内置了多种地图控件,同时提供 IControl 接口用于创建自定义控件。本节学习:

  • 5 种内置控件的使用
  • 控件位置管理
  • 自定义控件开发(IControl 接口)

添加和移除控件

// 添加控件
map.addControl(control, position?)

// 移除控件
map.removeControl(control)

控件位置

支持 4 个位置,默认 'top-right'

┌──────────────────────────────────┐
│  top-left          top-right     │
│                                  │
│           地图区域               │
│                                  │
│  bottom-left    bottom-right     │
└──────────────────────────────────┘

内置控件详解

1. NavigationControl — 导航控件

提供缩放按钮(+/-)和指南针按钮。

const nav = new maplibregl.NavigationControl({
  showCompass: true,   // 显示指南针(默认 true)
  showZoom: true,      // 显示缩放按钮(默认 true)
  visualizePitch: true // 指南针是否可视化倾斜角度(默认 false)
})

map.addControl(nav, 'top-right')

功能说明

  • 点击 + / - 缩放地图
  • 点击指南针重置为正北
  • 拖拽指南针可旋转地图

2. ScaleControl — 比例尺控件

显示当前缩放级别对应的比例尺。

const scale = new maplibregl.ScaleControl({
  maxWidth: 150,     // 比例尺最大宽度(像素),默认 100
  unit: 'metric'     // 单位:'metric'(公制) | 'imperial'(英制) | 'nautical'(海里)
})

map.addControl(scale, 'bottom-left')

特点:地图缩放时比例尺会自动更新。


3. FullscreenControl — 全屏控件

切换地图为全屏模式。

const fullscreen = new maplibregl.FullscreenControl({
  container: document.querySelector('#map')  // 可选,指定全屏的容器
})

map.addControl(fullscreen, 'top-right')

⚠️ 部分浏览器/iframe 中可能不支持全屏 API。


4. GeolocateControl — 定位控件

获取用户的地理位置并在地图上标注。

const geolocate = new maplibregl.GeolocateControl({
  positionOptions: {
    enableHighAccuracy: true  // 启用高精度定位
  },
  trackUserLocation: true,     // 持续追踪用户位置
  showUserHeading: true        // 显示用户朝向
})

map.addControl(geolocate, 'top-right')

// 监听定位事件
geolocate.on('geolocate', (e) => {
  console.log('定位成功:', e.coords.longitude, e.coords.latitude)
})

geolocate.on('error', (e) => {
  console.error('定位失败:', e.message)
})

⚠️ 需要 HTTPS 环境才能使用定位功能(localhost 除外)。


5. AttributionControl — 归属控件

显示地图数据的版权归属信息。

// 默认已自动添加,可通过构造参数禁用后手动添加
const map = new maplibregl.Map({
  attributionControl: false  // 禁用默认的归属控件
})

// 手动添加自定义归属
const attribution = new maplibregl.AttributionControl({
  compact: true,                              // 折叠模式
  customAttribution: '© 我的地图项目'          // 自定义归属文本
})

map.addControl(attribution, 'bottom-right')

自定义控件(IControl 接口)

MapLibre 提供 IControl 接口,只需实现两个方法:

interface IControl {
  onAdd(map: Map): HTMLElement    // 添加时调用,返回控件 DOM
  onRemove(map: Map): void        // 移除时调用,清理资源
}

示例:坐标显示控件

class CoordinateControl implements maplibregl.IControl {
  _map: maplibregl.Map | undefined
  _container: HTMLElement | undefined

  onAdd(map: maplibregl.Map): HTMLElement {
    this._map = map

    // 创建控件容器
    this._container = document.createElement('div')
    this._container.className = 'maplibregl-ctrl maplibregl-ctrl-group'
    this._container.style.cssText = `
      padding: 6px 10px;
      font-size: 12px;
      background: #fff;
      border-radius: 4px;
      box-shadow: 0 1px 4px rgba(0,0,0,0.2);
    `

    // 监听地图移动,更新坐标
    const update = () => {
      if (!this._map || !this._container) return
      const center = this._map.getCenter()
      const zoom = this._map.getZoom()
      this._container.innerHTML = `
        📍 ${center.lng.toFixed(4)}, ${center.lat.toFixed(4)}<br/>
        🔍 Zoom: ${zoom.toFixed(2)}
      `
    }

    map.on('move', update)
    update()

    return this._container
  }

  onRemove(): void {
    this._container?.parentNode?.removeChild(this._container)
    this._map = undefined
  }
}

// 使用
map.addControl(new CoordinateControl(), 'bottom-right')

示例:一键回到初始位置

class HomeControl implements maplibregl.IControl {
  _map: maplibregl.Map | undefined
  _container: HTMLElement | undefined
  _homeCenter: [number, number]
  _homeZoom: number

  constructor(center: [number, number], zoom: number) {
    this._homeCenter = center
    this._homeZoom = zoom
  }

  onAdd(map: maplibregl.Map): HTMLElement {
    this._map = map
    this._container = document.createElement('div')
    this._container.className = 'maplibregl-ctrl maplibregl-ctrl-group'

    const button = document.createElement('button')
    button.type = 'button'
    button.title = '回到初始位置'
    button.innerHTML = '🏠'
    button.style.cssText = 'width:30px;height:30px;cursor:pointer;font-size:16px;border:none;background:#fff;'

    button.addEventListener('click', () => {
      this._map?.flyTo({
        center: this._homeCenter,
        zoom: this._homeZoom
      })
    })

    this._container.appendChild(button)
    return this._container
  }

  onRemove(): void {
    this._container?.parentNode?.removeChild(this._container)
    this._map = undefined
  }
}

// 使用
map.addControl(new HomeControl([116.39, 39.91], 10), 'top-left')

控件样式定制

使用 CSS 覆盖内置控件样式

/* 修改导航控件按钮大小 */
.maplibregl-ctrl-zoom-in,
.maplibregl-ctrl-zoom-out {
  width: 36px !important;
  height: 36px !important;
}

/* 修改比例尺样式 */
.maplibregl-ctrl-scale {
  background: rgba(0, 0, 0, 0.6) !important;
  color: #fff !important;
  border-color: #fff !important;
  font-size: 11px !important;
}

/* 自定义控件容器阴影 */
.maplibregl-ctrl-group {
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15) !important;
  border-radius: 8px !important;
}

自定义控件使用 Vue 组件

对于复杂的自定义控件,可以在 onAdd 中挂载 Vue 组件:

import { createApp } from 'vue'
import MyControlComponent from './MyControl.vue'

class VueControl implements maplibregl.IControl {
  _container: HTMLElement | undefined
  _app: any

  onAdd(map: maplibregl.Map): HTMLElement {
    this._container = document.createElement('div')
    this._app = createApp(MyControlComponent, { map })
    this._app.mount(this._container)
    return this._container
  }

  onRemove(): void {
    this._app?.unmount()
    this._container?.parentNode?.removeChild(this._container)
  }
}

控件最佳实践

  1. 合理安排位置:导航控件放右上角,比例尺放左下角,避免重叠
  2. 控件不宜过多:3-5 个为宜,过多会干扰地图交互
  3. 移动端适配:移动端可以隐藏部分控件,保持界面简洁
  4. 组件卸载:切换路由时要 removeControlmap.remove() 避免内存泄漏
  5. 样式类名规范:自定义控件容器添加 maplibregl-ctrl 类名,保持与内置控件一致的样式

本课小结

  • 5 种内置控件:Navigation、Scale、Fullscreen、Geolocate、Attribution
  • 控件通过 addControl(ctrl, position) 添加,支持 4 个位置
  • 自定义控件实现 IControl 接口(onAdd 返回 DOM,onRemove 清理)
  • 可以使用 CSS 覆盖内置控件样式
  • 复杂控件可在 onAdd 中挂载 Vue 组件

📌 上一节:03. 地图基础操作 📌 下一节:05. 地图样式详解