零、写在前面
📌 本系列教程完整目录 :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无视觉影响。因此本示例把建筑改为平面填充,用三个相互重叠的半透明平面图层直观演示顺序变化。
二、功能实现
本示例包含三个贴地平面图层,初始顺序自下而上:建筑(米色)→ 标注(蓝色)→ 对照(绿色) 。标注、对照矩形由建筑的范围(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>
五、在线示例

操作说明:
- 场景包含 3 个平面图层:建筑(米色)、标注(蓝色)、对照(绿色),初始顺序为建筑(底)→标注(中)→对照(顶)。
- 点击「建筑上移 / 标注上移 / 对照上移」通过
map.reorder(layer, idx±1)调整绘制顺序,右侧顺序面板实时更新,三个相互重叠的半透明图层可直观看到覆盖关系变化。- 点击「标注层显示在列表」切换
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
七、系列导航
💡 小贴士 :在 3D 中因为有深度缓冲,图层顺序对「立体实体遮挡」影响有限,但对半透明叠加层、标注层、贴地层 的顺序依然关键:把参与排序的图层设为相互重叠的平面覆盖层,即可直观看到 reorder 的覆盖效果。配合
listMode还能控制图层在 UI 列表中的呈现,是组织复杂场景层次的基础手段。