自定义控件
控件是地图上的一块界面,交互是对鼠标手势的响应。 大部分「按钮点了没反应」的问题,都是把这两者的职责搞混了。
| 维度 | ol.control.Control | ol.interaction.Interaction |
|---|---|---|
| 职责 | 界面元素,按钮、面板、比例尺 | 手势与鼠标事件,绘制、拖动、选择 |
| 挂载 | map.addControl() | map.addInteraction() |
| 位置 | 地图容器或指定 DOM 节点 | 无界面 |
| 典型用途 | 底图切换、图层开关、状态显示 | 绘制、修改、框选、测距 |
一个控件可以调用交互对象的方法,比如按钮触发 draw.interaction 的启用。
反过来不建议:交互里直接改 DOM 会让状态难以追踪。
最小可用的控件
继承 ol.control.Control,在构造里准备好 DOM,交给父类。
父类的构造参数 element 就是最终显示的内容。
class BaseSwitcher extends ol.control.Control {
constructor(opt_options) {
const options = opt_options || {};
const button = document.createElement('button');
button.type = 'button';
button.className = 'base-switcher-btn';
button.textContent = '切换底图';
button.title = '在影像与街道底图之间切换';
const element = document.createElement('div');
element.className = 'base-switcher ol-unselectable ol-control';
element.appendChild(button);
super({ element: element, target: options.target });
this.onToggle_ = options.onToggle;
button.addEventListener('click', this.handleClick_.bind(this), false);
}
handleClick_(event) {
event.preventDefault();
if (this.onToggle_) this.onToggle_();
}
}
var switcher = new BaseSwitcher({
onToggle: function () {
var usingImagery = map.getLayers().item(0).get('name') === 'imagery';
map.getLayers().setAt(0, usingImagery ? streetLayer : imageryLayer);
}
});
map.addControl(switcher);
类名里的 ol-unselectable 阻止地图的拖动与缩放手势穿透到控件上,
ol-control 提供默认的定位与背景样式。缺了前者,
点按钮时地图会跟着平移。
不要用箭头函数做事件回调。控件销毁时无法解绑匿名函数。
用 bind(this) 保存引用,并在 setMap(null) 时移除监听。
控件与地图状态同步
比例尺、坐标读数这类控件需要跟随视图变化。重写 setMap,
在拿到地图实例后注册监听,移除时解绑。
class ResolutionReadout extends ol.control.Control {
constructor() {
const element = document.createElement('div');
element.className = 'resolution-readout ol-unselectable';
super({ element: element });
this.element_ = element;
this.moveKey_ = null;
}
setMap(map) {
if (this.moveKey_) {
ol.Observable.unByKey(this.moveKey_);
this.moveKey_ = null;
}
super.setMap(map);
if (!map) return;
this.moveKey_ = map.on('moveend', this.update_.bind(this));
this.update_();
}
update_() {
const map = this.getMap();
if (!map) return;
const view = map.getView();
const mpp = ol.proj.getPointResolution(
view.getProjection(), view.getResolution(), view.getCenter()
);
this.element_.textContent = '1 像素 ≈ ' + mpp.toFixed(1) + ' 米';
}
}
map.addControl(new ResolutionReadout());
getResolution() 的单位是地图单位/像素。在 Web 墨卡托下,
高纬度地区的实际地面距离会被放大,因此要用 getPointResolution
换算,不能直接用分辨率乘以比例尺。
把控件放到页面工具栏
控件不一定非要在图面上。传入 target 或直接把
getElement() 挂到页面节点,就能放进顶栏、侧边面板。
此时地图交互事件不会干扰这些元素。
var toolbar = document.getElementById('map-toolbar');
var switcher = new BaseSwitcher({ onToggle: toggleBase });
map.addControl(switcher);
// 挂到页面工具栏,而不是地图容器
toolbar.appendChild(switcher.getElement());
// 需要移除时两者都要处理
map.removeControl(switcher);
switcher.getElement().remove();
注意事项
setMap可能被调用多次(重新挂载),注册监听前先解绑旧的。- 控件元素的样式要写在自己的类名下,直接改
.ol-control会影响所有内置控件。 - 按钮的
click事件要preventDefault(),否则表单场景会触发表单提交。 - 控件里读地图状态必须判空:
getMap()在控件被移除后返回undefined。 - 大量自定义控件会遮挡地图,折叠成菜单比平铺一排更实用。
经纬志(2026)。《自定义控件》。https://www.jingweizhi.com/openlayers/tutorial/custom-control.html(访问日期 2026-11-03)。