ArcGIS JS 基础教程(28):图层渲染顺序管理

零、写在前面

📌 本系列教程完整目录 :ArcGIS JS 系列基础教程(100个项目常用热门功能)

💡 在线示例 :完整可运行的 HTML 示例,无需任何环境配置,可直接在浏览器中打开体验 🗂️ 专栏导航 :收藏 + 关注,专栏文章第一时间送达 ❤️ 一键三连:点赞 + 评论 + 收藏


一、功能介绍

在三维场景中,多个图层通过 map.layers(一个 Collection)管理。数组越靠后的图层绘制越上层(在 3D 中表现为叠放优先)。合理管理图层顺序,可以避免标注被遮挡、保证重要数据置顶显示。

核心操作:

  • map.add(layer, index) :在指定索引插入图层(index 越大越靠上,0 为底层)。
  • map.reorder(layer, index):将已有图层移动到指定索引。
  • layer.listMode :控制图层在 LayerList 组件中的显示行为(show / hide / hide-children)。

⚠️ 重要:在 SceneView(3D)中,map.layers 的顺序主要影响「平面覆盖类图层」的叠放 。对于立体符号(extrude 拉伸、3D 对象),遮挡由三维空间位置决定,reorder 无视觉影响。因此本示例把建筑改为平面填充,用三个相互重叠的半透明平面图层直观演示顺序变化。

参考:官方 Map.layers / Collection


二、功能实现

本示例包含三个贴地平面图层,初始顺序自下而上:建筑(米色)→ 标注(蓝色)→ 对照(绿色) 。标注、对照矩形由建筑的范围(fullExtent)经官方 projectOperator 投影生成,保证与建筑精确重叠,从而让 reorder 的覆盖关系清晰可见。

2.1 插入到指定位置

javascript 复制代码
// 在索引 1 处插入(即在底层之上、其他层之下)
map.add(highlightLayer, 1);

2.2 运行时重排

javascript 复制代码
// 把标注层移到数组最顶部(最上层)
const topIndex = map.layers.length - 1;
map.reorder(highlightLayer, topIndex);

// 上移一层
const idx = map.layers.indexOf(highlightLayer);
if (idx < map.layers.length - 1) map.reorder(highlightLayer, idx + 1);

2.3 按建筑范围动态生成重叠矩形

用 buildingsLayer.fullExtent(Web Mercator)构造矩形,再用官方 projectOperator 转为经纬度,确保标注/对照与建筑完全重叠:

javascript 复制代码
await projectOperator.load();
const fe = buildingsLayer.fullExtent;
const polyMerc = new Polygon({
    rings: [[
        [fe.xmin, fe.ymin], [fe.xmax, fe.ymin], [fe.xmax, fe.ymax],
        [fe.xmin, fe.ymax], [fe.xmin, fe.ymin]
    ]],
    spatialReference: SpatialReference.WebMercator
});
const polyWgs = projectOperator.execute(polyMerc, SpatialReference.WGS84);

2.4 控制图层面板显隐(listMode)

javascript 复制代码
layer.listMode = "hide";   // 在 LayerList 中隐藏该图层

三、功能应用

应用场景 配置
底图→地形→建筑→标注 按 map.layers 顺序组织层次
防止标注被遮挡 标注层 reorder 到最上层
图层面板分组管理 listMode 控制显隐
重点数据置顶 运行时 map.reorder(layer, top)

四、核心代码

📦 完整代码 已保存至 sample/lesson28_layer_order.html,可直接在浏览器打开。

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>第28课:图层渲染顺序管理</title>
    <link rel="stylesheet" href="https://js.arcgis.com/5.0/esri/themes/light/main.css">
    <script type="module" src="https://js.arcgis.com/5.0/"></script>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { font-family: "Microsoft YaHei", sans-serif; }
        #mapContainer { width: 100vw; height: 100vh; }
        .page-title {
            position: absolute; top: 20px; left: 50%; transform: translateX(-50%);
            background: rgba(255,255,255,0.95); padding: 10px 24px; border-radius: 6px;
            font-size: 18px; font-weight: bold; z-index: 100;
            box-shadow: 0 2px 8px rgba(0,0,0,0.15);
        }
        .control-panel {
            position: absolute; top: 80px; right: 20px;
            background: rgba(255,255,255,0.95); padding: 16px; border-radius: 8px;
            box-shadow: 0 2px 12px rgba(0,0,0,0.15);
            z-index: 100; min-width: 320px;
        }
        .control-panel h3 { margin: 0 0 8px 0; font-size: 14px; color: #333; }
        .section { margin-bottom: 12px; padding-bottom: 10px; border-bottom: 1px solid #eee; }
        .section:last-child { border-bottom: none; margin-bottom: 0; }
        .btn-row { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 6px; }
        .btn-row button {
            flex: 1; min-width: 70px; padding: 6px 0;
            border: 1px solid #d9d9d9; border-radius: 4px;
            background: white; cursor: pointer; font-size: 12px;
        }
        .btn-row button:hover { border-color: #1890ff; color: #1890ff; }
        .btn-row button.on { background: #1890ff; color: white; border-color: #1890ff; }
        .info-card {
            margin-top: 10px; padding: 10px 12px;
            background: #f0f5ff; border-radius: 6px;
            border-left: 3px solid #1890ff; font-size: 12px; line-height: 1.6;
        }
        .info-card .val { font-weight: bold; color: #1890ff; }
        .status-text {
            position: absolute; bottom: 20px; left: 50%; transform: translateX(-50%);
            background: rgba(0,0,0,0.7); color: white; padding: 8px 20px;
            border-radius: 20px; font-size: 13px; z-index: 100; pointer-events: none;
            white-space: nowrap;
        }
    </style>
</head>
<body>
<h1 class="page-title">第28课:图层渲染顺序管理</h1>

<div class="control-panel">
    <div class="section">
        <h3>🔼 图层顺序(底部 → 顶部)</h3>
        <div class="btn-row">
            <button id="btnBldUp">🏢 建筑上移</button>
            <button id="btnBldDown">🏢 建筑下移</button>
        </div>
        <div class="btn-row">
            <button id="btnMkUp">🔵 标注上移</button>
            <button id="btnMkDown">🔵 标注下移</button>
        </div>
        <div class="btn-row">
            <button id="btnCtrUp">🟢 对照上移</button>
            <button id="btnCtrDown">🟢 对照下移</button>
        </div>
    </div>
    <div class="section">
        <h3>👁️ 列表显隐(listMode)</h3>
        <div class="btn-row">
            <button id="btnListMode" class="on">标注层显示在列表</button>
        </div>
    </div>
    <div class="info-card">
        <div>当前顺序(上→下):</div>
        <div class="val" id="orderInfo">加载中...</div>
    </div>
</div>

<div class="status-text" id="statusText">图层渲染顺序管理 | map.reorder / listMode</div>

<div id="mapContainer"></div>

<script type="module">
    const Map = await $arcgis.import("@arcgis/core/Map.js");
    const SceneView = await $arcgis.import("@arcgis/core/views/SceneView.js");
    const FeatureLayer = await $arcgis.import("@arcgis/core/layers/FeatureLayer.js");
    const GraphicsLayer = await $arcgis.import("@arcgis/core/layers/GraphicsLayer.js");
    const Polygon = await $arcgis.import("@arcgis/core/geometry/Polygon.js");
    const SpatialReference = await $arcgis.import("@arcgis/core/geometry/SpatialReference.js");
    const projectOperator = await $arcgis.import("@arcgis/core/geometry/operators/projectOperator.js");
    const getTianditu = await $arcgis.import("https://openlayers.vip/examples/resources/tianditu.js");

    const vecLayers = getTianditu.default({ type: "vec_w" });
    const map = new Map({ basemap: { baseLayers: [vecLayers.base, vecLayers.anno] } });

    const view = new SceneView({
        container: "mapContainer", map: map,
        camera: {
            position: { longitude: 121.435, latitude: 31.245, z: 4000 },
            heading: 30, tilt: 60
        }
    });

    // 建筑图层
    const buildingsLayer = new FeatureLayer({
        url: "https://gs3d.geosceneonline.cn/server/rest/services/Hosted/ShangHaiBuilding/FeatureServer/0",
        title: "上海建筑",
        elevationInfo: { mode: "on-the-ground" },
        outFields: ["id", "floor"],
        renderer: {
            type: "simple",
            symbol: {
                type: "polygon-3d",
                symbolLayers: [{ type: "fill", material: { color: [212, 184, 124, 0.7] },
                    outline: { color: "#999999", size: 1 } }]
            }
        }
    });

    // 高亮标注图层(半透明多边形,贴地,覆盖建筑区域)
    const highlightLayer = new GraphicsLayer({ title: "标注层" });
    // 对照覆盖层(半透明绿色,与标注层完全重叠,用于直观演示 reorder 顺序)
    const contrastLayer = new GraphicsLayer({ title: "对照层" });

    // 三个平面层:建筑(底) → 标注(中) → 对照(顶),绿色盖蓝色
    map.addMany([buildingsLayer, highlightLayer, contrastLayer]);

    // 按建筑范围动态生成标注/对照矩形,确保与建筑精确重叠
    async function addOverlayRects() {
        await projectOperator.load();
        const fe = buildingsLayer.fullExtent;                       // Web Mercator (102100)
        const polyMerc = new Polygon({
            rings: [[
                [fe.xmin, fe.ymin], [fe.xmax, fe.ymin], [fe.xmax, fe.ymax],
                [fe.xmin, fe.ymax], [fe.xmin, fe.ymin]
            ]],
            spatialReference: SpatialReference.WebMercator
        });
        const polyWgs = projectOperator.execute(polyMerc, SpatialReference.WGS84); // 转成经纬度

        const marker = {
            geometry: polyWgs,
            symbol: { type: "polygon-3d", symbolLayers: [{ type: "fill",
                material: { color: [24, 144, 255, 0.35] },
                outline: { color: [24, 144, 255, 1], size: 2 } }] }
        };
        const contrast = {
            geometry: polyWgs,
            symbol: { type: "polygon-3d", symbolLayers: [{ type: "fill",
                material: { color: [0, 200, 83, 0.45] },
                outline: { color: [0, 200, 83, 1], size: 2 } }] }
        };
        highlightLayer.add(marker);
        contrastLayer.add(contrast);
    }

    function refreshOrder() {
        const topToBottom = map.layers.toArray().reverse();
        document.getElementById("orderInfo").textContent = topToBottom.map(l => l.title).join("  →  ");
    }
    function setStatus(msg) { document.getElementById("statusText").textContent = msg; }

    view.when(async () => {
        await addOverlayRects();
        view.goTo(buildingsLayer.fullExtent, { duration: 2000, easing: "cubic-out" });
        refreshOrder();
    });

    function moveUp(layer, name) {
        const idx = map.layers.indexOf(layer);
        if (idx < map.layers.length - 1) {
            map.reorder(layer, idx + 1);
            refreshOrder();
            setStatus(name + " 上移一层");
        }
    }
    function moveDown(layer, name) {
        const idx = map.layers.indexOf(layer);
        if (idx > 0) {
            map.reorder(layer, idx - 1);
            refreshOrder();
            setStatus(name + " 下移一层");
        }
    }

    document.getElementById("btnBldUp").addEventListener("click", () => moveUp(buildingsLayer, "建筑"));
    document.getElementById("btnBldDown").addEventListener("click", () => moveDown(buildingsLayer, "建筑"));
    document.getElementById("btnMkUp").addEventListener("click", () => moveUp(highlightLayer, "标注"));
    document.getElementById("btnMkDown").addEventListener("click", () => moveDown(highlightLayer, "标注"));
    document.getElementById("btnCtrUp").addEventListener("click", () => moveUp(contrastLayer, "对照"));
    document.getElementById("btnCtrDown").addEventListener("click", () => moveDown(contrastLayer, "对照"));

    let listShown = true;
    document.getElementById("btnListMode").addEventListener("click", function () {
        listShown = !listShown;
        highlightLayer.listMode = listShown ? "show" : "hide";
        this.classList.toggle("on", listShown);
        this.textContent = listShown ? "标注层显示在列表" : "标注层隐藏于列表";
        setStatus("标注层 listMode = " + highlightLayer.listMode);
    });
</script>
</body>
</html>

五、在线示例

🔗 在线体验 :southjor.github.io/arcgis-exam...

操作说明:

  1. 场景包含 3 个平面图层:建筑(米色)、标注(蓝色)、对照(绿色),初始顺序为建筑(底)→标注(中)→对照(顶)。
  2. 点击「建筑上移 / 标注上移 / 对照上移」通过 map.reorder(layer, idx±1) 调整绘制顺序,右侧顺序面板实时更新,三个相互重叠的半透明图层可直观看到覆盖关系变化。
  3. 点击「标注层显示在列表」切换 listMode 为 show / hide(控制图层面板显隐)。

六、关键 API 说明

API 说明
map.layers 图层集合(Collection),顺序决定绘制上下层
map.add(layer, index) 在指定索引插入图层
map.addMany([...]) 批量添加(按顺序)
map.reorder(layer, index) 将图层移动到指定索引
map.layers.indexOf(layer) 获取图层当前索引
layer.listMode show / hide / hide-children
layer.fullExtent 图层数据范围(用于生成覆盖矩形)
projectOperator 官方投影算子,WGS84 与 WebMercator 互转

参考链接: Map API | Collection


七、系列导航

⬅️ 上一篇 :ArcGIS JS 基础教程(27):DisplayFilter 显示过滤器

➡️ 下一篇 :ArcGIS JS 基础教程(29):CSVLayer 表格点位图层


💡 小贴士 :在 3D 中因为有深度缓冲,图层顺序对「立体实体遮挡」影响有限,但对半透明叠加层、标注层、贴地层 的顺序依然关键:把参与排序的图层设为相互重叠的平面覆盖层,即可直观看到 reorder 的覆盖效果。配合 listMode 还能控制图层在 UI 列表中的呈现,是组织复杂场景层次的基础手段。

相关推荐
huakoh1 小时前
MCP 工具报错走哪条通道:三条探针的最小复现检查
前端
不爱说话郭德纲1 小时前
从“点点点”到一键出包:我把 uni-app x Android 离线打包做成了脚本
android·前端·uni-app
扶风ff1 小时前
练题簿在线免费刷题:创建个人任务,把刷题、听题和模考目标放到每天的学习中
开发语言·javascript·学习·小程序
Lstone73641 小时前
从 Jetpack Compose 到 CMP:跨平台开发学习笔记
前端
deli0071 小时前
多加一粒沙,整堆为什么就塌了?sandpile 模型 20 万粒实测
前端
asong1 小时前
没有源代码,AI 也能拆解软件:这个开源项目已有 3.7 万 Star
javascript·后端·github
涛涛ing1 小时前
乱序HTML流正式进入浏览器:前端流式渲染的“框架特权”被终结了
前端
__sjfzllv___1 小时前
在职前端Leader学习/转行 AI Agent -DAY73
前端
用户1733598075371 小时前
纯前端 PDF 压平避坑指南:压平后表单字段变了?
前端·javascript·vue.js