马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
概述
本文将先容Leaflet库中最后一个组件,即图层控制组件 Control.Layers。
源码实现
- export var Layers = Control.extend({
- options: {
- collapsed: true,
- position: "topright",
- autoZIndex: true,
- hideSingleBase: false,
- sortLayers: false,
- sortFunction: function (layerA, layerB, nameA, nameB) {
- return nameA < nameB ? -1 : nameB < nameA ? 1 : 0;
- },
- },
- initialize: function (baseLayers, overlays, options) {
- Util.setOptions(this, options);
- this._layerControlInputs = [];
- this._layers = [];
- this._lastZIndex = 0;
- this._handlingClick = false;
- this._preventClick = false;
- for (var i in baseLayers) {
- this._addLayer(baseLayers[i], i);
- }
- for (i in overlays) {
- this._addLayer(overlays[i], i, true);
- }
- },
- onAdd: function (map) {
- this._initLayout();
- this._update();
- this._map = map;
- map.on("zoomend", this._checkDisabledLayers, this);
- for (var i = 0; i < this._layers.length; i++) {
- this._layers[i].layer.on("add remove", this._onLayerChange, this);
- }
- return this._container;
- },
- addTo: function (map) {
- Control.prototype.addTo.call(this, map);
- return this._expandIfNotCollapsed();
- },
- onRemove: function () {
- this._map.off("zoomend", this._checkDisabledLayers, this);
- for (var i = 0; i < this._layers.length; i++) {
- this._layers[i].layer.off("add remove", this._onLayerChange, this);
- }
- },
- addBaseLayer: function (layer, name) {
- this._addLayer(layer, name);
- return this._map ? this._update() : this;
- },
- addOverlay: function (layer, name) {
- this._addLayer(layer, name, true);
- return this._map ? this._update() : this;
- },
- removeLayer: function (layer) {
- layer.off("add remove", this._onLayerChange, this);
- var obj = this._getLayer(Util.stamp(layer));
- if (obj) {
- this._layers.splice(this._layers.indexOf(obj), 1);
- }
- return this._map ? this._update() : this;
- },
- expand: function () {
- DomUtil.addClass(this._container, "leaflet-control-layers-expanded");
- this._section.style.height = null;
- var acceptableHeight =
- this._map.getSize().y - (this._container.offsetTop + 50);
- if (acceptableHeight < this._section.clientHeight) {
- DomUtil.addClass(this._section, "leaflet-control-layers-scrollbar");
- this._section.style.height = acceptableHeight + "px";
- } else {
- DomUtil.removeClass(this._section, "leaflet-control-layers-scrollbar");
- }
- this._checkDisabledLayers();
- return this;
- },
- collapse: function () {
- DomUtil.removeClass(this._container, "leaflet-control-layers-expanded");
- return this;
- },
- _initLayout: function () {
- var className = "leaflet-control-layers",
- container = (this._container = DomUtil.create("div", className)),
- collapsed = this.options.collapsed;
- container.setAttribute("aria-haspopup", true);
- DomEvent.disableClickPropagation(container);
- DomEvent.disableScrollPropagation(container);
- var section = (this._section = DomUtil.create(
- "section",
- className + "-list"
- ));
- if (collapsed) {
- this._map.on("click", this.collapse, this);
- DomEvent.on(
- container,
- {
- mouseenter: this._expandSafely,
- mouseleave: this.collapse,
- },
- this
- );
- }
- var link = (this._layersLink = DomUtil.create(
- "a",
- className + "-toggle",
- container
- ));
- link.href = "#";
- link.title = "Layers";
- link.setAttribute("role", "button");
- DomEvent.on(
- link,
- {
- keydown: function (e) {
- if (e.keyCode === 13) {
- this._expandSafely();
- }
- },
- click: function (e) {
- DomEvent.preventDefault(e);
- this._expandSafely();
- },
- },
- this
- );
- if (!collapsed) {
- this.expand();
- }
- this._baseLayersList = DomUtil.create("div", className + "-base", section);
- this._separator = DomUtil.create("div", className + "-separator", section);
- this._overlaysList = DomUtil.create(
- "div",
- className + "-overlays",
- section
- );
- container.appendChild(section);
- },
- _getLayer: function (id) {
- for (var i = 0; i < this._layers.length; i++) {
- if (this._layers[i] && Util.stamp(this._layers[i].layer) === id) {
- return this._layers[i];
- }
- }
- },
- _addLayer: function (layer, name, overlay) {
- if (this._map) {
- layer.on("add remove", this._onLayerChange, this);
- }
- this._layers.push({
- layer: layer,
- name: name,
- overlay: overlay,
- });
- if (this.options.sortLayers) {
- this._layers.sort(
- Util.bind(function (a, b) {
- return this.options.sortFunction(a.layer, b.layer, a.name, b.name);
- }, this)
- );
- }
- if (this.options.autoZIndex && layer.setZIndex) {
- this._lastZIndex++;
- layer.setZIndex(this._lastZIndex);
- }
- this._expandIfNotCollapsed();
- },
- _update: function () {
- if (!this._container) {
- return this;
- }
- DomUtil.empty(this._baseLayersList);
- DomUtil.empty(this._overlaysList);
- this._layerControlInputs = [];
- var baseLayersPresent,
- overlaysPresent,
- i,
- obj,
- baseLayersCount = 0;
- for (i = 0; i < this._layers.length; i++) {
- obj = this._layers[i];
- this._addItem(obj);
- overlaysPresent = overlaysPresent || obj.overlay;
- baseLayersPresent = baseLayersPresent || !obj.overlay;
- baseLayersCount += !obj.overlay ? 1 : 0;
- }
- if (this.options.hideSingleBase) {
- baseLayersPresent = baseLayersPresent && baseLayersCount > 1;
- this._baseLayersList.style.display = baseLayersPresent ? "" : "none";
- }
- this._separator.style.display =
- overlaysPresent && baseLayersPresent ? "" : "none";
- return this;
- },
- _onLayerChange: function (e) {
- if (!this._handlingClick) {
- this._update();
- }
- var obj = this._getLayer(Util.stamp(e.target));
- var type = obj.overlay
- ? e.type === "add"
- ? "overlayadd"
- : "overlayremove"
- : e.type === "add"
- ? "baselayerchange"
- : null;
- if (type) {
- this._map.fire(type, obj);
- }
- },
- _createRadioElement: function (name, checked) {
- var radioHtml =
- '<input type="radio" class="leaflet-control-layers-selector" name="' +
- name +
- '"' +
- (checked ? ' checked="checked"' : "") +
- "/>";
- var radioFragment = document.createElement("div");
- radioFragment.innerHTML = radioHtml;
- return radioFragment.firstChild;
- },
- _addItem: function (obj) {
- var label = document.createElement("label"),
- checked = this._map.hasLayer(obj.layer),
- input;
- if (obj.overlay) {
- input = document.createElement("input");
- input.type = "checkbox";
- input.className = "leaflet-control-layers-selector";
- input.defaultChecked = checked;
- } else {
- input = this._createRadioElement(
- "leaflet-base-layers_" + Util.stamp(this),
- checked
- );
- }
- this._layerControlInputs.push(input);
- input.layerId = Util.stamp(obj.layer);
- DomEvent.on(input, "click", this._onInputClick, this);
- var name = document.createElement("span");
- name.innerHTML = " " + obj.name;
- var holder = document.createElement("span");
- label.appendChild(holder);
- holder.appendChild(input);
- holder.appendChild(name);
- var container = obj.overlay ? this._overlaysList : this._baseLayersList;
- container.appendChild(label);
- this._checkDisabledLayers();
- return label;
- },
- _onInputClick: function () {
- if (this._preventClick) {
- return;
- }
- var inputs = this._layerControlInputs,
- input,
- layer;
- var addedLayers = [],
- removedLayers = [];
- this._handlingClick = true;
- for (var i = inputs.length - 1; i >= 0; i--) {
- input = inputs[i];
- layer = this._getLayer(input.layerId).layer;
- if (input.checked) {
- addedLayers.push(layer);
- } else if (!input.checked) {
- removedLayers.push(layer);
- }
- }
- for (i = 0; i < removedLayers.length; i++) {
- if (this._map.hasLayer(removedLayers[i])) {
- this._map.removeLayer(removedLayers[i]);
- }
- }
- for (i = 0; i < addedLayers.length; i++) {
- if (!this._map.hasLayer(addedLayers[i])) {
- this._map.addLayer(addedLayers[i]);
- }
- }
- this._handlingClick = false;
- this._refocusOnMap();
- },
- _checkDisabledLayers: function () {
- var inputs = this._layerControlInputs,
- input,
- layer,
- zoom = this._map.getZoom();
- for (var i = inputs.length - 1; i >= 0; i--) {
- input = inputs[i];
- layer = this._getLayer(input.layerId).layer;
- input.disabled =
- (layer.options.minZoom !== undefined && zoom < layer.options.minZoom) ||
- (layer.options.maxZoom !== undefined && zoom > layer.options.maxZoom);
- }
- },
- _expandIfNotCollapsed: function () {
- if (this._map && !this.options.collapsed) {
- this.expand();
- }
- return this;
- },
- _expandSafely: function () {
- var section = this._section;
- this._preventClick = true;
- DomEvent.on(section, "click", DomEvent.preventDefault);
- this.expand();
- var that = this;
- setTimeout(function () {
- DomEvent.off(section, "click", DomEvent.preventDefault);
- that._preventClick = false;
- });
- },
- });
- export var layers = function (baseLayers, overlays, options) {
- return new Layers(baseLayers, overlays, options);
- };
复制代码 核心结构
- export var Layers = Control.extend({...});
- export var layers = function (...) { return new Layers(...) };
复制代码
- 继续自 Leaflet 的 Control 基类,实现图层控制功能。
- 提供工厂函数 layers() 简化实例化操作。
配置项 (options)
- options: {
- collapsed: true, // 默认折叠
- position: "topright", // 控件位置
- autoZIndex: true, // 自动管理图层 z-index
- hideSingleBase: false, // 是否隐藏单一基础图层
- sortLayers: false, // 是否排序图层
- sortFunction: (a, b) => { ... } // 自定义排序函数
- }
复制代码 关键配置说明
- autoZIndex
自动为新图层分配递增的 z-index,确保叠加次序精确。
- sortLayers
启用后按 sortFunction 排序图层(默认按名称字母排序)。
- hideSingleBase
当仅有一个底子图层时隐藏其选项地区。
初始化 (initialize)
- initialize: function (baseLayers, overlays, options) {
- Util.setOptions(this, options);
- this._layerControlInputs = []; // 存储输入控件
- this._layers = []; // 存储图层信息
- this._lastZIndex = 0; // 自动 Z-Index 计数器
- // 添加初始图层
- for (var i in baseLayers) this._addLayer(baseLayers[i], i);
- for (i in overlays) this._addLayer(overlays[i], i, true);
- }
复制代码 参数说明
- baseLayers: 底子图层对象(互斥,如地图类型切换)
- overlays: 覆盖层对象(可叠加,如标记层)
生命周期方法
onAdd(map)
- onAdd: function (map) {
- this._initLayout(); // 初始化 DOM 结构
- this._update(); // 渲染图层选项
- this._map = map;
- map.on("zoomend", this._checkDisabledLayers, this); // 监听缩放事件
- // 绑定图层变化事件
- this._layers.forEach(layer => layer.layer.on("add remove", this._onLayerChange, this));
- }
复制代码 onRemove()
- onRemove: function () {
- this._map.off("zoomend", this._checkDisabledLayers, this);
- // 解绑图层事件
- this._layers.forEach(layer => layer.layer.off("add remove", this._onLayerChange, this));
- }
复制代码 图层管理 API
添加/移除图层
- addBaseLayer(layer, name); // 添加基础图层
- addOverlay(layer, name); // 添加覆盖层
- removeLayer(layer); // 移除指定图层
复制代码 核心逻辑方法
- _addLayer(layer, name, overlay) {
- // 处理排序、自动 Z-Index
- if (this.options.sortLayers) this._layers.sort(...);
- if (this.options.autoZIndex) layer.setZIndex(++this._lastZIndex);
- }
复制代码 DOM 与交互
控件布局 (_initLayout)
- _initLayout: function () {
- // 创建 DOM 结构
- this._container = DomUtil.create("div", "leaflet-control-layers");
- this._section = DomUtil.create("section", "leaflet-control-layers-list");
- // 折叠/展开交互逻辑
- if (this.options.collapsed) {
- this._map.on("click", this.collapse);
- DomEvent.on(container, { mouseenter: this._expandSafely, mouseleave: this.collapse });
- }
- }
复制代码 更新逻辑 (_update)
- _update: function () {
- // 清空并重新渲染所有选项
- DomUtil.empty(this._baseLayersList);
- DomUtil.empty(this._overlaysList);
- this._layers.forEach(layer => this._addItem(layer));
- }
复制代码 变乱处理
输入控件点击 (_onInputClick)
- _onInputClick: function () {
- // 处理图层显隐切换
- const addedLayers = [], removedLayers = [];
- this._layerControlInputs.forEach(input => {
- const layer = this._getLayer(input.layerId).layer;
- input.checked ? addedLayers.push(layer) : removedLayers.push(layer);
- });
- // 更新地图图层
- removedLayers.forEach(layer => this._map.removeLayer(layer));
- addedLayers.forEach(layer => this._map.addLayer(layer));
- }
复制代码 图层状态变革 (_onLayerChange)
- _onLayerChange: function (e) {
- // 触发 Leaflet 事件:baselayerchange / overlayadd / overlayremove
- const obj = this._getLayer(Util.stamp(e.target));
- const eventType = obj.overlay ?
- (e.type === "add" ? "overlayadd" : "overlayremove") :
- (e.type === "add" ? "baselayerchange" : null);
- if (eventType) this._map.fire(eventType, obj);
- }
复制代码 辅助功能
动态禁用图层 (_checkDisabledLayers)
- _checkDisabledLayers: function () {
- // 根据当前缩放级别禁用不符合条件的图层
- const zoom = this._map.getZoom();
- this._layerControlInputs.forEach(input => {
- const layer = this._getLayer(input.layerId).layer;
- input.disabled =
- (layer.options.minZoom !== undefined && zoom < layer.options.minZoom) ||
- (layer.options.maxZoom !== undefined && zoom > layer.options.maxZoom);
- });
- }
复制代码 安全睁开逻辑 (_expandSafely)
- _expandSafely: function () {
- // 临时阻止点击事件防止误操作
- this._preventClick = true;
- DomEvent.on(this._section, "click", DomEvent.preventDefault);
- setTimeout(() => {
- DomEvent.off(this._section, "click", DomEvent.preventDefault);
- this._preventClick = false;
- }, 0);
- }
复制代码 计划亮点
- 相应式计划
- 自动根据地图缩放级别禁用不符合条件的图层选项
- 睁开时动态盘算最大高度避免溢出视口
- 可扩展性
- 支持通过 sortFunction 自定义图层排序规则
- 允许通过 autoZIndex 自动管理图层叠加次序
- 无障碍支持
- 使用 aria-haspopup 标记控件
- 支持键盘操作(通过回车键睁开)
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |