ArcGIS JS 基础教程(47):内置组件集成
零、写在前面
📌 本系列教程完整目录 :ArcGIS JS 系列基础教程(100个项目常用热门功能)
💡 在线示例 :完整可运行的 HTML 示例,无需任何环境配置,可直接在浏览器中打开体验
🗂️ 专栏导航 :收藏 + 关注,专栏文章第一时间送达
❤️ 一键三连:点赞 + 评论 + 收藏
一、功能介绍
ArcGIS JS 5.0 提供了一整套 Web Components(自定义元素) ,把地图交互封装成 <arcgis-xxx> 标签。相比「先 new View,再 new Widget,再 view.ui.add」的命令式写法,组件化模式 更简洁:用 <arcgis-scene> 作为容器,把各种组件作为子元素、通过 slot 放入对应角位,它们会自动绑定到同一个视图,无需手动 view.ui.add。
本课把最常用的内置组件一次性集成到一个 3D 场景里:
<arcgis-zoom>/<arcgis-navigation-toggle>/<arcgis-compass>/<arcgis-home>(左上)<arcgis-layer-list>/<arcgis-fullscreen>(右上)<arcgis-legend>/<arcgis-coordinate-conversion>(右下)<arcgis-scale-bar>/<arcgis-daylight>(左下)
参考:官方 Components 概览 | arcgis-scene 组件
💡 三维要点 :组件模式与
SceneView完全兼容;<arcgis-daylight>提供日照时间轴,<arcgis-coordinate-conversion>提供坐标实时换算,都是 3D 场景的常用增强件。
二、功能实现
2.1 用 arcgis-scene 作容器 + slot 放组件
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>第47课:内置组件集成</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>
html, body { margin: 0; height: 100%; font-family: "Microsoft YaHei", sans-serif; }
#viewDiv { width: 100%; 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: 200;
box-shadow: 0 2px 8px rgba(0,0,0,0.15); pointer-events: none;
}
.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: 200; pointer-events: none;
white-space: nowrap;
}
</style>
</head>
<body>
<h1 class="page-title">第47课:内置组件集成</h1>
<!-- 使用 arcgis-scene 组件作为容器,内置组件作为子元素通过 slot 自动绑定视图 -->
<arcgis-scene id="viewDiv"
camera-position="121.498, 31.205, 4000"
camera-tilt="60"
camera-heading="20">
<!-- 左上角:缩放 / 导航切换 / 指北针 / 主页 -->
<arcgis-zoom slot="top-left"></arcgis-zoom>
<arcgis-navigation-toggle slot="top-left"></arcgis-navigation-toggle>
<arcgis-compass slot="top-left"></arcgis-compass>
<arcgis-home slot="top-left"></arcgis-home>
<!-- 右上角:图层列表 / 全屏 -->
<arcgis-layer-list slot="top-right"></arcgis-layer-list>
<arcgis-fullscreen slot="top-right"></arcgis-fullscreen>
<!-- 右下角:图例 / 坐标转换 -->
<arcgis-legend slot="bottom-right"></arcgis-legend>
<arcgis-coordinate-conversion slot="bottom-right"></arcgis-coordinate-conversion>
<!-- 左下角:比例尺 / 日照 -->
<arcgis-scale-bar slot="bottom-left"></arcgis-scale-bar>
<arcgis-daylight slot="bottom-left"></arcgis-daylight>
</arcgis-scene>
<div class="status-text" id="statusText">内置组件集成 | 多组件协同的 3D 场景</div>
<script type="module">
const [Map, SceneView, FeatureLayer, GraphicsLayer, Graphic] = await $arcgis.import([
"@arcgis/core/Map.js",
"@arcgis/core/views/SceneView.js",
"@arcgis/core/layers/FeatureLayer.js",
"@arcgis/core/layers/GraphicsLayer.js",
"@arcgis/core/Graphic.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] } });
// 监测点图层(唯一值渲染,供 Legend 展示)
const monitorLayer = (() => {
const cats = ["A类", "B类", "C类"];
const colors = {
"A类": [227, 74, 51],
"B类": [56, 135, 220],
"C类": [60, 180, 110]
};
const graphics = [];
for (let i = 0; i < 90; i++) {
graphics.push(new Graphic({
geometry: {
type: "point",
x: 121.46 + Math.random() * 0.08,
y: 31.18 + Math.random() * 0.06,
z: 80 + Math.random() * 400,
spatialReference: { wkid: 4326 }
},
attributes: {
OBJECTID: i + 1,
NAME: "监测点" + (i + 1),
CATEGORY: cats[i % 3]
}
}));
}
return new FeatureLayer({
source: graphics,
objectIdField: "OBJECTID",
fields: [
{ name: "OBJECTID", type: "oid" },
{ name: "NAME", type: "string" },
{ name: "CATEGORY", type: "string" }
],
geometryType: "point",
spatialReference: { wkid: 4326 },
title: "实时监测点",
elevationInfo: { mode: "relative-to-ground" },
renderer: {
type: "unique-value",
field: "CATEGORY",
uniqueValueInfos: cats.map(cat => ({
value: cat,
symbol: {
type: "point-3d",
symbolLayers: [{
type: "object",
resource: { primitive: "sphere" },
width: 220, height: 220, depth: 220,
material: { color: colors[cat] }
}]
}
}))
}
});
})();
const buildingLayer = (() => {
const layer = new GraphicsLayer({ title: "重点建筑体" });
const bases = [
{ x: 121.490, y: 31.230, h: 900, c: [120, 170, 230] },
{ x: 121.495, y: 31.225, h: 1400, c: [230, 160, 70] },
{ x: 121.500, y: 31.220, h: 700, c: [120, 200, 160] },
{ x: 121.485, y: 31.218, h: 1100, c: [200, 120, 200] }
];
bases.forEach((b, idx) => {
layer.add(new Graphic({
geometry: { type: "point", x: b.x, y: b.y, z: 0, spatialReference: { wkid: 4326 } },
symbol: {
type: "point-3d",
symbolLayers: [{
type: "object",
resource: { primitive: "cube" },
width: 350, depth: 350, height: b.h,
material: { color: b.c }
}]
},
attributes: { NAME: "建筑" + (idx + 1) }
}));
});
return layer;
})();
map.addMany([buildingLayer, monitorLayer]);
// ===== arcgis-scene 组件:绑定 map 并等待就绪 =====
const viewElement = document.querySelector("arcgis-scene");
const view = viewElement.view;
view.map = map;
await viewElement.viewOnReady();
// 默认定位:自动框选全部数据范围,确保初始视角合适
await monitorLayer.when();
// goTo 的目标只能是几何/Graphic/Viewpoint/Camera 等,图层需用其 fullExtent
view.goTo([monitorLayer.fullExtent, buildingLayer.fullExtent], { duration: 1500 }).catch(() => {});
setStatus("内置组件集成就绪 | 尝试左上/右上/左下/右下的各类组件");
function setStatus(msg) { document.getElementById("statusText").textContent = msg; }
</script>
</body>
</html>
2.2 取视图、绑地图、等待就绪
javascript
const viewElement = document.querySelector("arcgis-scene");
const view = viewElement.view; // 组件自动创建并暴露 view
view.map = map; // 绑定地图
await viewElement.viewOnReady(); // 等待组件与视图就绪
三、功能应用
| 组件 | 作用 |
|---|---|
| arcgis-zoom | 缩放按钮 |
| arcgis-navigation-toggle | 旋转/平移模式切换 |
| arcgis-compass | 指北针(点击回正) |
| arcgis-home | 回到初始视角 |
| arcgis-layer-list | 图层列表管理 |
| arcgis-fullscreen | 全屏 |
| arcgis-legend | 图例 |
| arcgis-coordinate-conversion | 坐标实时换算 |
| arcgis-scale-bar | 比例尺 |
| arcgis-daylight | 日照时间轴 |
四、核心代码
📦 完整代码 已保存至
sample/lesson47_builtin_components.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>第47课:内置组件集成</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>
html, body { margin: 0; height: 100%; font-family: "Microsoft YaHei", sans-serif; }
#viewDiv { width: 100%; 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: 200;
box-shadow: 0 2px 8px rgba(0,0,0,0.15); pointer-events: none;
}
.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: 200; pointer-events: none;
white-space: nowrap;
}
</style>
</head>
<body>
<h1 class="page-title">第47课:内置组件集成</h1>
<!-- 使用 arcgis-scene 组件作为容器,内置组件作为子元素通过 slot 自动绑定视图 -->
<arcgis-scene id="viewDiv"
camera-position="121.498, 31.205, 4000"
camera-tilt="60"
camera-heading="20">
<!-- 左上角:缩放 / 导航切换 / 指北针 / 主页 -->
<arcgis-zoom slot="top-left"></arcgis-zoom>
<arcgis-navigation-toggle slot="top-left"></arcgis-navigation-toggle>
<arcgis-compass slot="top-left"></arcgis-compass>
<arcgis-home slot="top-left"></arcgis-home>
<!-- 右上角:图层列表 / 全屏 -->
<arcgis-layer-list slot="top-right"></arcgis-layer-list>
<arcgis-fullscreen slot="top-right"></arcgis-fullscreen>
<!-- 右下角:图例 / 坐标转换 -->
<arcgis-legend slot="bottom-right"></arcgis-legend>
<arcgis-coordinate-conversion slot="bottom-right"></arcgis-coordinate-conversion>
<!-- 左下角:比例尺 / 日照 -->
<arcgis-scale-bar slot="bottom-left"></arcgis-scale-bar>
<arcgis-daylight slot="bottom-left"></arcgis-daylight>
</arcgis-scene>
<div class="status-text" id="statusText">内置组件集成 | 多组件协同的 3D 场景</div>
<script type="module">
const [Map, SceneView, FeatureLayer, GraphicsLayer, Graphic] = await $arcgis.import([
"@arcgis/core/Map.js",
"@arcgis/core/views/SceneView.js",
"@arcgis/core/layers/FeatureLayer.js",
"@arcgis/core/layers/GraphicsLayer.js",
"@arcgis/core/Graphic.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] } });
// 监测点图层(唯一值渲染,供 Legend 展示)
const monitorLayer = (() => {
const cats = ["A类", "B类", "C类"];
const colors = {
"A类": [227, 74, 51],
"B类": [56, 135, 220],
"C类": [60, 180, 110]
};
const graphics = [];
for (let i = 0; i < 90; i++) {
graphics.push(new Graphic({
geometry: {
type: "point",
x: 121.46 + Math.random() * 0.08,
y: 31.18 + Math.random() * 0.06,
z: 80 + Math.random() * 400,
spatialReference: { wkid: 4326 }
},
attributes: {
OBJECTID: i + 1,
NAME: "监测点" + (i + 1),
CATEGORY: cats[i % 3]
}
}));
}
return new FeatureLayer({
source: graphics,
objectIdField: "OBJECTID",
fields: [
{ name: "OBJECTID", type: "oid" },
{ name: "NAME", type: "string" },
{ name: "CATEGORY", type: "string" }
],
geometryType: "point",
spatialReference: { wkid: 4326 },
title: "实时监测点",
elevationInfo: { mode: "relative-to-ground" },
renderer: {
type: "unique-value",
field: "CATEGORY",
uniqueValueInfos: cats.map(cat => ({
value: cat,
symbol: {
type: "point-3d",
symbolLayers: [{
type: "object",
resource: { primitive: "sphere" },
width: 220, height: 220, depth: 220,
material: { color: colors[cat] }
}]
}
}))
}
});
})();
const buildingLayer = (() => {
const layer = new GraphicsLayer({ title: "重点建筑体" });
const bases = [
{ x: 121.490, y: 31.230, h: 900, c: [120, 170, 230] },
{ x: 121.495, y: 31.225, h: 1400, c: [230, 160, 70] },
{ x: 121.500, y: 31.220, h: 700, c: [120, 200, 160] },
{ x: 121.485, y: 31.218, h: 1100, c: [200, 120, 200] }
];
bases.forEach((b, idx) => {
layer.add(new Graphic({
geometry: { type: "point", x: b.x, y: b.y, z: 0, spatialReference: { wkid: 4326 } },
symbol: {
type: "point-3d",
symbolLayers: [{
type: "object",
resource: { primitive: "cube" },
width: 350, depth: 350, height: b.h,
material: { color: b.c }
}]
},
attributes: { NAME: "建筑" + (idx + 1) }
}));
});
return layer;
})();
map.addMany([buildingLayer, monitorLayer]);
// ===== arcgis-scene 组件:绑定 map 并等待就绪 =====
const viewElement = document.querySelector("arcgis-scene");
const view = viewElement.view;
view.map = map;
await viewElement.viewOnReady();
// 默认定位:自动框选全部数据范围,确保初始视角合适
await monitorLayer.when();
// goTo 的目标不接受图层,需使用图层的 fullExtent
view.goTo([monitorLayer.fullExtent, buildingLayer.fullExtent], { duration: 1500 }).catch(() => {});
setStatus("内置组件集成就绪 | 尝试左上/右上/左下/右下的各类组件");
function setStatus(msg) { document.getElementById("statusText").textContent = msg; }
</script>
</body>
</html>
五、在线示例
🔗 在线体验 :https://southjor.github.io/arcgis-examples/lessons/lesson47.html

操作说明:
- 用
<arcgis-scene>作为 3D 容器,各类组件通过slot放入四角,自动绑定视图。- 左上:缩放 / 导航切换 / 指北针 / 主页;右上:图层列表 / 全屏。
- 右下:图例 / 坐标转换;左下:比例尺 / 日照时间轴。
- 拖动
<arcgis-daylight>时间轴可改变场景光照与阴影。
六、关键 API 说明
| API | 说明 |
|---|---|
<arcgis-scene> |
3D 场景容器组件(自动创建 SceneView) |
slot="top-left" 等 |
把组件放入对应角位,自动绑定视图 |
viewElement.view |
取组件内部的 SceneView |
viewElement.viewOnReady() |
等待组件与视图就绪 |
<arcgis-daylight> |
日照时间轴(3D 光照) |
<arcgis-coordinate-conversion> |
坐标实时换算 |
参考链接: ArcGIS Maps SDK Components
七、系列导航
💡 小贴士 :组件化(
<arcgis-xxx>)是 ArcGIS JS 5.0 推荐的集成方式,用slot代替view.ui.add,用声明式标签代替命令式new Widget,代码更清爽、与框架(React/Vue)也更契合。本系列后续课程都优先采用组件写法。