我最近在使用Overlay的时候遇到了一个问题,我向地图中添加了两种不同的Overlay(下图中的蓝色标牌和粉色标牌),我希望粉色标牌可以显示在最上层,可偏偏蓝色标牌却将其遮挡住了。于是我对Overlay的层级开始起了兴趣,希望可以找到控制Overlay层级顺序的方法。
一、Overlay的原始层级顺序
决定Overlay原始层级的因素
首先得搞清楚一个问题,为什么粉色标牌的层级会比蓝色标牌低呢?
我观察了一番发现原因是因为我是先将蓝色标牌添加到地图中,后将粉色标牌添加到地图中。
如果调换一下代码顺序,先添加粉色标牌,那么就变成了粉色标牌的层级更高了
因此可以得出结论:
决定Overlay原始层级的因素是添加Overlay的先后顺序,先添加到地图中的Overlay层级高,后添加到地图中的Overlay层级低。
为什么是"先高后低"的顺序?
为什么会有这种"先高后低"的层级顺序呢?难道Elment元素就是按照这种顺序排列的吗?
于是我便进行了一次测试,依次插入蓝色、粉色两个元素,这两个元素都脱标且z-index
属性值相同,结果发生后插入的粉色元素覆盖了先插入的蓝色元素。
这说明在默认情况下Element元素的层级实际上遵循的是"先低后高"的原则,这个与Overlay的情况是不符的。
于是我查看了一下OpenLayers地图元素中的DOM数据,居然发现粉色标牌的元素在最前面,而蓝色标牌的元素在后面。这说明其实粉色标牌的元素才是先被插入到地图元素中的,这与我们编程的时候使用map.addOverlay
插入的顺序是相反的。
那我大概就明白了,OpenLayers的地图中用于存储Overlay的应该是一个栈结构,由于栈结构遵循着"后进先出"的原则,因此在渲染Overlay时,后插入的Overlay会先从存储栈中弹出进行渲染,因此才出现了层级"先高后低"的情况。
我用代码模拟了一下这种方式:
JavaScript
// 点击添加overlay
function handleClick(event) {
// 添加蓝色标牌
const el_blue = document.createElement("div");
el_blue.className = "test-overlay blue";
el_blue.innerHTML = "蓝色overlay";
addOverlay(el_blue);
// 添加粉色标牌
const el_pink = document.createElement("div");
el_pink.className = "test-overlay pink";
el_pink.innerHTML = "粉色overlay";
addOverlay(el_pink);
}
//地图的Overlay存储栈
const overlayStack = [];
function addOverlay(overlay) {
//将overlay添加到存储栈中
overlayStack.push(overlay);
// 渲染overlay到地图上
const map = document.getElementById("map");
for (let i = overlayStack.length - 1; i >= 0; i--) {
const el = overlayStack[i];
map.appendChild(el);
}
}
二、控制Overlay层级的方法
上面的理论探索纯属个人兴趣,其实我真正要解决的还是如何控制Overlay层级的问题。
当然最简单的方法就是控制添加Overlay的顺序,保证想要展示在上层的Overlay先添加。但是这种方法显得有些"蠢萌"了,在很多复杂的项目中我们是无法保障Overlay的插入顺序的,因此我认为需要找到更加有效的方法。
提到控制显示层级我们很容易就能想到CSS中的z-index
属性,这似乎是一个不错的思路,但是z-index
属性究竟应该加在哪儿呢?
z-index的错误使用
一开始我想的很简单,我认为只要给每个Overlay所绑定的element设置z-index
就可以了。
CSS
.test-overlay {
position: relative;
padding: 10px;
cursor: pointer;
/* 蓝色overlay样式 */
&.blue {
z-index: 1;
background-color: skyblue;
}
/* 粉色overlay样式 */
&.pink {
z-index: 2; /* 给粉色标牌设置更高的层级 */
background-color: pink;
}
}
但是设置之后发现好像没有任何的作用。为什会这样呢?
原因是因为,添加到DOM树中的Overlay的element还会被包装一层,所以我设置z-index
的div.blue
元素和div.pink
元素实际上是表兄弟元素(它们的父元素是兄弟元素)
表兄弟元素之间的层级关系就不由它们的z-index
属性决定,而是要看它们父元素的层级。也就是说z-index
应该设置在div.blue
元素和div.pink
的父元素也就是Overlay的包装元素上。
如何正确的使用z-index控制Overlay的层级
那么如何给Overlay的包装元素设置z-index
属性呢?我找到了两种方法。
方法一,通过Overlay的className
属性给包装元素添加一个类名。
首先在创建Overlay时设置className
属性,给蓝色标牌和粉色标牌设置不同的类名
JavaScript
// 添加蓝色overlay
positions.forEach((position, index) => {
addOverlay(window.map, {
id: `blue-${index}`,
groupId: "blue",
element: createBlueElement(),
position,
className: "ol-overlay-container ol-selectable blue-container",
});
});
// 添加粉色overlay
positions
.filter((_, index) => index % 2 === 0)
.forEach((position, index) => {
addOverlay(window.map, {
id: `pink-${index}`,
groupId: "pink",
element: createPinkElement(),
offset: [20, 20],
position,
className: "ol-overlay-container ol-selectable pink-container",
});
});
然后再通过类名给不同的Overlay的包装元素设置不同的层级
CSS
.blue-container {
z-index: 1;
}
.pink-container {
z-index: 2;
}
最终就可以实现粉色标牌不被蓝色标牌遮挡。
方法二,通过:has
选择器给包装元素添加样式
:has
选择器可以让你在选择元素上更佳灵活,也就是可以让你选中本元素之前的兄弟级别的元素或者是父元素亦或者是祖先元素。
它的使用方法是:当前元素之前的元素:has(当前元素)
因此我们就可以利用:has
选择器分别选中div.blue
元素和div.pink
元素的父元素,然后给它们设置层级。
CSS
.ol-overlay-container.ol-selectable:has(>.blue){
z-index: 1;
}
.ol-overlay-container.ol-selectable:has(>.pink){
z-index: 2;
}