@zaxxz/snapkit
v0.2.0
Published
一个面向前端项目的截图、编辑与 WebGL 合成 SDK。
Maintainers
Readme
@zaxxz/snapkit
一个面向前端业务系统的截图 SDK,提供页面截图、截图后裁剪批注、复制保存、WebRTC 截屏、html2canvas DOM 截图,以及 WebGL/Cesium/Three.js 图层合成能力。
安装
npm install @zaxxz/snapkit也可以按子路径导入:
import { capture } from '@zaxxz/snapkit/capture';
import { captureAndEdit } from '@zaxxz/snapkit/capture-editor';功能特性
- 支持
capture()基础截图。 - 支持
captureAndEdit()截图后打开编辑器,进行裁剪、画笔、矩形、箭头、文字批注。 - 支持
openCaptureEditor()对已有图片打开编辑器。 - 支持
dataURL、Blob、File、HTMLCanvasElement四种输出类型。 - 支持 WebRTC
getDisplayMedia截屏,也支持html2canvasDOM 截图兜底。 - 支持 WebGL canvas / base64 与 DOM 截图合成,适配 Cesium、Three.js、Mapbox GL、deck.gl 等场景。
- 提供统一
SnapkitError和错误码,方便业务方按失败原因做不同提示。 - 同时发布 ESM、CJS 和 TypeScript 类型声明。
快速开始
import { capture } from '@zaxxz/snapkit';
const image = await capture({
strategy: 'auto',
target: '.dashboard',
mimeType: 'image/png',
onCaptured(meta) {
console.log('实际截图策略:', meta.strategy);
}
});默认返回 dataURL 字符串,可以直接赋给 img.src、复制到剪贴板,或下载保存。
API
capture(options?)
function capture(options?: CaptureOptions): Promise<string>;capture() 默认返回 dataURL。你也可以通过 output 指定其他返回类型。
const blob = await capture({
target: '#report',
output: 'blob',
mimeType: 'image/png'
});
const formData = new FormData();
formData.append('file', blob);完整输出类型:
const dataURL = await capture({ output: 'dataURL' });
const blob = await capture({ output: 'blob' });
const file = await capture({ output: 'file', fileName: 'report.png' });
const canvas = await capture({ output: 'canvas' });常用配置:
interface CaptureOptions {
strategy?: 'auto' | 'webrtc' | 'html2canvas';
output?: 'dataURL' | 'blob' | 'file' | 'canvas';
fileName?: string;
target?: HTMLElement | string;
mimeType?: string;
quality?: number;
timeout?: number;
frameDelay?: number;
html2canvas?: object;
webgl?: boolean | WebGLCaptureOptions;
cesium?: boolean | WebGLCaptureOptions;
onCaptured?: (meta: CaptureResultMeta) => void;
}captureAndEdit(options?)
先截图,再打开截图编辑器。用户点击完成时返回编辑后的 dataURL,用户取消时返回 null。
import { captureAndEdit } from '@zaxxz/snapkit';
const editedImage = await captureAndEdit({
target: '.dashboard',
editor: {
fileName: 'dashboard.png',
strokeColor: '#ff4d4f',
strokeWidth: 3,
onCopy(dataUrl) {
console.log('已复制:', dataUrl.length);
},
onSave(dataUrl) {
console.log('已保存:', dataUrl.length);
}
}
});编辑器工具栏包含裁剪、画笔、矩形、箭头、文字、撤销、重置、复制、保存、完成和取消。
openCaptureEditor(imageUrl, options?)
如果业务方已经有图片 dataURL,可以直接打开编辑器:
import { openCaptureEditor } from '@zaxxz/snapkit';
const result = await openCaptureEditor(existingDataUrl, {
fileName: 'edited.png'
});auto 策略说明
strategy: 'auto' 是 SDK 推荐策略。当前执行顺序是:
- 如果配置了
webgl或cesium,优先走html2canvas + WebGL 合成。 - 如果没有 WebGL 配置,再判断当前环境是否支持 WebRTC。
- WebRTC 可用时优先使用
getDisplayMedia,获取真实屏幕画面。 - WebRTC 不可用、超时或失败时,自动降级到
html2canvas。
这样设计的原因是:业务按钮截图通常更希望“点击即截图”,不希望弹出浏览器授权框;而 WebGL 场景通过 DOM 截图加 WebGL 合成,更适合 Cesium、Three.js 这类页面内部截图。
如果你明确想触发浏览器屏幕选择器,可以强制指定:
await capture({
strategy: 'webrtc'
});如果你只想截某个 DOM 区域,可以强制指定:
await capture({
strategy: 'html2canvas',
target: '#panel'
});错误处理
SDK 提供统一错误类型 SnapkitError 和错误码 SnapkitErrorCode。业务方可以根据 error.code 展示不同提示。
import { capture, SnapkitError, SNAPKIT_ERROR_CODES } from '@zaxxz/snapkit';
try {
await capture({
strategy: 'webrtc'
});
} catch (error) {
if (error instanceof SnapkitError) {
if (error.code === SNAPKIT_ERROR_CODES.WEBRTC_NEED_HTTPS) {
console.warn('请在 HTTPS 或 localhost 环境使用 WebRTC 截屏');
}
if (error.code === SNAPKIT_ERROR_CODES.WEBRTC_PERMISSION_DENIED) {
console.warn('用户取消了屏幕共享授权');
}
}
}常见错误码:
| 错误码 | 含义 |
| --- | --- |
| SNAPKIT_TARGET_NOT_FOUND | 传入的 target 选择器没有匹配到元素 |
| SNAPKIT_WEBRTC_NOT_SUPPORTED | 当前浏览器不支持 getDisplayMedia |
| SNAPKIT_WEBRTC_NEED_HTTPS | WebRTC 截屏需要 HTTPS 或 localhost |
| SNAPKIT_WEBRTC_PERMISSION_DENIED | 用户拒绝或取消屏幕共享授权 |
| SNAPKIT_HTML2CANVAS_FAILED | html2canvas 渲染 DOM 失败 |
| SNAPKIT_WEBGL_CANVAS_NOT_FOUND | 开启 WebGL 合成但没有找到 canvas 或 image |
| SNAPKIT_WEBGL_EXPORT_FAILED | WebGL canvas 导出失败 |
| SNAPKIT_CANVAS_TAINTED | canvas 被跨域资源污染,无法导出 |
| SNAPKIT_WEBGL_IMAGE_LOAD_FAILED | 传入的 WebGL 图片加载失败 |
| SNAPKIT_WEBGL_COMPOSITE_FAILED | WebGL 图层与 DOM 图层合成失败 |
WebGL 图层合成
为什么需要 webgl
html2canvas 主要面向 DOM 和 CSS 渲染,并不真正理解 WebGL 场景。Cesium、Three.js、Mapbox GL、deck.gl、Babylon.js 或自研 WebGL 渲染器的画面,在 DOM 截图时可能出现黑屏、透明、空白或错位。
Snapkit 的 webgl 方案不是解析三维场景,而是进行图层合成:
- 业务方提供 WebGL 图层,可以是
canvas,也可以是已经生成好的 dataURL/base64。 - SDK 截 DOM 时临时隐藏 WebGL 元素,避免
html2canvas抓到黑块。 - SDK 让 DOM 截图中对应区域保持透明。
- SDK 先绘制 WebGL 底图,再绘制 DOM 截图。
- 最终得到“WebGL 画面 + DOM 控件/弹窗/标注”的完整截图。
推荐:传入 canvas
const image = await capture({
strategy: 'auto',
target: '#map-page',
webgl: {
canvas: () => viewer.scene.canvas,
beforeCapture() {
viewer.scene.requestRender();
viewer.scene.render();
},
renderDelay: 80
}
});这种方式适合:
- Cesium 的
viewer.scene.canvas - Three.js 的
renderer.domElement - Mapbox GL 的地图 canvas
- 自研 WebGL renderer 的 canvas
更稳定:传入 dataURL/base64
如果业务方已经知道如何稳定生成 WebGL 快照,可以直接传 image。此时 SDK 只负责合成。
const image = await capture({
target: '#map-page',
webgl: {
image: () => renderer.domElement.toDataURL('image/png'),
element: () => renderer.domElement,
rect: () => renderer.domElement.getBoundingClientRect()
}
});只传 image 时,建议同时传 element 和 rect:
webgl: {
image: () => webglImage,
element: () => webglCanvas,
rect: () => webglCanvas.getBoundingClientRect()
}image 说明“画什么”,element 说明 DOM 截图时隐藏哪个元素,rect 说明这张图片应该画到最终截图的哪个位置。
webgl 配置项
interface WebGLCaptureOptions {
enabled?: boolean;
canvas?: HTMLCanvasElement | string | (() => HTMLCanvasElement | undefined);
image?: string | (() => string | Promise<string>);
element?: HTMLElement | string | (() => HTMLElement | undefined);
rect?: DOMRect | { left: number; top: number; width: number; height: number } | (() => DOMRect | undefined);
hideCanvasDuringDomCapture?: boolean;
renderDelay?: number;
beforeCapture?: (canvas: HTMLCanvasElement) => void | Promise<void>;
afterCapture?: (canvas: HTMLCanvasElement) => void | Promise<void>;
}常用写法:
webgl: {
canvas: '.map-container canvas'
}webgl: {
image: async () => {
await waitTilesReady();
return canvas.toDataURL('image/png');
},
element: () => canvas,
rect: () => canvas.getBoundingClientRect()
}webgl: {
canvas: () => viewer.scene.canvas,
hideCanvasDuringDomCapture: true,
renderDelay: 100,
beforeCapture(canvas) {
console.log(canvas.width, canvas.height);
}
}Cesium 示例
推荐使用通用 webgl 配置:
const image = await capture({
strategy: 'auto',
target: '#cesium-page',
webgl: {
canvas: () => viewer.scene.canvas,
beforeCapture() {
viewer.scene.requestRender();
viewer.scene.render();
},
renderDelay: 80
}
});Cesium 初始化时建议开启:
const viewer = new Viewer(container, {
contextOptions: {
webgl: {
preserveDrawingBuffer: true
}
}
});preserveDrawingBuffer: true 可以提高 canvas 导出稳定性,但可能带来渲染性能开销。建议只在确实需要截图的页面或业务中开启。
兼容写法:
await capture({
target: '#cesium-page',
cesium: {
canvas: () => viewer.scene.canvas,
beforeCapture() {
viewer.scene.render();
}
}
});cesium 仍然可用,但它只是 webgl 的别名,新项目建议统一使用 webgl。
Three.js 示例
const renderer = new THREE.WebGLRenderer({
antialias: true,
preserveDrawingBuffer: true
});
const image = await capture({
target: '#three-page',
webgl: {
canvas: () => renderer.domElement,
beforeCapture() {
renderer.render(scene, camera);
}
}
});也可以让业务方自己生成图片,再交给 SDK 合成:
const image = await capture({
target: '#three-page',
webgl: {
image: () => renderer.domElement.toDataURL('image/png'),
element: () => renderer.domElement,
rect: () => renderer.domElement.getBoundingClientRect()
}
});Vue3 使用示例
<template>
<section>
<div ref="captureRef" class="report-panel">
<h2>销售看板</h2>
<p>这里是需要截图的业务内容。</p>
</div>
<button @click="handleCapture">截图</button>
<button @click="handleCaptureAndEdit">截图并编辑</button>
<img v-if="preview" :src="preview" alt="截图预览" />
</section>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { capture, captureAndEdit, SnapkitError } from '@zaxxz/snapkit';
const captureRef = ref<HTMLElement | null>(null);
const preview = ref('');
async function handleCapture() {
try {
preview.value = await capture({
target: captureRef.value!,
strategy: 'auto',
output: 'dataURL'
});
} catch (error) {
if (error instanceof SnapkitError) {
console.warn(error.code, error.message);
}
}
}
async function handleCaptureAndEdit() {
const result = await captureAndEdit({
target: captureRef.value!,
strategy: 'auto',
editor: {
fileName: 'report.png'
}
});
if (result) {
preview.value = result;
}
}
</script>上传到后端时可以直接输出 Blob:
const blob = await capture({
target: captureRef.value!,
output: 'blob'
});
const formData = new FormData();
formData.append('file', blob, 'report.png');Vue2 使用示例
<template>
<section>
<div ref="captureRef" class="report-panel">
<h2>销售看板</h2>
<p>这里是需要截图的业务内容。</p>
</div>
<button @click="handleCapture">截图</button>
<button @click="handleCaptureAndEdit">截图并编辑</button>
<img v-if="preview" :src="preview" alt="截图预览" />
</section>
</template>
<script>
import { capture, captureAndEdit, SnapkitError } from '@zaxxz/snapkit';
export default {
data() {
return {
preview: ''
};
},
methods: {
async handleCapture() {
try {
this.preview = await capture({
target: this.$refs.captureRef,
strategy: 'auto',
output: 'dataURL'
});
} catch (error) {
if (error instanceof SnapkitError) {
console.warn(error.code, error.message);
}
}
},
async handleCaptureAndEdit() {
const result = await captureAndEdit({
target: this.$refs.captureRef,
strategy: 'auto',
editor: {
fileName: 'report.png'
}
});
if (result) {
this.preview = result;
}
}
}
};
</script>使用限制 / 注意事项
- WebRTC 截屏依赖
navigator.mediaDevices.getDisplayMedia,只能在 HTTPS 或 localhost 环境使用,并且一定会触发用户授权。 html2canvas不是真实截图,它是对 DOM 和 CSS 的模拟渲染,对 WebGL、视频、iframe、跨域图片支持有限。- 不同源 iframe 的内部内容不能被父页面直接读取。可以由子页面通过
postMessage把自己的截图 dataURL 传给父页面,再由父页面使用webgl.image或业务 canvas 合成。 - WebGL canvas 如果绘制了跨域资源,可能导致 canvas tainted,浏览器会禁止
toDataURL/toBlob导出。 - Cesium、Three.js 等 WebGL 场景建议开启
preserveDrawingBuffer,但它可能增加 GPU 内存和渲染性能开销。 - 大尺寸页面或高倍屏截图会占用较多内存,必要时可以降低
html2canvas.scale或缩小截图区域。 - 页面滚动、CSS transform、fixed 弹窗、缩放容器等复杂布局可能导致合成错位,建议显式传
webgl.rect修正位置。 - 如果页面中有跨域图片,建议图片服务开启 CORS,并设置
html2canvas: { useCORS: true }。
常见问题
为什么 WebGL 区域会黑屏?
WebGL 画面通常在 GPU 缓冲区中,html2canvas 并不真正理解 WebGL 场景。某些浏览器或渲染器配置下,DOM 截图只能得到黑色、透明或空白区域。建议使用 webgl.canvas 或 webgl.image 让 SDK 做图层合成。
传 canvas 还是传 image?
如果 WebGL canvas 可以稳定读取,传 canvas 更简单。如果业务库有自己的截图 API,或者读取时机比较复杂,传 image 更稳定。
合成位置错位怎么办?
优先显式传 element 和 rect:
webgl: {
image: () => snapshot,
element: () => canvas,
rect: () => canvas.getBoundingClientRect()
}如果页面存在滚动、缩放、弹窗或 CSS transform,显式传 rect 会更稳定。
本地开发
npm install
npm run typecheck
npm run build发布
首次发布前请确认已经登录 npm,并且当前账号拥有 @zaxxz scope 的发布权限。
npm login
npm run typecheck
npm run build
npm pack --dry-run
npm publish --access public