OpenLayers:如何控制Overlay的层级?

我最近在使用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-indexdiv.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;
}

参考资料

  1. OpenLayers v10.5.0 API - Class: Overlay
  2. OpenLayers之 OverLay问题汇总_openlayers overlay zindex-CSDN博客
  3. :has() - CSS:层叠样式表 | MDN
  4. 伪类选择器中的:has_has选择器-CSDN博客
相关推荐
RoyLin6 分钟前
TypeScript设计模式:迭代器模式
javascript·后端·node.js
xw537 分钟前
uni-app中v-if使用”异常”
前端·uni-app
!win !1 小时前
uni-app中v-if使用”异常”
前端·uni-app
IT_陈寒1 小时前
Java 性能优化:5个被低估的JVM参数让你的应用吞吐量提升50%
前端·人工智能·后端
南囝coding2 小时前
《独立开发者精选工具》第 018 期
前端·后端
小桥风满袖2 小时前
极简三分钟ES6 - ES9中for await of
前端·javascript
半花2 小时前
i18n国际语言化配置
前端
编程贝多芬2 小时前
Promise 的场景和最佳实践
前端·javascript
Asort2 小时前
JavaScript 从零开始(四):基础语法详解——从变量声明到数据类型的完全指南
前端·javascript
木木jio2 小时前
前端大文件分片上传 —— 基于 React 的工程化实现
前端·javascript