@hx-cd/screenshot
v0.1.2
Published
A pure JavaScript browser screenshot library with zero dependencies
Maintainers
Readme
@hx-cd/screenshot
A pure JavaScript browser screenshot library with zero external dependencies. Uses SVG foreignObject for high-fidelity captures.
Features
- Zero Dependencies - Pure vanilla JavaScript, no external libraries
- High Fidelity - SVG foreignObject preserves CSS/layout precision
- Multiple Capture Modes - Viewport, element, region, full-page screenshots
- Interactive Selection - Drag-to-select with resizable selection box
- Advanced Editor - 7 annotation tools (arrow, rect, circle, pen, text, blur, mosaic)
- Undo/Redo - Full history support with Ctrl+Z / Ctrl+Y
- Popover Toolbar - Context-aware toolbar below selection
- Magnifier - 2x-10x zoom for pixel-level precision
- Watermark - Text or image watermarks with positioning and tiling
- Compression - Quality control and format conversion (PNG/JPEG/WebP)
- Custom Actions - Extensible editor with custom buttons
- High-DPI Support - Scale factor for retina displays
Installation
pnpm add @hx-cd/screenshot
# or
npm install @hx-cd/screenshot
# or
yarn add @hx-cd/screenshotQuick Start
Basic Usage
import { Screenshot } from '@hx-cd/screenshot';
const screenshot = new Screenshot();
// Capture viewport
const result = await screenshot.captureViewport();
console.log(`Captured: ${result.width} x ${result.height}`);
// Download the result
screenshot.download(result.canvas, 'screenshot.png');
// Or get as blob
const blob = await result.toBlob();
// Or as data URL
const dataUrl = result.toDataURL();Interactive Selection (框选编辑模式)
import { Screenshot } from '@hx-cd/screenshot';
const screenshot = new Screenshot();
// Capture with user selection - shows editor after selection
const result = await screenshot.captureSelection();
// User flow:
// 1. Drag to select region → Screenshot captured immediately
// 2. Adjust selection using 8 drag handles (corners + edges)
// 3. Move selection by dragging the selection box
// 4. Select annotation tools from popover toolbar
// 5. Draw on the canvas (arrow, rect, circle, pen, text, blur, mosaic)
// 6. Click Confirm or press Enter to complete
// 7. Click Cancel or press ESC to abortAPI Reference
Screenshot Class
const screenshot = new Screenshot(options?: ScreenshotOptions);Options
interface ScreenshotOptions {
scale?: number; // Scale factor for high-DPI (default: 1)
backgroundColor?: string; // Background color (default: '#ffffff')
useCORS?: boolean; // Use CORS for images (default: true)
includeShadow?: boolean; // Include shadow DOM (default: true)
format?: 'image/png' | 'image/jpeg' | 'image/webp'; // Output format
quality?: number; // Image quality 0-1 (default: 0.95)
maxWidth?: number; // Maximum output width
maxHeight?: number; // Maximum output height
customActions?: CustomAction[]; // Custom actions for editor
captureRoot?: Element; // Root element to limit capture scope
}Capture Methods
// Capture viewport (visible area)
const result = await screenshot.captureViewport();
// Capture specific element (by selector or element reference)
const result = await screenshot.captureElement('.selector');
const result = await screenshot.captureElement(document.getElementById('my-element'));
// Capture specific region (coordinates relative to viewport)
const result = await screenshot.captureRegion(x, y, width, height);
// Capture full page (including scrollable area)
const result = await screenshot.captureFullPage(useTiling?: boolean);
// Capture with user selection (opens interactive editor)
const result = await screenshot.captureSelection(captureRoot?: Element);Image Processing
// Add watermark
const watermarked = await screenshot.addWatermark(canvas, {
text: '© 2025 My Company',
position: 'bottom-right',
opacity: 0.5,
fontSize: 24,
color: '#ffffff',
});
// Apply annotations programmatically
const annotated = screenshot.applyAnnotations(canvas, [
{
type: 'rect',
x: 50,
y: 50,
width: 100,
height: 60,
color: '#ff0000',
lineWidth: 3,
},
]);
// Create interactive annotation editor
const editor = screenshot.createAnnotationEditor(canvas);
// Compress canvas
const blob = await screenshot.compress(canvas, { quality: 0.8, format: 'image/jpeg' });
// Compress image blob
const compressedBlob = await screenshot.compressImage(originalBlob, { quality: 0.7 });Utility Methods
// Download canvas
screenshot.download(canvas, 'filename.png');
// Download blob
screenshot.downloadBlob(blob, 'filename.png');
// Copy to clipboard
await screenshot.copyToClipboard(canvas);
// Convert canvas to blob
const blob = await screenshot.toBlob(canvas);
// Convert canvas to data URL
const dataUrl = screenshot.toDataURL(canvas);
// Update options
screenshot.setOptions({ quality: 0.8 });
// Get current options (with defaults)
const options = screenshot.getOptions();CaptureResult Interface
All capture methods return a CaptureResult:
interface CaptureResult {
canvas: HTMLCanvasElement;
width: number;
height: number;
toBlob: (type?: string, quality?: number) => Promise<Blob>;
toDataURL: (type?: string, quality?: number) => string;
}Annotation Tools
| Tool | Type | Description |
|------|------|-------------|
| Arrow | arrow | Draw arrows with configurable color/width |
| Rectangle | rect | Draw rectangles |
| Circle | circle | Draw circles/ellipses |
| Pen | pen | Freehand drawing |
| Text | text | Add text labels with font size control |
| Mosaic | mosaic | Pixelate region |
| Blur | blur | Gaussian blur region |
Keyboard Shortcuts
| Key | Action |
|-----|--------|
| Enter | Confirm selection |
| Escape | Cancel selection |
| Ctrl+Z | Undo |
| Ctrl+Y or Ctrl+Shift+Z | Redo |
Custom Actions
Extend the editor with custom buttons:
const screenshot = new Screenshot({
customActions: [
{
label: 'Export',
icon: '📥',
title: 'Export as PNG',
onClick: async (canvas, ctx) => {
const blob = await ctx.getBlob();
// Your custom logic here
},
enabled: () => true, // Optional: controls button disabled state
},
],
});Architecture
Core Rendering Pipeline
1. Clone DOM tree (deep clone)
↓
2. Inline styles (200+ CSS properties via getComputedStyle)
↓
3. Inline resources (images, backgrounds, canvases)
↓
4. Serialize to SVG foreignObject
↓
5. Render to Canvas via Image + drawImage
↓
6. Export (Blob/DataURL/download)Key Modules
| Module | Purpose |
|--------|---------|
| svgRenderer.ts | Core buildSvgDataUrl() and renderSvgToCanvas() |
| utils/style.ts | Recursive style inlining with scroll compensation |
| utils/dom.ts | DOM cloning and render utilities |
| utils/resource.ts | External resource inlining (CORS support) |
| features/annotation.ts | 7 annotation tools |
| features/editor/ | Selection, toolbar, magnifier, editor |
| core/history/ | Undo/redo with Command + Memento patterns |
Browser Compatibility
| Browser | Minimum Version | Support | |---------|-----------------|---------| | Chrome | 70+ | ✅ Full | | Firefox | 65+ | ✅ Full | | Safari | 13+ | ✅ Full | | Edge | 79+ | ✅ Full | | Chrome (Android) | 70+ | ✅ Full | | Safari (iOS) | 13+ | ✅ Full |
Not supported:
- IE 11 and below (no foreignObject support)
- Old Android WebView (<70)
Known Limitations
- Cross-origin images - Require CORS headers or will fail to inline
- WebGL content - Not captured (canvas appears blank)
- Video content - Only poster image is captured
- iframes - Content inside iframes is not captured
- CSS filters - Limited support for some complex filters
- Box shadows - May be clipped in some cases
Scroll Handling
The library correctly handles scroll offsets in three scenarios:
| Case | Detection | Handling |
|------|-----------|----------|
| Table-only container | overflow: auto + single <table> child | Apply transform to <tbody> only |
| Standard container | overflow: auto + multiple/non-table children | Apply transform to direct children |
| Non-scroll container | overflow: visible or no overflow | No transform applied |
Table Header Fix
Problem: Browser natively fixes <thead> at container top. Applying transform to <table> breaks this.
Solution: Apply scroll compensation only to <tbody> elements:
<div style="overflow: auto;" data-scroll-x="50" data-scroll-y="100">
<table> ← No transform (table header stays fixed)
<thead>...</thead> ← Browser native fixed positioning
<tbody>...</tbody> ← transform: translate(-50px, -100px)
</table>
</div>Performance Tips
- Use appropriate scale - Don't use high scale for large captures
- Inline images - Large images increase memory usage
- Tile large captures - Use
captureFullPage(true)for very long pages - Clean up - Call
destroy()for selection tools when done
Package Structure
src/
├── index.ts # Main entry
├── Screenshot.ts # Screenshot class
├── types.ts # TypeScript types
│
├── capture/ # Capture mechanisms
│ ├── svgRenderer.ts # SVG → Canvas core
│ ├── baseCapture.ts # Unified capture pipeline
│ ├── element.ts # Element capture
│ ├── viewport.ts # Viewport capture
│ ├── fullPage.ts # Full-page/tiled capture
│ └── region.ts # Region capture/crop
│
├── features/ # Feature modules
│ ├── annotation.ts # 7 annotation tools
│ ├── watermark.ts # Watermark functionality
│ ├── compress.ts # Image compression
│ └── editor/
│ ├── AdvancedSelection.ts # Selection tool with editor
│ ├── Editor.ts # Standalone image editor
│ ├── Toolbar.ts # Unified toolbar
│ ├── SelectionCanvas.ts # Canvas display manager
│ ├── SelectionResizer.ts # 8-handle resizer
│ ├── EditorRenderer.ts # Canvas rendering
│ ├── EditorStateManager.ts # State management
│ └── magnifier.ts # Magnifier tool
│
├── utils/ # Utilities
│ ├── style.ts # Style inlining (200+ props)
│ ├── dom.ts # DOM utilities
│ ├── resource.ts # Resource inlining
│ ├── canvas.ts # Canvas utilities
│ └── download.ts # Download/clipboard
│
├── core/history/ # Undo/redo
│ ├── HistoryManager.ts # Command + Memento pattern
│ └── commands.ts # Command implementations
│
└── styles/ # CSS styles
├── selection.css
└── editor.cssLicense
MIT
