ArcGIS JS 基础教程(12):视图截图 takeScreenshot

简介: ArcGIS JS 基础教程(12):视图截图 takeScreenshot

零、写在前面

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

💡 在线示例:完整可运行的 HTML 示例,无需任何环境配置,可直接在浏览器中打开体验
🗂️ 专栏导航:收藏 + 关注,专栏文章第一时间送达
❤️ 一键三连:点赞(给教程充电)+ 评论(提问必回)+ 收藏(下次再看)


一、功能介绍

在 WebGIS 开发中,经常需要将当前地图视图保存为图片,用于生成报告、分享成果、制作演示稿等。ArcGIS Maps SDK for JavaScript 提供了 SceneView.takeScreenshot() 方法,可以一键捕获当前三维场景的高清截图。

takeScreenshot() 的核心特性:

  • 异步截图:返回 Promise,在场景渲染完成后自动捕获当前帧
  • 格式可选:支持 PNG(无损)和 JPEG / JPG(有损压缩)
  • 自定义尺寸:可以指定输出图片的宽高(像素),实现 4K/8K 高清截图
  • 质量控制:JPEG 格式支持 quality 质量参数(官方 5.0 类型为 0~100,默认 98),平衡清晰度和文件大小
  • 直接下载:返回对象的 dataUrl 属性可直接用于 <a> 下载或 <img> 预览

二、功能实现

核心 API: SceneView.takeScreenshot(options?),返回 Promise<Screenshot>,Screenshot.dataUrl 是 base64 编码的图片数据。

2.1 基础截图

// 最简单的用法:捕获当前视图,默认 PNG 格式
view.takeScreenshot().then(shot => {
   
    // shot.dataUrl 是 base64 编码的图片数据
    console.log("截图完成!", shot.dataUrl.substring(0, 100) + "...");
});

2.2 指定输出格式

// PNG 截图(默认,无损)
const pngShot = await view.takeScreenshot({
    format: "png" });

// JPEG 截图(有损压缩,文件更小)
const jpgShot = await view.takeScreenshot({
   
    format: "jpg",
    quality: 80             // 官方 5.0 类型为 0~100(默认 98)。值越小文件越小但质量越低
});
console.log(jpgShot.dataUrl);  // 直接取 dataUrl 属性

2.3 自定义图片尺寸

默认情况下截图尺寸等于当前视口尺寸。指定 width / height 可以生成更高分辨率的图片:

// 生成 4K(3840x2160)高清截图
const shot = await view.takeScreenshot({
   
    width: 3840,
    height: 2160,
    format: "png"
});
// shot.dataUrl 中包含 4K 分辨率的 base64 图片数据

⚠️ 注意: 超大尺寸截图会消耗更多内存和时间,并且可能受限于浏览器和 GPU 的性能上限。

2.4 下载截图为图片文件

直接使用 dataUrl 触发浏览器下载:

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 在新窗口预览截图

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" ❌ 有损 50~100 ~150-500 KB (q=80) 网页展示、邮件附件、快速分享
"jpeg" ❌ 有损 50~100 同 jpg 同 jpg(别名)

2.7 完整参数说明

view.takeScreenshot({
   
    format: "png",           // "png" | "jpg" | "jpeg",默认 "png"
    quality: 98,             // JPEG 质量 0~100,PNG 忽略此参数,官方默认 98
    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,可直接在浏览器打开。

<!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="50">低 50(文件最小)</option>
                <option value="70">中 70</option>
                <option value="85" selected>高 85(推荐)</option>
                <option value="100">最高 100</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
        }
    });

    let lastCanvas = null;   // 保存最近一次截图结果

    // ===== 添加 3D 场景元素(让截图更丰富) =====
    function addSceneObjects() {
    
        const layer = new GraphicsLayer({
    
            title: "场景元素"
        });

        // 中心金色大楼
        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(() => {
    
        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
            );
        });

        // 下载 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

在这里插入图片描述

操作说明:

  1. 调整视角(拖拽/缩放/旋转),点击「📸 截图」捕获当前视图
  2. 点击「💾 下载 PNG」一键截图并下载为 PNG 文件
  3. 点击「📦 下载 JPG」截图并下载为 JPG,注意对比文件大小
  4. 修改格式(PNG/JPEG)和质量(50~100),再截图观察差异
  5. 设置自定义宽度/高度(如 3840×2160),生成超高清截图
  6. 点击「🖥️ 4K 高清截图」一键生成 3840×2160 截图并下载
  7. 点击「🔍 预览截图」在弹窗中查看截图效果,弹窗中也支持下载

六、关键API说明

API 类型 默认值 说明
view.takeScreenshot(options?) 方法 — 异步截取当前视图,返回 Promise<{ dataUrl: string }>
options.format "png" \ "jpg" \ "jpeg" "png" 输出图片格式。PNG 无损,JPEG 有损
options.quality number (0~100) 98 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: 70~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() 返回的是带 dataUrl 的 Screenshot 对象。想做水印、标注、合成图例这类二次加工,把 dataUrl 画到自建 Canvas 上(ctx.drawImage(img, ...)),处理完再用 canvas.toDataURL() 导出即可。

相关文章
|
12天前
|
Linux 开发工具 iOS开发
Codex保姆级教程(下载+安装+登录+使用,含所有版本,2026最新)
Codex是OpenAI推出的开源AI编程智能体,本地运行,可理解整个代码库,支持自然语言指令改代码、执行命令、跑测试、修Bug,兼容Win/macOS/Linux。(239字)
4908 33
|
13天前
|
JavaScript API iOS开发
DeepSeek Harness下载、安装、配置一篇搞定(2026整理,送常用插件)
DeepSeek Harness(DSH)是DeepSeek于2026年开源的MIT协议AI Agent框架,支持插件化组装模型、工具、沙箱与UI,兼容主流大模型。本文提供免Node.js的桌面端安装包(Win/macOS),含详细图文教程及热门插件推荐。
4565 52
|
13天前
|
iOS开发 Docker MacOS
【2026实测】Docker Desktop下载、安装、汉化、配置教程(附官网安装包)
Docker Desktop 是 Docker 官方推出的桌面容器管理工具,集成引擎、CLI、图形界面与 Kubernetes,支持 Win/macOS/Linux。2026 最新版 4.88.1 强化 ARM 原生运行、镜像加速及资源可视化,附中文汉化与国内镜像配置指南。
2192 7
|
7天前
|
云安全 人工智能 安全
|
7天前
|
Linux 开发工具 iOS开发
2026最新codex下载安装保姆级使用教程(下载+安装+登录+使用,含所有历史版本)
本文详解OpenAI Codex国内快速上手指南:提供Win/macOS/Linux全平台免安装包、npm/Homebrew等多方式安装、账号/API Key双登录、简体中文切换,覆盖下载、安装、汉化、使用及高频避坑(如微软商店失败、npm超时等),10分钟即可跑通。
1612 3
|
5天前
|
人工智能 安全 API
DeepSeek Harness下载、安装、配置一篇搞定(2026整理,送常用插件)
DeepSeek Harness(dsh)是DeepSeek AI于2026年开源的轻量级Agent运行框架,基于Cordis内核,践行“一切皆插件”理念。支持Web/桌面双端,开箱即用;内置多模型接入、丰富插件生态(超3.5万),适配编程、办公、创作等场景。(239字)
1076 0
|
13天前
|
安全 算法 定位技术
《高性价比人生指南》完整原版:普通人用最少的资源,过好这一生的底层逻辑~How To Live Better
📖《高性价比人生指南》是一份基于实证、拒绝焦虑的生存手册,倡导以“人生时薪”为视角,重新计算时间、金钱与精力的成本与收益。它不承诺逆袭,只教人算清账:省下的钱若耗掉时间便亏;买体验比买物品更值;睡眠是最高杠杆的健康投资;关系贵在情绪价值而非表面热络;学习重在改变行为而非自我安慰。核心理念:用低成本、高回报的微小行动,换取长期的选择权——不是成为谁,而是掌控自己人生的主动权。
2215 6
|
6天前
|
人工智能 并行计算 调度
本地 AI 一键部署工具 LocModel:Windows 端集成画图、视频生成、Agent 的开源模型运行方案
LocModel 是专为 Windows 设计的一站式本地 AI 工具,支持零配置启动、自动硬件检测、GGUF 模型一键加载、文生图/视频生成及 Agent 自动化任务。适配 8G 显存,内置显存分片调度,轻松实现 AI 漫剧批量创作与离线大模型调用。(239 字)