话不多说,直接上方案~
方案对比
方案 | star | 实现成本 | 原理 | 特点 | live demo | 其他 |
---|---|---|---|---|---|---|
HTML Canvas原生绘制 | - | 高 | 通过HTMLCanvasElement 的drawImage 方法,将页面元素(如<video> 、<img> 或DOM节点)绘制到Canvas,再通过toDataURL 或toBlob 生成图片 | 如果需要区域截屏、对截图进行标注,需要额外开发**如果需要支持对页面任意结构截图,最好在html2canvas/dom-to-image基础上二次封装 | ||
第三方库(html2canvas) | 31.3k | 需二次封装 | 通过解析DOM结构,模拟浏览器渲染过程,将CSS样式和DOM节点转换为Canvas绘制指令,最终生成图片 | 支持大部分CSS(flex、grid、伪类),但对SVG支持有限如果需要区域截屏、对截图进行标注,一些方案需要额外开发 | ||
第三方库(dom-to-image) | 10.6k | 需二次封装 | 核心原理是利用 SVG 的 标签嵌入 HTML,再通过 Canvas 渲染为图像 | 支持更多现代CSS(如filter、clip-path)如果需要区域截屏、对截图进行标注,需要额外开发 | ||
js-screen-shot | 889 | 低 | > 引用- 获取当前可视区域的内容,将其存储起来- 为整个cnavas画布绘制蒙层- 在获取到的内容中进行拖拽,绘制镂空选区- 选择截图工具栏的工具,选择画笔大小等信息- 在选区内拖拽绘制对应的图形- 将选区内的内容转换为图片 | www.kaisir.cn/js-screen-s… | 文章分享:www.kaisir.cn/js-screen-s… | |
第三方库(region-screenshot-js) | 52 | 低 | 基于 dom-to-image、jquery、getDisplayMedia 实现 | 实现选区框绘制、拖动缩放、标注工具集成,最终生成带标注的截图 | weijun-lab.github.io/region-scre… | |
视频捕获 | - | 低 | 通过 drawImage(video, 0, 0) 将当前视频帧绘制到 canvas调用 canvas.toDataURL('image/png') 生成图片数据 | 截取视频某一帧 |
技术方案
基于HTML Canvas的原生绘制
原理:通过HTMLCanvasElement
的drawImage
方法,将页面元素(如<video>
、<img>
或DOM节点)绘制到Canvas,再通过toDataURL
或toBlob
生成图片。
适用场景:
-
截取特定DOM节点(如图表、卡片)
-
截取视频帧或实时画面
实现步骤(React+TS示例) :
// 截取指定Ref的DOM节点
const captureElement = async (ref: React.RefObject<HTMLElement>) => {
const element = ref.current;
if (!element) return null;
// 获取元素尺寸
const { width, height } = element.getBoundingClientRect();
// 创建Canvas并绘制
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
// 绘制DOM节点(需处理跨域)
const blob = await new Promise<Blob | null>(resolve =>
html2canvas(element).then(canvas => canvas.toBlob(resolve))
);
return blob;
};
优缺点:
优点 | 缺点 |
---|---|
无需第三方依赖 | 复杂CSS(如阴影、伪类)丢失 |
支持实时视频帧截取 | 跨域资源(图片、字体)无法绘制 |
第三方截图库(html2canvas/dom-to-image)
原理:通过解析DOM结构,模拟浏览器渲染过程,将CSS样式和DOM节点转换为Canvas绘制指令,最终生成图片。
实现示例(React+html2canvas) :
import html2canvas from 'html2canvas';
const ScreenshotComponent = () => {
const targetRef = useRef<HTMLDivElement>(null);
const handleCapture = async () => {
if (!targetRef.current) return;
// 生成截图
const canvas = await html2canvas(targetRef.current, {
useCORS: true, // 解决跨域图片问题
logging: false, // 关闭日志
backgroundColor: null // 保留透明背景
});
// 生成下载链接
const url = canvas.toDataURL('image/png');
const a = document.createElement('a');
a.href = url;
a.download = 'screenshot.png';
a.click();
};
return (
<div>
<div ref={targetRef} style={{ padding: '20px', border: '1px solid #ddd' }}>
<h3>待截图内容</h3>
<p>示例文本与图片:<img src="/assets/example.png" alt="示例" /></p>
</div>
<Button onClick={handleCapture}>截图</Button>
</div>
);
};
兼容性:
-
支持现代浏览器(Chrome 60+、Firefox 53+、Edge 79+)
-
不支持IE(需polyfill)
-
部分CSS特性(如
box-shadow: inset
、backdrop-filter
)可能无法正确渲染
选区截屏插件(region-screenshot-js)
原理:基于Canvas和鼠标事件监听,实现选区框绘制、拖动缩放、标注工具集成,最终生成带标注的截图。
核心功能:
-
自由选区(矩形/圆形/多边形)
-
标注工具(画笔、文字、表情、马赛克)
-
截图参数配置(初始选区、输出格式)
实现示例(React+TS) :
import RegionScreenshot from 'region-screenshot-js';
const RegionScreenshotTool = () => {
const [screenshotUrl, setScreenshotUrl] = useState('');
const handleCapture = () => {
const screenshot = new RegionScreenshot({
initialRegion: { top: 100, left: 100, width: 800, height: 600 }, // 初始选区
customDrawing: [
{
className: 'emoji',
optionsHtml:
<img class="active" src="/assets/emoji-1.png"/> <img src="/assets/emoji-2.png"/>
,
onDrawingOpen: (canvas, options, save) => {
canvas.onclick = (e) => {
const img = options.querySelector('img.active') as HTMLImageElement;
canvas.getContext('2d')?.drawImage(img, e.offsetX - 10, e.offsetY - 10);
save();
};
}
}
]
});
screenshot.on('screenshotGenerated', (url) => {
setScreenshotUrl(url);
});
};
return (
<div>
<Button onClick={handleCapture}>选区截图</Button>
{screenshotUrl && <img src={screenshotUrl} alt="选区截图" />}
</div>
);
};
优势:
-
提供完整的交互组件(选区框、工具面板)
-
支持自定义标注工具扩展
-
输出包含标注的最终图片
视频捕获
关键技术点:
- Canvas 绘图:
- 使用隐藏的
<canvas>
元素 - 通过
drawImage(video, 0, 0)
将当前视频帧绘制到 canvas - 调用
canvas.toDataURL('image/png')
生成图片数据
const ctx = canvas.getContext('2d');
ctx?.drawImage(video, 0, 0, canvas.width, canvas.height);
-
视频时间控制:
-
通过
videoRef.current.currentTime
获取当前播放时间 -
监听
onTimeUpdate
事件实时更新时间状态
实现示例:
import React, { useState, useRef, useEffect } from 'react';
import { Button, message, Upload, Modal, Image } from 'antd';
import { UploadOutlined, CameraOutlined } from '@ant-design/icons';
import {
containerStyle,
videoContainerStyle,
videoStyle,
controlsStyle,
previewStyle
} from './styles';
import { VideoFrameCaptureProps, VideoState } from './types';
const VideoFrameCapture = ({
onCapture,
previewWidth = 300,
previewHeight = 180
}: VideoFrameCaptureProps) => {
const [videoState, setVideoState] = useState<VideoState>({
videoUrl: '',
isLoaded: false,
currentTime: 0,
});
const [capturePreview, setCapturePreview] = useState<string>('');
const [isPreviewVisible, setIsPreviewVisible] = useState(false);
const videoRef = useRef<HTMLVideoElement>(null);
const canvasRef = useRef<HTMLCanvasElement>(null);
// 处理视频文件上传
const handleUpload = (file: File) => {
const reader = new FileReader();
reader.onload = (e) => {
if (e.target?.result) {
setVideoState(prev => ({
...prev,
videoUrl: e.target.result as string,
isLoaded: false,
}));
}
};
reader.readAsDataURL(file);
return false; // 阻止默认上传行为
};
// 视频加载完成事件
const handleVideoLoaded = () => {
setVideoState(prev => ({ ...prev, isLoaded: true }));
};
// 截取当前帧
const captureFrame = () => {
if (!videoRef.current || !canvasRef.current) return;
const video = videoRef.current;
const canvas = canvasRef.current;
const ctx = canvas.getContext('2d');
// 设置Canvas尺寸与视频一致
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
// 绘制当前帧到Canvas
ctx?.drawImage(video, 0, 0, canvas.width, canvas.height);
// 生成图片Data URL
const dataUrl = canvas.toDataURL('image/png');
setCapturePreview(dataUrl);
setIsPreviewVisible(true);
onCapture?.(dataUrl);
message.success('截图成功!');
};
// 视频时间更新监听
const handleTimeUpdate = () => {
if (videoRef.current) {
setVideoState(prev => ({
...prev,
currentTime: videoRef.current.currentTime,
}));
}
};
return (
<div style={containerStyle}>
<h2>视频帧截取</h2>
{/* 视频上传 */}
<Upload
accept="video/*"
beforeUpload={handleUpload}
showUploadList={false}
>
<Button icon={<UploadOutlined />}>上传视频文件</Button>
</Upload>
{/* 视频播放器 */}
{videoState.videoUrl && (
<div style={videoContainerStyle}>
<video
ref={videoRef}
style={videoStyle}
src={videoState.videoUrl}
controls
onLoadedData={handleVideoLoaded}
onTimeUpdate={handleTimeUpdate}
aria-label="视频播放器"
/>
{/* 隐藏的Canvas用于截图 */}
<canvas ref={canvasRef} style={{ display: 'none' }} />
</div>
)}
{/* 操作按钮 */}
<div style={controlsStyle}>
<Button
type="primary"
icon={<CameraOutlined />}
onClick={captureFrame}
disabled={!videoState.isLoaded}
aria-label="截取当前帧"
>
截取当前帧
</Button>
</div>
{/* 截图预览模态框 */}
<Modal
title="截图预览"
visible={isPreviewVisible}
onOk={() => setIsPreviewVisible(false)}
onCancel={() => setIsPreviewVisible(false)}
width={previewWidth + 48} // 加上模态框内边距
>
<div style={previewStyle}>
<Image
src={capturePreview}
width={previewWidth}
height={previewHeight}
alt="截取帧"
/>
</div>
</Modal>
</div>
);
};
export default VideoFrameCapture;