平时在使用OpenLayers的Overlay时常感觉不便,于是最近我便封装了一些Overlay增删改查的方法,以提高可用性。这边文章中我会介绍我封装的方法,同时记录这个过程中踩的一些坑。
添加Overlay
JavaScript
/**
* @abstract 添加overlay
* @param {*} map
* @param {object} options overlay的配置项
* @param {string} options.id overlay的id,可以用来唯一标识overlay
* @param {string} options.groupId overlay的组名,可以用来批量操作overlay
* @param {boolean} options.visibility overlay的初始可见性
* options中的其它参数参考 ol/Overlay的构造函数参数
*/
export const addOverlay = (map, options) => {
let {
groupId = "default",
positioning = "center-center",
visibility = true,
element,
...other
} = options;
// 将DOMstring转换为DOM元素
if (!(element instanceof HTMLElement)) {
const container = document.createElement("div");
container.innerHTML = element;
element = container.firstChild;
}
// 创建overlay
const overlay = new Overlay({
element,
positioning,
...other,
});
// 设置额外的属性
overlay.setProperties({
groupId,
visibility,
});
// 设置初始的可见性
overlay.on("change:map", () => {
if (!visibility) {
setTimeout(() => {
overlay.getElement().parentElement.style.display = "none";
}, 0);
}
});
// 添加到地图
map.addOverlay(overlay);
};
addOverlay方法的特别之处
我封装的添加Overlay的方法,主要的改造是三个地方。
第一,对Overlay的element
属性做了一个封装。
在我第一次使用Overlay的时候我就犯了一个错误,我给element
属性传递了一个DOMString
(比如<div class="test-overlay pink">粉色overlay</div>
),结果就出现了类似于下面的报错。这是因为Overlay的element
属性只能接收一个DOM元素。
现在经过我的封装后,我的
addOverlay
方法允许传入的element
属性是一个DOMString
。
第二,给Overlay添加一些额外的属性,这些属性会在后续帮助我去实现一些对Overlay的操作。
JavaScript
// 设置额外的属性
overlay.setProperties({
groupId,
visibility,
});
第三,给Overlay设置了初始的可见性,如果传入的visibility
参数为false
,就会暂时先将overlay隐藏。(我还考虑后续或许可以通过这个来实现popup)
JavaScript
// 设置初始的可见性
overlay.on("change:map", () => {
if (!visibility) {
setTimeout(() => {
overlay.getElement().parentElement.style.display = "none";
}, 0);
}
});
查询Overlay
JavaScript
/**
* @abstract 查找overlay
* @param {*} map
* @param {object} condition 筛选条件
* @param {string} condition.id overlay的id筛选需要操作的overlay
* @param {string} condition.groupId overlay的groupId筛选需要操作的overlay
* @param {function} condition.condition 自定义条件函数,筛选需要操作的overlay,返回true则操作,false则忽略
* @returns {Array} 符合条件的overlay数组
*/
export const findOverlay = (map, { id, groupId, condition }) => {
const targetOverlays = [];
const overlays = map.getOverlays().getArray();
overlays.forEach(overlay => {
const groupId_ = overlay.get("groupId");
const id_ = overlay.getId();
if (id) {
if (id_ === id) {
targetOverlays.push(overlay);
}
}
if (groupId) {
if (groupId_ === groupId) {
targetOverlays.push(overlay);
}
}
if (condition) {
if (condition(overlay, id_, groupId_, overlays)) {
targetOverlays.push(overlay);
}
}
});
return targetOverlays;
};
findOverlay支持的查询方式
我设计的查找Overlay的方法findOverlay
支持通过三种方式进行查找:
- 通过
id
查找,这个id
是创建 Overlay时的参数(我建议在创建overlay时最好都要添加这个参数)
JavaScript
// 查找id为'overlay-1'的overlay
findOverlay( map , { id : 'overlay-1' })
- 通过
groupId
查找,这个是我在addOverlay
方法中添加的一个额外属性,我的计划是通过groupId
实现对Overlay的分组管理,可以同时去操作一组的Overlay。
JavaScript
// 查找所有groupId 为 stationTooltip 的overlay
findOverlay( map , { groupId : 'stationTooltip' })
- 通过一个筛选函数
condition
来进行查找,这个就类似于js中Array.prototype.find()
方法。我会为地图中绑定的每个overlay都调用condition
方法,若condition
方法返回true
则将Overlay返回,若返回false
则Overlay会被忽略。
condition
方法将会接收到四个参数
-
overlay
,当前的overlay对象;id
,当前的overlay对象的id;groupId
,当前overlay对象的groupId;overlays
,地图中的所有overlay对象组成的数组
JavaScript
// 查找所有groupId 为 stationTooltip 的overlay
findOverlay( map , { condition:(overlay , id , groupId , overlays)=> groupId == "stationTooltip" })
最后,注意findOverlay
方法返回一个包含所有符合条件的Overlay对象的数组。
删除Overlay
JavaScript
/**
* @abstract 移除overlay
* @param {*} map
* @param {object} condition 筛选条件
* @param {string} condition.id overlay的id筛选需要操作的overlay
* @param {string} condition.groupId overlay的groupId筛选需要操作的overlay
* @param {function} condition.condition 自定义条件函数,筛选需要操作的overlay,返回true则操作,false则忽略
*/
export const removeOverlay = (map, { id, groupId, condition }) => {
const targetOverlays = findOverlay(map, { id, groupId, condition });
targetOverlays.forEach(overlay => {
map.removeOverlay(overlay);
});
};
/**
*@abstract 清除所有overlay
* @param {*} map
*/
export const clearOverlay = map => {
const overlays = map.getOverlays();
overlays.clear();
};
我封装了两个删除Overlay的方法removeOverlay
和clearOverlay
,前者删除符合条件的Overlay,后者清空地图中的所有Overlay。
错误的删除方式
之前我在网上看到下面的这种删除Overlay的方式。也就是边遍历边删除,这种方法是错误的。会有删除不尽的问题。
JavaScript
const overlays = map.getOverlays();
overlays.forEach((overlay, index) => {
if (overlay.getId() === id) {
overlays.removeAt(index);
}
});
正确的写法是先通过遍历将想要删除Overlay收集到一个数组中,然后再循环数组进行删除.。
JavaScript
const overlays = map.getOverlays();
const targetOverlays = []
overlays.forEach((overlay, index) => {
if (overlay.getId() === id) {
targetOverlays.push(overlay)
}
});
targetOverlays.forEach((overlay)=>{
map.removeOverlay(overlay)
})
显示/隐藏Overlay
JavaScript
/**
* @abstract 设置overlay的可见性
* @param {*} map
* @param {object} condition 筛选条件
* @param {string} condition.id overlay的id筛选需要操作的overlay
* @param {string} condition.groupId overlay的groupId筛选需要操作的overlay
* @param {function} condition.condition 自定义条件函数,筛选需要操作的overlay,返回true则操作,false则忽略
* @param {boolean} visible 要设置的可见性
*/
export const setOverlayVisibility = (
map,
{ id, groupId, condition, visible }
) => {
const targetOverlays = findOverlay(map, { id, groupId, condition });
targetOverlays.forEach(overlay => {
overlay.setProperties({
visibility: visible,
});
const element = overlay.getElement();
element.parentElement.style.display = visible ? "flex" : "none";
});
};
/**
* @abstract 切换overlay的可见性
* @param {*} map
* @param {object} condition 筛选条件
* @param {string} condition.id overlay的id筛选需要操作的overlay
* @param {string} condition.groupId overlay的groupId筛选需要操作的overlay
* @param {function} condition.condition 自定义条件函数,筛选需要操作的overlay,返回true则操作,false则忽略
*/
export const toggleOverlayVisibility = (map, { id, groupId, condition }) => {
const targetOverlays = findOverlay(map, { id, groupId, condition });
targetOverlays.forEach(overlay => {
const newVisibility = !overlay.get("visibility");
overlay.setProperties({
visibility: newVisibility,
});
const element = overlay.getElement();
element.parentElement.style.display = newVisibility ? "flex" : "none";
});
};
显示/隐藏Overlay的方法我也封装了两个 setOverlayVisibility
用于手动的设置显隐,toggleOverlayVisibility
用于自动设置显隐。
隐藏Overlay的方法是去修改Overlay的element的父元素的display
。我们在创建Overlay的时候传入的element实际上会被用另一个元素包装起来,这个元素(默认class为ol-overlay-container ol-selectable
的元素)才是Overlay的根元素。