ArcGIS JS 基础教程(12):视图截图 takeScreenshot
零、写在前面
📌 本系列教程完整目录 :ArcGIS JS 系列基础教程(100个项目常用热门功能)
💡 在线示例 :完整可运行的 HTML 示例,无需任何环境配置,可直接在浏览器中打开体验
🗂️ 专栏导航 :收藏 + 关注,专栏文章第一时间送达
❤️ 一键三连:点赞(给教程充电)+ 评论(提问必回)+ 收藏(下次再看)
一、功能介绍
在 WebGIS 开发中,经常需要将当前地图视图保存为图片------用于生成报告、分享成果、制作演示稿等。ArcGIS Maps SDK for JavaScript 提供了 SceneView.takeScreenshot() 方法,可以一键捕获当前三维场景的高清截图。
takeScreenshot() 的核心特性:
- 异步截图:返回 Promise,在场景渲染完成后自动捕获当前帧
- 格式可选:支持 PNG(无损)和 JPEG / JPG(有损压缩)
- 自定义尺寸:可以指定输出图片的宽高(像素),实现 4K/8K 高清截图
- 质量控制:JPEG 格式支持 0~1 质量参数,平衡清晰度和文件大小
- 直接下载 :返回对象的
dataUrl属性可直接用于<a>下载或<img>预览
二、功能实现
核心 API: SceneView.takeScreenshot(options?),返回 Promise<{ dataUrl: string }>(5.0 中返回带 dataUrl 属性的对象)。
2.1 基础截图
javascript
// 最简单的用法:捕获当前视图,默认 PNG 格式
view.takeScreenshot().then(shot => {
// shot.dataUrl 是 base64 编码的图片数据
console.log("截图完成!", shot.dataUrl.substring(0, 100) + "...");
});
2.2 指定输出格式
javascript
// PNG 截图(默认,无损)
const pngShot = await view.takeScreenshot({ format: "png" });
// JPEG 截图(有损压缩,文件更小)
const jpgShot = await view.takeScreenshot({
format: "jpg",
quality: 0.8 // 0~1,默认 0.92。值越小文件越小但质量越低
});
console.log(jpgShot.dataUrl); // 直接取 dataUrl 属性
2.3 自定义图片尺寸
默认情况下截图尺寸等于当前视口尺寸。指定 width / height 可以生成更高分辨率的图片:
javascript
// 生成 4K(3840x2160)高清截图
const shot = await view.takeScreenshot({
width: 3840,
height: 2160,
format: "png"
});
// shot.dataUrl 中包含 4K 分辨率的 base64 图片数据
⚠️ 注意: 超大尺寸截图会消耗更多内存和时间,并且可能受限于浏览器和 GPU 的性能上限。
2.4 下载截图为图片文件
直接使用 dataUrl 触发浏览器下载:
javascript
async function downloadScreenshot() {
const shot = await view.takeScreenshot({
format: "png",
width: 1920,
height: 1080
});
// 通过 dataUrl 创建下载链接
const link = document.createElement("a");
link.href = shot.dataUrl;
link.download = "screenshot_" + Date.now() + ".png";
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
2.5 在新窗口预览截图
javascript
async function previewScreenshot() {
const shot = await view.takeScreenshot({ format: "png" });
// 在新窗口打开截图
const win = window.open("", "_blank");
win.document.write(`<img src="${shot.dataUrl}" style="max-width:100%;" />`);
win.document.title = "场景截图预览";
}
2.6 格式与质量对比
| 格式 | 有无损 | 质量参数 | 典型文件大小(1920×1080) | 适用场景 |
|---|---|---|---|---|
"png" |
✅ 无损 | 不支持 | ~3-8 MB | 需要清晰细节的报告、印刷输出 |
"jpg" |
❌ 有损 | 0.5~1.0 |
~150-500 KB (q=0.8) | 网页展示、邮件附件、快速分享 |
"jpeg" |
❌ 有损 | 0.5~1.0 |
同 jpg | 同 jpg(别名) |
2.7 完整参数说明
javascript
view.takeScreenshot({
format: "png", // "png" | "jpg" | "jpeg",默认 "png"
quality: 0.92, // JPEG 质量 0~1,PNG 忽略此参数,默认 0.92
width: 1920, // 输出宽度(像素),默认 = 当前视口宽度
height: 1080 // 输出高度(像素),默认 = 当前视口高度
});
三、功能应用
| 应用场景 | 格式 | 尺寸 | 说明 |
|---|---|---|---|
| 生成项目汇报截图 | PNG | 1920×1080 | 无损细节,适合插入 PPT/文档 |
| 网页快速分享 | JPEG, q=0.7 | 视口尺寸 | 文件小,适合聊天/邮件 |
| 4K 高清出品图 | PNG | 3840×2160 | 宣传物料、大屏展示 |
| 自动定期截图(监控) | JPEG, q=0.5 | 1280×720 | 节省存储空间,时序对比 |
| 截图预览弹窗 | PNG | 视口尺寸 | 截图后在新窗口查看 |
| 带水印下载 | PNG | 1920×1080 | 截图后通过 Canvas 叠加水印再下载 |
四、核心代码
📦 完整代码 已保存至
sample/lesson12_take_screenshot.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>第12课:视图截图 takeScreenshot</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: 300px;
}
.control-panel h3 { margin: 0 0 10px 0; font-size: 14px; color: #333; }
.section { margin-bottom: 14px; 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; }
.btn-row button {
flex: 1; min-width: 80px; padding: 8px 0;
border: 1px solid #d9d9d9; border-radius: 4px;
background: white; cursor: pointer; font-size: 13px;
transition: all 0.2s;
}
.btn-row button:hover { border-color: #1890ff; color: #1890ff; }
.btn-row button.primary { background: #1890ff; color: white; border-color: #1890ff; }
.btn-row button.primary:hover { background: #40a9ff; }
.form-group { margin-bottom: 10px; }
.form-group label { display: block; font-size: 12px; color: #666; margin-bottom: 4px; }
.form-group select, .form-group input { width: 100%; padding: 5px 8px; border: 1px solid #d9d9d9; border-radius: 4px; font-size: 12px; }
.preview-modal {
display: none;
position: fixed; top: 0; left: 0; width: 100vw; height: 100vh;
background: rgba(0,0,0,0.8); z-index: 999;
justify-content: center; align-items: center;
}
.preview-modal.show { display: flex; }
.preview-modal .modal-content {
max-width: 90vw; max-height: 90vh;
background: white; border-radius: 8px; padding: 12px;
box-shadow: 0 8px 32px rgba(0,0,0,0.3);
position: relative;
}
.preview-modal img { max-width: 100%; max-height: 80vh; display: block; }
.preview-modal .close-btn {
position: absolute; top: -12px; right: -12px;
width: 32px; height: 32px; border-radius: 50%;
background: #ff4d4f; color: white; border: none;
font-size: 18px; cursor: pointer; line-height: 32px; text-align: center;
}
.preview-modal .modal-actions { margin-top: 10px; display: flex; gap: 8px; justify-content: center; }
.preview-modal .modal-actions button {
padding: 6px 16px; border: 1px solid #d9d9d9; border-radius: 4px;
background: white; cursor: pointer; font-size: 13px;
}
.preview-modal .modal-actions button:hover { border-color: #1890ff; 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">第12课:视图截图 takeScreenshot</h1>
<div class="control-panel">
<div class="section">
<h3>📸 快速操作</h3>
<div class="btn-row">
<button id="btnScreenshot" class="primary">📸 截图</button>
<button id="btnDownload">💾 下载 PNG</button>
<button id="btnDownloadJpg">📦 下载 JPG</button>
</div>
<div class="btn-row" style="margin-top:6px;">
<button id="btnPreview">🔍 预览截图</button>
<button id="btn4K">🖥️ 4K 高清截图</button>
</div>
</div>
<div class="section">
<h3>⚙️ 截图参数</h3>
<div class="form-group">
<label>格式(format):</label>
<select id="selFormat">
<option value="png">PNG(无损)</option>
<option value="jpg">JPEG(有损压缩)</option>
</select>
</div>
<div class="form-group">
<label>质量(quality,仅 JPEG):</label>
<select id="selQuality">
<option value="0.5">低 0.5(文件最小)</option>
<option value="0.7">中 0.7</option>
<option value="0.85" selected>高 0.85(推荐)</option>
<option value="1.0">最高 1.0</option>
</select>
</div>
<div class="form-group">
<label>自定义宽度(像素,留空=视口宽):</label>
<input type="number" id="inputWidth" placeholder="如 1920" min="100" max="7680">
</div>
<div class="form-group">
<label>自定义高度(像素,留空=视口高):</label>
<input type="number" id="inputHeight" placeholder="如 1080" min="100" max="4320">
</div>
</div>
</div>
<div class="status-text" id="statusText">📸 takeScreenshot | 点击按钮截图</div>
<div class="preview-modal" id="previewModal">
<div class="modal-content">
<button class="close-btn" id="btnClosePreview">✕</button>
<img id="previewImage" src="" alt="截图预览" />
<div class="modal-actions">
<button id="btnModalDownload">💾 下载此图</button>
</div>
</div>
</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 GraphicsLayer = await $arcgis.import("@arcgis/core/layers/GraphicsLayer.js");
const Graphic = await $arcgis.import("@arcgis/core/Graphic.js");
const Mesh = await $arcgis.import("@arcgis/core/geometry/Mesh.js");
const Point = await $arcgis.import("@arcgis/core/geometry/Point.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] },
ground: {
surface: {
elevationLayers: [{
url: "https://www.geosceneonline.cn/image/rest/services/OpenData/ChinaTerrain3D/ImageServer/"
}]
}
}
});
const view = new SceneView({
container: "mapContainer",
map: map,
camera: {
position: { longitude: 116.397, latitude: 39.917, z: 3000 },
heading: 30,
tilt: 50
}
});
window.view = view;
let lastCanvas = null; // 保存最近一次截图结果
// ===== 添加 3D 场景元素(让截图更丰富) =====
function addSceneObjects() {
const layer = new GraphicsLayer({
title: "场景元素",
castShadows: true,
receiveShadows: true
});
// 中心金色大楼
layer.add(new Graphic({
geometry: Mesh.createBox(
new Point({ longitude: 116.397, latitude: 39.917, z: 0 }),
{ size: { width: 300, height: 500, depth: 300 } }
),
symbol: {
type: "mesh-3d",
symbolLayers: [{ type: "fill", material: { color: [220, 180, 80, 0.9] } }]
}
}));
// 周围小建筑群
[[0.004, 0.003], [-0.004, 0.002], [0.003, -0.003], [-0.003, -0.002]].forEach(([dx, dy]) => {
layer.add(new Graphic({
geometry: Mesh.createBox(
new Point({ longitude: 116.397 + dx, latitude: 39.917 + dy, z: 0 }),
{ size: { width: 120, height: 200 + Math.random() * 150, depth: 120 } }
),
symbol: {
type: "mesh-3d",
symbolLayers: [{ type: "fill", material: { color: [150 + Math.random() * 80, 160, 200, 0.9] } }]
}
}));
});
map.add(layer);
}
// ===== 读取截图参数 =====
function getScreenshotOptions() {
const format = document.getElementById("selFormat").value;
const quality = parseFloat(document.getElementById("selQuality").value);
const width = document.getElementById("inputWidth").value
? parseInt(document.getElementById("inputWidth").value) : undefined;
const height = document.getElementById("inputHeight").value
? parseInt(document.getElementById("inputHeight").value) : undefined;
const opts = { format };
if (format === "jpg" || format === "jpeg") opts.quality = quality;
if (width) opts.width = width;
if (height) opts.height = height;
return opts;
}
function getMimeType(format) {
return format === "jpg" || format === "jpeg" ? "image/jpeg" : "image/png";
}
function getFileExt(format) {
return format === "jpg" || format === "jpeg" ? "jpg" : "png";
}
// ===== 下载截图(5.0 返回 { dataUrl } 对象) =====
function downloadScreenshot(screenshot, format) {
const ext = getFileExt(format);
const dataUrl = screenshot.dataUrl;
const link = document.createElement("a");
link.href = dataUrl;
link.download = `screenshot_${Date.now()}.${ext}`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
function setStatus(msg, duration) {
document.getElementById("statusText").textContent = msg;
if (duration) setTimeout(() => {
document.getElementById("statusText").textContent = "📸 takeScreenshot | 点击按钮截图";
}, duration);
}
// ===== 初始化 =====
view.when(() => {
console.log("场景加载完成");
addSceneObjects();
// 截图
document.getElementById("btnScreenshot").addEventListener("click", async () => {
setStatus("⏳ 正在截图...");
const opts = getScreenshotOptions();
lastCanvas = await view.takeScreenshot(opts);
const dataUrl = lastCanvas.dataUrl;
setStatus(
`✅ 截图完成!格式:${opts.format.toUpperCase()}` +
`,DataURL 长度:${Math.round(dataUrl.length / 1024)}KB`,
5000
);
console.log("截图完成");
});
// 下载 PNG
document.getElementById("btnDownload").addEventListener("click", async () => {
setStatus("⏳ 正在生成 PNG 截图...");
const shot = await view.takeScreenshot({ format: "png" });
lastCanvas = shot;
downloadScreenshot(shot, "png");
setStatus("✅ PNG 下载已触发!", 3000);
});
// 下载 JPG
document.getElementById("btnDownloadJpg").addEventListener("click", async () => {
setStatus("⏳ 正在生成 JPG 截图...");
const shot = await view.takeScreenshot({
format: "jpg",
quality: parseFloat(document.getElementById("selQuality").value)
});
lastCanvas = shot;
downloadScreenshot(shot, "jpg");
setStatus("✅ JPG 下载已触发!质量:" + document.getElementById("selQuality").value, 3000);
});
// 预览截图
document.getElementById("btnPreview").addEventListener("click", async () => {
setStatus("⏳ 正在生成预览...");
const shot = lastCanvas || await view.takeScreenshot(getScreenshotOptions());
lastCanvas = shot;
document.getElementById("previewImage").src = shot.dataUrl;
document.getElementById("previewModal").classList.add("show");
setStatus("🔍 截图预览已打开");
});
// 关闭预览
document.getElementById("btnClosePreview").addEventListener("click", () => {
document.getElementById("previewModal").classList.remove("show");
});
document.getElementById("previewModal").addEventListener("click", (e) => {
if (e.target === e.currentTarget) {
document.getElementById("previewModal").classList.remove("show");
}
});
// 预览中的下载按钮
document.getElementById("btnModalDownload").addEventListener("click", () => {
if (lastCanvas) {
const opts = getScreenshotOptions();
downloadScreenshot(lastCanvas, opts.format);
setStatus("✅ 截图已下载!", 3000);
}
});
// 4K 高清截图
document.getElementById("btn4K").addEventListener("click", async () => {
setStatus("⏳ 正在生成 4K 高清截图...");
const shot = await view.takeScreenshot({
format: "png",
width: 3840,
height: 2160
});
lastCanvas = shot;
downloadScreenshot(shot, "png");
setStatus("✅ 4K 截图下载已触发!尺寸:3840×2160px", 5000);
});
});
</script>
</body>
</html>
五、在线示例
🔗 在线体验地址(GitHub资源,等待时间较长,或者架梯子) :https://southjor.github.io/arcgis-examples/lessons/lesson12.html

操作说明:
- 调整视角(拖拽/缩放/旋转),点击「📸 截图」捕获当前视图
- 点击「💾 下载 PNG」一键截图并下载为 PNG 文件
- 点击「📦 下载 JPG」截图并下载为 JPG,注意对比文件大小
- 修改格式(PNG/JPEG)和质量(0.5~1.0),再截图观察差异
- 设置自定义宽度/高度(如 3840×2160),生成超高清截图
- 点击「🖥️ 4K 高清截图」一键生成 3840×2160 截图并下载
- 点击「🔍 预览截图」在弹窗中查看截图效果,弹窗中也支持下载
六、关键API说明
| API | 类型 | 默认值 | 说明 |
|---|---|---|---|
view.takeScreenshot(options?) |
方法 | --- | 异步截取当前视图,返回 Promise<{ dataUrl: string }> |
options.format |
"png" | "jpg" | "jpeg" |
"png" |
输出图片格式。PNG 无损,JPEG 有损 |
options.quality |
number (0~1) | 0.92 | JPEG 质量参数。仅对 JPEG 格式生效,PNG 忽略 |
options.width |
number | 视口宽度 | 输出图片宽度(像素) |
options.height |
number | 视口高度 | 输出图片高度(像素) |
shot.dataUrl |
string | --- | 返回对象的 base64 Data URL,可直接用于 <img src> 或 <a href> |
常见坑点
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 截图内容与屏幕不一致 | UI 控件(标题栏、按钮)也被截入 | 截图是纯 WebGL 渲染内容,UI HTML 不会出现在截图中 |
| PNG 文件很大(>5MB) | PNG 无损压缩,场景越复杂文件越大 | 换用 JPEG 格式 + quality: 0.7~0.85 |
quality 参数设置后无效 |
quality 仅对 JPEG 有效,PNG 忽略 |
确认 format: "jpg" |
| 超宽尺寸截图失败 | GPU 内存不足或浏览器限制 | 降低 width/height,或分块截图后拼接 |
toBlob is not a function |
5.0 返回的是 { dataUrl } 对象而非 Canvas |
直接使用 shot.dataUrl 下载/预览 |
七、系列导航
⬅️ 上一篇 :ArcGIS JS 基础教程(11):飞行定位 goTo
➡️ 下一篇 :ArcGIS JS 基础教程(13):屏幕坐标与地理坐标互转
💡 小贴士 :
takeScreenshot()返回的是HTMLCanvasElement------这意味着你可以在截图后使用 Canvas API 进行二次加工!比如叠加水印文字、绘制标注箭头、合成图例,甚至在截图上画热力圈。只要你能用 Canvas 画出来,就能在截图基础上实现。