P0-1: 删除 docker-compose-override.yml + deploy-server 两份 workers=2→1(预防性修复,服务器根 compose 已是 workers=1)

This commit is contained in:
Simon
2026-07-17 23:16:40 +08:00
parent 3ed86d5fb3
commit 939de70e48
11412 changed files with 2257596 additions and 145 deletions
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024-present yorickshan and html2canvas-pro contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,85 @@
<p align="center">
<img src="https://raw.githubusercontent.com/yorickshan/html2canvas-pro/main/docs/public/logo.png" height="150">
</p>
<h1 align="center">
html2canvas-pro
</h1>
<p align="center">
Next generation JavaScript screenshots tool.
<p>
<p align="center">
<a href="https://github.com/yorickshan/html2canvas-pro/actions/workflows/ci.yml"><img src="https://github.com/yorickshan/html2canvas-pro/actions/workflows/ci.yml/badge.svg?branch=main" alt="build status"></a>
<a href=https://npm.im/html2canvas-pro><img src="https://badgen.net/npm/v/html2canvas-pro" alt="npm version"></a>
<a href=http://npm.im/html2canvas-pro><img src="https://badgen.net/npm/dm/html2canvas-pro" alt="npm downloads"></a>
<a href="https://www.jsdelivr.com/package/npm/html2canvas-pro"><img src="https://data.jsdelivr.com/v1/package/npm/html2canvas-pro/badge" /></a>
<p>
<p align="center">
<a href="https://yorickshan.github.io/html2canvas-pro/getting-started.html">Getting Started</a>
| <a href="https://deepwiki.com/yorickshan/html2canvas-pro">DeepWiki</a>
</p>
<br>
## Why html2canvas-pro?
html2canvas-pro is a fork of [niklasvh/html2canvas](https://github.com/niklasvh/html2canvas) that includes various fixes and new features. It offers several advantages over the original html2canvas, such as:
- support color function ```color()``` (including relative colors)
- support color function ```lab()```
- support color function ```lch()```
- support color function ```oklab()```
- support color function ```oklch()```
- Support object-fit of ```<img/>```
- **Image smoothing control** - Support CSS `image-rendering` property and global `imageSmoothing` option for pixel art and retro graphics
- Fixed some [issues](./CHANGELOG.md)
If you found this helpful, don't forget to
leave a star 🌟.
## Installation
```sh
npm install html2canvas-pro
pnpm / yarn add html2canvas-pro
```
## Usage
```javascript
import html2canvas from 'html2canvas-pro';
```
To render an `element` with html2canvas-pro with some (optional) [options](/docs/configuration.md), simply call `html2canvas(element, options);`
### Basic Example
```javascript
html2canvas(document.body).then(function(canvas) {
document.body.appendChild(canvas);
});
```
### Controlling Output Dimensions
⚠️ **Important**: By default, the output canvas dimensions are affected by `devicePixelRatio`.
```javascript
// If you need exact pixel dimensions (e.g., for a specific file size):
html2canvas(element, {
width: 1920,
height: 1080,
scale: 1 // Set scale to 1 for exact dimensions
}).then(canvas => {
// Canvas will be exactly 1920×1080 pixels
const dataURL = canvas.toDataURL('image/png');
});
```
See the [Configuration Guide](/docs/configuration.md#canvas-dimensions) for more details.
## Contribution
If you want to add some features, feel free to submit PR.
If you want to become a maintainer on it, please contact me.
## License
[MIT](LICENSE).
@@ -0,0 +1,256 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Image Smoothing Demo - html2canvas-pro</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 1200px;
margin: 20px auto;
padding: 20px;
}
.demo-section {
margin-bottom: 40px;
border: 1px solid #ddd;
padding: 20px;
border-radius: 8px;
}
.demo-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
margin-top: 20px;
}
.demo-item {
border: 1px solid #ccc;
padding: 10px;
text-align: center;
}
.pixel-art {
width: 100px;
height: 100px;
background: linear-gradient(45deg, #ff0000 25%, #00ff00 25%, #00ff00 50%, #0000ff 50%, #0000ff 75%, #ffff00 75%);
margin: 10px auto;
}
.pixelated {
image-rendering: pixelated;
image-rendering: -webkit-optimize-contrast;
image-rendering: crisp-edges;
}
.smooth {
image-rendering: smooth;
}
.auto {
image-rendering: auto;
}
button {
background: #007bff;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
margin: 5px;
}
button:hover {
background: #0056b3;
}
.output {
margin-top: 20px;
border: 1px solid #ddd;
padding: 10px;
background: #f5f5f5;
}
canvas {
border: 1px solid #999;
display: block;
margin: 10px auto;
}
</style>
</head>
<body>
<h1>Image Smoothing Demo</h1>
<p>This demo shows how html2canvas-pro handles different image-rendering CSS properties and global imageSmoothing options.</p>
<div class="demo-section">
<h2>Test 1: CSS image-rendering Property</h2>
<p>These images use different CSS <code>image-rendering</code> values:</p>
<div class="demo-grid" id="css-demo">
<div class="demo-item">
<h3>pixelated</h3>
<div class="pixel-art pixelated"></div>
<p>CSS: <code>image-rendering: pixelated</code></p>
</div>
<div class="demo-item">
<h3>crisp-edges</h3>
<div class="pixel-art" style="image-rendering: crisp-edges;"></div>
<p>CSS: <code>image-rendering: crisp-edges</code></p>
</div>
<div class="demo-item">
<h3>smooth (default)</h3>
<div class="pixel-art smooth"></div>
<p>CSS: <code>image-rendering: smooth</code></p>
</div>
<div class="demo-item">
<h3>auto</h3>
<div class="pixel-art auto"></div>
<p>CSS: <code>image-rendering: auto</code></p>
</div>
</div>
<button onclick="renderCssDemo()">Render with CSS Properties</button>
<div class="output" id="css-output"></div>
</div>
<div class="demo-section">
<h2>Test 2: Global imageSmoothing Option</h2>
<p>Same image rendered with different global options:</p>
<div class="demo-grid" id="global-demo">
<div class="demo-item">
<h3>Original</h3>
<div class="pixel-art"></div>
</div>
</div>
<button onclick="renderWithSmoothing()">Render with Smoothing (default)</button>
<button onclick="renderWithoutSmoothing()">Render without Smoothing</button>
<button onclick="renderWithQuality()">Render with High Quality</button>
<div class="output" id="global-output"></div>
</div>
<div class="demo-section">
<h2>Test 3: Low Resolution Image Upscaling</h2>
<p>A small pixel art image upscaled to show the smoothing difference:</p>
<div class="demo-grid" id="upscale-demo">
<div class="demo-item">
<h3>8x8 Pixel Art</h3>
<canvas id="small-pixel-art" width="8" height="8"></canvas>
</div>
</div>
<button onclick="renderUpscaleSmooth()">Upscale with Smoothing</button>
<button onclick="renderUpscalePixelated()">Upscale Pixelated</button>
<div class="output" id="upscale-output"></div>
</div>
<script src="../dist/html2canvas-pro.js"></script>
<script>
// Create a simple pixel art on canvas
function createPixelArt() {
const canvas = document.getElementById('small-pixel-art');
const ctx = canvas.getContext('2d');
const colors = ['#ff0000', '#00ff00', '#0000ff', '#ffff00', '#ff00ff', '#00ffff', '#ffffff', '#000000'];
for (let y = 0; y < 8; y++) {
for (let x = 0; x < 8; x++) {
ctx.fillStyle = colors[(x + y) % colors.length];
ctx.fillRect(x, y, 1, 1);
}
}
}
createPixelArt();
async function renderCssDemo() {
const element = document.getElementById('css-demo');
const output = document.getElementById('css-output');
output.innerHTML = '<p>Rendering with CSS image-rendering properties...</p>';
try {
const canvas = await html2canvas(element, { logging: true });
output.innerHTML = '<h3>Result:</h3>';
output.appendChild(canvas);
console.log('CSS demo rendered successfully');
} catch (error) {
output.innerHTML = `<p style="color: red;">Error: ${error.message}</p>`;
}
}
async function renderWithSmoothing() {
const element = document.getElementById('global-demo');
const output = document.getElementById('global-output');
output.innerHTML = '<p>Rendering with smoothing enabled (default)...</p>';
try {
const canvas = await html2canvas(element, {
imageSmoothing: true,
logging: true
});
output.innerHTML = '<h3>Result (smoothing=true):</h3>';
output.appendChild(canvas);
} catch (error) {
output.innerHTML = `<p style="color: red;">Error: ${error.message}</p>`;
}
}
async function renderWithoutSmoothing() {
const element = document.getElementById('global-demo');
const output = document.getElementById('global-output');
output.innerHTML = '<p>Rendering without smoothing (pixelated)...</p>';
try {
const canvas = await html2canvas(element, {
imageSmoothing: false,
logging: true
});
output.innerHTML = '<h3>Result (smoothing=false):</h3>';
output.appendChild(canvas);
} catch (error) {
output.innerHTML = `<p style="color: red;">Error: ${error.message}</p>`;
}
}
async function renderWithQuality() {
const element = document.getElementById('global-demo');
const output = document.getElementById('global-output');
output.innerHTML = '<p>Rendering with high quality smoothing...</p>';
try {
const canvas = await html2canvas(element, {
imageSmoothing: true,
imageSmoothingQuality: 'high',
logging: true
});
output.innerHTML = '<h3>Result (quality=high):</h3>';
output.appendChild(canvas);
} catch (error) {
output.innerHTML = `<p style="color: red;">Error: ${error.message}</p>`;
}
}
async function renderUpscaleSmooth() {
const canvas = document.getElementById('small-pixel-art');
const output = document.getElementById('upscale-output');
output.innerHTML = '<p>Upscaling with smoothing...</p>';
try {
const result = await html2canvas(canvas.parentElement, {
imageSmoothing: true,
scale: 4,
logging: true
});
output.innerHTML = '<h3>Result (smooth, 4x scale):</h3>';
output.appendChild(result);
} catch (error) {
output.innerHTML = `<p style="color: red;">Error: ${error.message}</p>`;
}
}
async function renderUpscalePixelated() {
const canvas = document.getElementById('small-pixel-art');
const output = document.getElementById('upscale-output');
output.innerHTML = '<p>Upscaling without smoothing (pixelated)...</p>';
try {
const result = await html2canvas(canvas.parentElement, {
imageSmoothing: false,
scale: 4,
logging: true
});
output.innerHTML = '<h3>Result (pixelated, 4x scale):</h3>';
output.appendChild(result);
} catch (error) {
output.innerHTML = `<p style="color: red;">Error: ${error.message}</p>`;
}
}
</script>
</body>
</html>
@@ -0,0 +1,602 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>html2canvas-pro 重构后功能测试</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
background: #f5f5f5;
}
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 30px;
border-radius: 10px;
margin-bottom: 30px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
.test-section {
background: white;
padding: 20px;
margin-bottom: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.test-section h2 {
color: #333;
border-bottom: 2px solid #667eea;
padding-bottom: 10px;
margin-top: 0;
}
/* 背景测试 */
.bg-color {
width: 200px;
height: 100px;
background-color: #ff6b6b;
margin: 10px;
display: inline-block;
}
.bg-image {
width: 200px;
height: 100px;
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100"><rect fill="%23f0f" width="50" height="50"/><rect fill="%230ff" x="50" width="50" height="50"/><rect fill="%23ff0" y="50" width="50" height="50"/><rect fill="%230f0" x="50" y="50" width="50" height="50"/></svg>');
background-size: cover;
margin: 10px;
display: inline-block;
}
.bg-linear {
width: 200px;
height: 100px;
background: linear-gradient(45deg, #ff6b6b, #4ecdc4);
margin: 10px;
display: inline-block;
}
.bg-radial {
width: 200px;
height: 100px;
background: radial-gradient(circle, #ffd93d, #ff6b6b);
margin: 10px;
display: inline-block;
}
/* 边框测试 */
.border-solid {
width: 150px;
height: 80px;
border: 5px solid #667eea;
margin: 10px;
display: inline-block;
padding: 10px;
}
.border-dashed {
width: 150px;
height: 80px;
border: 5px dashed #ff6b6b;
margin: 10px;
display: inline-block;
padding: 10px;
}
.border-dotted {
width: 150px;
height: 80px;
border: 5px dotted #4ecdc4;
margin: 10px;
display: inline-block;
padding: 10px;
}
.border-double {
width: 150px;
height: 80px;
border: 8px double #764ba2;
margin: 10px;
display: inline-block;
padding: 10px;
}
.border-rounded {
width: 150px;
height: 80px;
border: 5px solid #ffd93d;
border-radius: 15px;
margin: 10px;
display: inline-block;
padding: 10px;
}
/* 文本测试 */
.text-basic {
font-size: 18px;
color: #333;
margin: 10px 0;
}
.text-letter-spacing {
font-size: 18px;
letter-spacing: 5px;
color: #667eea;
margin: 10px 0;
}
.text-decoration {
font-size: 18px;
text-decoration: underline wavy #ff6b6b;
margin: 10px 0;
}
.text-shadow {
font-size: 24px;
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
color: #764ba2;
margin: 10px 0;
}
.text-overflow {
width: 200px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
border: 1px solid #ddd;
padding: 5px;
margin: 10px 0;
}
/* 效果测试 */
.effect-opacity {
width: 150px;
height: 80px;
background: #ff6b6b;
opacity: 0.5;
margin: 10px;
display: inline-block;
}
.effect-transform {
width: 150px;
height: 80px;
background: #4ecdc4;
transform: rotate(10deg) scale(0.9);
margin: 10px 20px;
display: inline-block;
}
.effect-clip {
width: 150px;
height: 80px;
background: #ffd93d;
clip-path: polygon(0 0, 100% 0, 100% 70%, 50% 100%, 0 70%);
margin: 10px;
display: inline-block;
}
/* Shadow DOM 测试 */
.shadow-host {
padding: 20px;
border: 2px solid #667eea;
border-radius: 8px;
margin: 10px 0;
}
/* 复合测试 */
.complex-box {
width: 300px;
padding: 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: 5px solid #fff;
border-radius: 15px;
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
color: white;
font-size: 18px;
text-shadow: 1px 1px 2px rgba(0,0,0,0.5);
margin: 20px auto;
transform: perspective(500px) rotateY(5deg);
}
.button {
background: #667eea;
color: white;
border: none;
padding: 12px 30px;
font-size: 16px;
border-radius: 6px;
cursor: pointer;
margin: 10px 5px;
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
transition: all 0.3s;
}
.button:hover {
background: #764ba2;
box-shadow: 0 4px 8px rgba(0,0,0,0.3);
transform: translateY(-2px);
}
.button.success {
background: #4ecdc4;
}
.button.danger {
background: #ff6b6b;
}
.results {
margin-top: 30px;
padding: 20px;
background: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.results canvas {
border: 1px solid #ddd;
margin: 10px;
max-width: 100%;
}
.log {
background: #f8f9fa;
border: 1px solid #ddd;
border-radius: 4px;
padding: 15px;
margin: 10px 0;
font-family: 'Courier New', monospace;
font-size: 12px;
max-height: 300px;
overflow-y: auto;
}
.log-entry {
margin: 5px 0;
padding: 3px 0;
border-bottom: 1px solid #eee;
}
.log-entry.error {
color: #ff6b6b;
}
.log-entry.success {
color: #4ecdc4;
}
.status {
display: inline-block;
padding: 5px 15px;
border-radius: 20px;
font-size: 14px;
font-weight: bold;
margin: 5px;
}
.status.pass {
background: #d4edda;
color: #155724;
}
.status.fail {
background: #f8d7da;
color: #721c24;
}
.status.pending {
background: #fff3cd;
color: #856404;
}
</style>
</head>
<body>
<div class="header">
<h1>🎨 html2canvas-pro 重构后功能测试</h1>
<p>完整测试所有渲染器:Background, Border, Text, Effects</p>
<p><strong>Phase 2 完成:</strong> CanvasRenderer 从 1458行 → 681行 (-53.3%)</p>
</div>
<div class="test-section">
<h2>1. BackgroundRenderer 测试 (265行)</h2>
<div id="bg-test">
<div class="bg-color">纯色背景</div>
<div class="bg-image">图片背景</div>
<div class="bg-linear">线性渐变</div>
<div class="bg-radial">径向渐变</div>
</div>
</div>
<div class="test-section">
<h2>2. BorderRenderer 测试 (218行)</h2>
<div id="border-test">
<div class="border-solid">实心边框</div>
<div class="border-dashed">虚线边框</div>
<div class="border-dotted">点线边框</div>
<div class="border-double">双线边框</div>
<div class="border-rounded">圆角边框</div>
</div>
</div>
<div class="test-section">
<h2>3. TextRenderer 测试 (597行)</h2>
<div id="text-test">
<p class="text-basic">基础文本渲染:The quick brown fox jumps over the lazy dog</p>
<p class="text-letter-spacing">字距渲染:L E T T E R S P A C I N G</p>
<p class="text-decoration">文本装饰:下划线和波浪线效果</p>
<p class="text-shadow">文本阴影:Shadow Effect Text</p>
<p class="text-overflow">文本溢出:这是一段很长的文本,会被截断并显示省略号</p>
</div>
</div>
<div class="test-section">
<h2>4. EffectsRenderer 测试 (121行)</h2>
<div id="effects-test">
<div class="effect-opacity">透明度效果</div>
<div class="effect-transform">变换效果</div>
<div class="effect-clip">裁剪效果</div>
</div>
</div>
<div class="test-section">
<h2>5. Shadow DOM 测试 (Issue #206 修复)</h2>
<div id="shadow-test">
<div class="shadow-host">
<p>这是 Shadow DOM 宿主元素</p>
<slot>默认内容</slot>
</div>
</div>
</div>
<div class="test-section">
<h2>6. 复合功能测试</h2>
<div id="complex-test">
<div class="complex-box">
<h3 style="margin-top: 0;">完整功能展示</h3>
<p>渐变背景 + 边框 + 圆角 + 阴影 + 文本阴影 + 3D变换</p>
</div>
</div>
</div>
<div class="test-section">
<h2>7. 性能监控测试</h2>
<div>
<p>启用性能监控,查看各阶段耗时:</p>
<button class="button" onclick="testWithPerformanceMonitoring()">运行性能监控测试</button>
</div>
</div>
<div class="test-section">
<h2>8. 输入验证测试 (Validator)</h2>
<div>
<p>测试新的输入验证和安全特性:</p>
<button class="button" onclick="testValidator()">运行验证器测试</button>
</div>
</div>
<div class="test-section">
<h2>控制面板</h2>
<button class="button" onclick="runAllTests()">🚀 运行所有测试</button>
<button class="button success" onclick="runBasicTest()">基础测试</button>
<button class="button" onclick="runRenderersTest()">渲染器测试</button>
<button class="button danger" onclick="clearResults()">清除结果</button>
</div>
<div class="results">
<h2>测试结果</h2>
<div id="status-panel">
<span class="status pending">等待测试...</span>
</div>
<div class="log" id="log"></div>
<div id="canvas-results"></div>
</div>
<script src="../dist/html2canvas-pro.js"></script>
<script>
const log = document.getElementById('log');
const statusPanel = document.getElementById('status-panel');
const canvasResults = document.getElementById('canvas-results');
function addLog(message, type = 'info') {
const entry = document.createElement('div');
entry.className = `log-entry ${type}`;
entry.textContent = `[${new Date().toLocaleTimeString()}] ${message}`;
log.appendChild(entry);
log.scrollTop = log.scrollHeight;
}
function updateStatus(text, type = 'pending') {
statusPanel.innerHTML = `<span class="status ${type}">${text}</span>`;
}
function clearResults() {
log.innerHTML = '';
canvasResults.innerHTML = '';
updateStatus('等待测试...', 'pending');
addLog('测试结果已清除');
}
async function runBasicTest() {
addLog('开始基础渲染测试...');
updateStatus('测试中...', 'pending');
try {
const element = document.querySelector('.header');
const canvas = await html2canvas(element, {
logging: true,
scale: 2
});
const section = document.createElement('div');
section.innerHTML = '<h3>基础测试结果:</h3>';
section.appendChild(canvas);
canvasResults.appendChild(section);
addLog(`✅ 基础测试完成 (${canvas.width}x${canvas.height})`, 'success');
updateStatus('基础测试通过', 'pass');
} catch (error) {
addLog(`❌ 基础测试失败: ${error.message}`, 'error');
updateStatus('基础测试失败', 'fail');
}
}
async function runRenderersTest() {
addLog('开始渲染器功能测试...');
updateStatus('测试中...', 'pending');
const tests = [
{ id: 'bg-test', name: 'BackgroundRenderer' },
{ id: 'border-test', name: 'BorderRenderer' },
{ id: 'text-test', name: 'TextRenderer' },
{ id: 'effects-test', name: 'EffectsRenderer' }
];
const rendererSection = document.createElement('div');
const rendererTitle = document.createElement('h3');
rendererTitle.textContent = '渲染器测试结果:';
rendererSection.appendChild(rendererTitle);
let passCount = 0;
for (const test of tests) {
try {
addLog(`测试 ${test.name}...`);
const element = document.getElementById(test.id);
const canvas = await html2canvas(element, {
logging: false,
scale: 1
});
const wrapper = document.createElement('div');
const title = document.createElement('h4');
title.textContent = `${test.name}`;
wrapper.appendChild(title);
wrapper.appendChild(canvas);
rendererSection.appendChild(wrapper);
addLog(`${test.name} 测试通过`, 'success');
passCount++;
} catch (error) {
addLog(`${test.name} 测试失败: ${error.message}`, 'error');
}
}
canvasResults.appendChild(rendererSection);
const status = passCount === tests.length ? 'pass' : 'fail';
updateStatus(`渲染器测试: ${passCount}/${tests.length} 通过`, status);
}
async function testWithPerformanceMonitoring() {
addLog('开始性能监控测试...');
updateStatus('性能测试中...', 'pending');
try {
const element = document.querySelector('.complex-box');
// 启用性能监控
const canvas = await html2canvas(element, {
logging: true,
enablePerformanceMonitoring: true,
scale: 2
});
addLog('✅ 性能监控测试完成,请查看控制台输出', 'success');
addLog('📊 应该看到详细的性能分析数据', 'success');
updateStatus('性能监控正常', 'pass');
const perfSection = document.createElement('div');
const perfTitle = document.createElement('h3');
perfTitle.textContent = '性能监控测试:';
const perfDesc = document.createElement('p');
perfDesc.textContent = '请查看浏览器控制台的性能数据输出';
perfSection.appendChild(perfTitle);
perfSection.appendChild(perfDesc);
perfSection.appendChild(canvas);
canvasResults.appendChild(perfSection);
} catch (error) {
addLog(`❌ 性能监控测试失败: ${error.message}`, 'error');
updateStatus('性能监控失败', 'fail');
}
}
async function testValidator() {
addLog('开始验证器测试...');
try {
// 测试1: 正常情况
addLog('测试1: 正常URL...');
const element1 = document.createElement('div');
element1.innerHTML = '<img src="https://example.com/image.jpg">';
// 这应该正常工作(如果没有配置验证器)
addLog('✅ 正常URL测试通过', 'success');
// 测试2: 检查是否支持自定义验证器
addLog('测试2: 检查验证器支持...');
if (typeof window.html2canvas === 'function') {
addLog('✅ html2canvas 函数可用', 'success');
}
// 测试3: 检查新导出
if (window.createDefaultValidator || window.Validator) {
addLog('✅ 验证器类已导出', 'success');
} else {
addLog('⚠️ 验证器类未在全局导出(可能是模块化)', 'info');
}
addLog('✅ 验证器测试完成', 'success');
updateStatus('验证器正常', 'pass');
} catch (error) {
addLog(`❌ 验证器测试失败: ${error.message}`, 'error');
updateStatus('验证器失败', 'fail');
}
}
async function runAllTests() {
clearResults();
addLog('======================================');
addLog('🎯 开始完整功能测试');
addLog('======================================');
addLog(`测试环境: ${navigator.userAgent}`);
addLog(`测试时间: ${new Date().toLocaleString()}`);
addLog('--------------------------------------');
updateStatus('运行完整测试...', 'pending');
// 依次运行所有测试
await runBasicTest();
await new Promise(resolve => setTimeout(resolve, 500));
await runRenderersTest();
await new Promise(resolve => setTimeout(resolve, 500));
await testWithPerformanceMonitoring();
await new Promise(resolve => setTimeout(resolve, 500));
await testValidator();
addLog('--------------------------------------');
addLog('✅ 所有测试完成!', 'success');
addLog('======================================');
updateStatus('所有测试完成 ✅', 'pass');
}
// 页面加载完成后自动记录
window.addEventListener('load', () => {
addLog('页面加载完成');
addLog('html2canvas-pro 重构版本已加载');
addLog('点击按钮开始测试...');
});
</script>
</body>
</html>
@@ -0,0 +1,35 @@
import tseslint from '@typescript-eslint/eslint-plugin';
import tsparser from '@typescript-eslint/parser';
import prettier from 'eslint-plugin-prettier';
import prettierConfig from 'eslint-config-prettier';
export default [
{
files: ['**/*.ts'],
languageOptions: {
parser: tsparser,
parserOptions: {
project: ['./tsconfig.json', './tests/tsconfig.json'],
ecmaVersion: 2018,
sourceType: 'module',
},
},
plugins: {
'@typescript-eslint': tseslint,
prettier: prettier,
},
rules: {
...prettierConfig.rules,
'no-console': ['error', { allow: ['warn', 'error'] }],
'@typescript-eslint/explicit-member-accessibility': ['error', { accessibility: 'no-public' }],
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-use-before-define': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'@typescript-eslint/class-name-casing': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
'prettier/prettier': 'error',
},
},
];
@@ -0,0 +1,5 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'jsdom',
roots: ['src']
};
@@ -0,0 +1,300 @@
// Karma configuration
// Generated on Sat Aug 05 2017 23:42:26 GMT+0800 (Malay Peninsula Standard Time)
const path = require('path');
const simctl = require('node-simctl');
const iosSimulator = require('appium-ios-simulator');
const listenAddress = 'localhost';
const port = 9876;
const log = require('karma/lib/logger').create('launcher:MobileSafari');
module.exports = function(config) {
// https://github.com/actions/virtual-environments/blob/master/images/macos/macos-10.15-Readme.md
const launchers = {
Safari_IOS_9: {
base: 'MobileSafari',
name: 'iPhone 5s',
platform: 'iOS',
sdk: '9.0'
},
Safari_IOS_10: {
base: 'MobileSafari',
name: 'iPhone 5s',
platform: 'iOS',
sdk: '10.0'
},
Safari_IOS_12: {
base: 'MobileSafari',
name: 'iPhone 5s',
platform: 'iOS',
sdk: '12.4'
},
Safari_IOS_13: {
base: 'MobileSafari',
name: 'iPhone 8',
platform: 'iOS',
sdk: '13.7'
},
Safari_IOS_14: {
base: 'MobileSafari',
name: 'iPhone 8',
platform: 'iOS',
sdk: '14.4'
},
Safari_IOS_15_0: {
base: 'MobileSafari',
name: 'iPhone 13',
platform: 'iOS',
sdk: '15.0'
},
Safari_IOS_15: {
base: 'MobileSafari',
name: 'iPhone 13',
platform: 'iOS',
sdk: '15.2'
},
SauceLabs_IE9: {
base: 'SauceLabs',
browserName: 'internet explorer',
version: '9.0',
platform: 'Windows 7'
},
SauceLabs_IE10: {
base: 'SauceLabs',
browserName: 'internet explorer',
version: '10.0',
platform: 'Windows 7'
},
SauceLabs_IE11: {
base: 'SauceLabs',
browserName: 'internet explorer',
version: '11.0',
platform: 'Windows 7'
},
SauceLabs_Edge18: {
base: 'SauceLabs',
browserName: 'MicrosoftEdge',
version: '18.17763',
platform: 'Windows 10'
},
SauceLabs_Android4: {
base: 'SauceLabs',
browserName: 'Browser',
platform: 'Android',
version: '4.4',
device: 'Android Emulator',
},
SauceLabs_iOS10_3: {
base: 'SauceLabs',
browserName: 'Safari',
platform: 'iOS',
version: '10.3',
device: 'iPhone 7 Plus Simulator'
},
SauceLabs_iOS9_3: {
base: 'SauceLabs',
browserName: 'Safari',
platform: 'iOS',
version: '9.3',
device: 'iPhone 6 Plus Simulator'
},
IE_9: {
base: 'IE',
'x-ua-compatible': 'IE=EmulateIE9',
flags: ['-extoff']
},
IE_10: {
base: 'IE',
'x-ua-compatible': 'IE=EmulateIE10',
flags: ['-extoff']
},
IE_11: {
base: 'IE',
flags: ['-extoff']
},
Safari_Stable: {
base: 'SafariNative'
},
Chrome_Stable: {
base: 'ChromeHeadless'
},
Firefox_Stable: {
base: 'Firefox'
}
};
const ciLauncher = launchers[process.env.TARGET_BROWSER];
const customLaunchers = ciLauncher ? {target_browser: ciLauncher} : {
stable_chrome: {
base: 'ChromeHeadless'
},
stable_firefox: {
base: 'Firefox'
}
};
const injectTypedArrayPolyfills = function(files) {
files.unshift({
pattern: path.resolve(__dirname, './node_modules/js-polyfills/typedarray.js'),
included: true,
served: true,
watched: false
});
};
injectTypedArrayPolyfills.$inject = ['config.files'];
const MobileSafari = function(baseBrowserDecorator, args) {
if(process.platform !== "darwin"){
log.error("This launcher only works in MacOS.");
this._process.kill();
return;
}
baseBrowserDecorator(this);
this.on('start', url => {
simctl.getDevices(args.sdk, args.platform).then(devices => {
const d = devices.find(d => {
return d.name === args.name;
});
if (!d) {
log.error(`No device found for sdk ${args.sdk} with name ${args.name}`);
log.info(`Available devices:`, devices);
this._process.kill();
return;
}
return iosSimulator.getSimulator(d.udid).then(device => {
return simctl.bootDevice(d.udid).then(() => device);
}).then(device => {
return device.waitForBoot(60 * 5 * 1000).then(() => {
return device.openUrl(url);
});
});
}).catch(e => {
console.log('err,', e);
});
});
};
MobileSafari.prototype = {
name: 'MobileSafari',
DEFAULT_CMD: {
darwin: '/Applications/Xcode.app/Contents/Developer/Applications/Simulator.app/Contents/MacOS/Simulator',
},
ENV_CMD: null,
};
MobileSafari.$inject = ['baseBrowserDecorator', 'args'];
config.set({
// base path that will be used to resolve all patterns (eg. files, exclude)
basePath: '',
// frameworks to use
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks: ['mocha', 'inline-mocha-fix'],
// list of files / patterns to load in the browser
files: [
'build/testrunner.js',
{ pattern: './tests/**/*', 'watched': true, 'included': false, 'served': true},
{ pattern: './dist/**/*', 'watched': true, 'included': false, 'served': true},
{ pattern: './node_modules/**/*', 'watched': true, 'included': false, 'served': true},
],
plugins: [
'karma-*',
{
'framework:inline-mocha-fix': ['factory', injectTypedArrayPolyfills]
},
{
'launcher:MobileSafari': ['type', MobileSafari]
}
],
// list of files to exclude
exclude: [
],
// preprocess matching files before serving them to the browser
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
preprocessors: {
},
// test results reporter to use
// possible values: 'dots', 'progress'
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
reporters: ['dots', 'junit'],
junitReporter: {
outputDir: 'tmp/junit/'
},
// web server listen address,
listenAddress,
// web server port
port,
// enable / disable colors in the output (reporters and logs)
colors: true,
// level of logging
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
logLevel: config.LOG_INFO,
// enable / disable watching file and executing tests whenever any file changes
autoWatch: true,
// start these browsers
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
browsers: Object.keys(customLaunchers),
customLaunchers,
// Continuous Integration mode
// if true, Karma captures browsers, runs the tests and exits
singleRun: true,
// Concurrency level
// how many browser should be started simultaneous
concurrency: 5,
proxies: {
'/dist': `http://localhost:${port}/base/dist`,
'/node_modules': `http://localhost:${port}/base/node_modules`,
'/tests': `http://localhost:${port}/base/tests`,
'/assets': `http://localhost:${port}/base/tests/assets`
},
client: {
mocha: {
// change Karma's debug.html to the mocha web reporter
reporter: 'html'
}
},
captureTimeout: 300000,
// Safari on CI (especially newer macOS runners) can be slow to establish
// socket.io heartbeats; generous timeouts avoid false ping-timeout failures.
pingTimeout: 120000,
browserSocketTimeout: 120000,
browserDisconnectTimeout: 120000,
browserDisconnectTolerance: 3,
browserNoActivityTimeout: 1200000
})
};
@@ -0,0 +1,144 @@
{
"name": "html2canvas-pro",
"description": "Screenshots with JavaScript. Next generation!",
"type": "module",
"main": "dist/html2canvas-pro.js",
"module": "dist/html2canvas-pro.esm.js",
"typings": "dist/types/index.d.ts",
"browser": "dist/html2canvas-pro.js",
"exports": {
".": {
"types": "./dist/types/index.d.ts",
"import": "./dist/html2canvas-pro.esm.js",
"require": "./dist/html2canvas-pro.js",
"default": "./dist/html2canvas-pro.esm.js"
}
},
"version": "2.0.4",
"author": {
"name": "yorickshan",
"email": "yorickshan@gmail.com",
"url": "https://github.com/yorickshan"
},
"license": "MIT",
"keywords": [
"html2canvas",
"screenshot"
],
"engines": {
"node": ">=16.0.0"
},
"homepage": "https://yorickshan.github.io/html2canvas-pro/",
"repository": {
"type": "git",
"url": "https://github.com/yorickshan/html2canvas-pro"
},
"devDependencies": {
"@commitlint/cli": "^20.3.0",
"@commitlint/config-conventional": "^20.2.0",
"@rollup/plugin-commonjs": "^29.0.0",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^16.0.3",
"@rollup/plugin-typescript": "^12.3.0",
"@types/chai": "^5.2.3",
"@types/express": "^5.0.6",
"@types/filenamify-url": "^2.0.1",
"@types/glob": "^9.0.0",
"@types/jest": "^29.5.12",
"@types/jest-image-snapshot": "^6.4.0",
"@types/karma": "^6.3.0",
"@types/mkdirp": "^2.0.0",
"@types/mocha": "^10.0.7",
"@types/node": "^25.0.3",
"@types/platform": "^1.3.4",
"@types/promise-polyfill": "^6.0.3",
"@types/serve-index": "^1.9.4",
"@typescript-eslint/eslint-plugin": "^8.50.0",
"@typescript-eslint/parser": "^8.50.0",
"appium-ios-simulator": "^8.0.9",
"base64-arraybuffer": "1.0.2",
"body-parser": "^2.2.1",
"chai": "6.2.2",
"change-case": "^5.4.4",
"conventional-changelog-cli": "^5.0.0",
"cors": "^2.8.5",
"cz-conventional-changelog": "^3.3.0",
"es6-promise": "^4.2.8",
"eslint": "^9.13.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.4",
"express": "^5.2.1",
"filenamify-url": "4.0.0",
"glob": "^13.0.0",
"husky": "^9.1.7",
"jest": "^29.7.0",
"jest-environment-jsdom": "^30.2.0",
"jest-image-snapshot": "^6.4.0",
"jquery": "^3.5.1",
"js-polyfills": "^0.1.42",
"karma": "^6.3.2",
"karma-chrome-launcher": "^3.1.0",
"karma-edge-launcher": "^0.4.2",
"karma-firefox-launcher": "^2.1.0",
"karma-ie-launcher": "^1.0.0",
"karma-junit-reporter": "^2.0.1",
"karma-mocha": "^2.0.1",
"karma-safarinative-launcher": "^1.1.0",
"karma-sauce-launcher": "^4.3.6",
"lint-staged": "^16.2.7",
"mocha": "^11.7.5",
"node-simctl": "^8.1.2",
"platform": "^1.3.6",
"prettier": "^3.3.3",
"puppeteer": "^24.34.0",
"replace-in-file": "^8.4.0",
"rimraf": "^6.0.1",
"rollup": "^4.24.2",
"rollup-plugin-sourcemaps": "^0.6.3",
"serve-index": "^1.9.1",
"slash": "5.1.0",
"standard-version": "^9.5.0",
"ts-jest": "^29.1.5",
"ts-node": "^10.9.2",
"tsx": "^4.22.3",
"typescript": "^5.5.4",
"uglify-js": "^3.13.10",
"vitepress": "^1.3.0",
"yargs": "^18.0.0"
},
"scripts": {
"prebuild": "rimraf dist/ && rimraf build/ && mkdirp dist && mkdirp build",
"build": "tsc --module commonjs && npx tsx --tsconfig tsconfig.json ./node_modules/.bin/rollup -c rollup.config.ts && npm run build:create-reftest-list && npx tsx --tsconfig tests/tsconfig.json ./node_modules/.bin/rollup -c tests/rollup.config.ts && npm run build:minify",
"build:testrunner": "npx tsx --tsconfig tests/tsconfig.json ./node_modules/.bin/rollup -c tests/rollup.config.ts",
"build:minify": "uglifyjs --compress --comments /^!/ -o dist/html2canvas-pro.min.js --mangle -- dist/html2canvas-pro.js",
"build:reftest-result-list": "npx tsx scripts/create-reftest-result-list.ts",
"build:create-reftest-list": "npx tsx scripts/create-reftest-list.ts tests/reftests/ignore.txt build/reftests.js",
"release": "sh scripts/version-upgrade.sh",
"format": "prettier --write \"{src,tests,scripts}/**/*.ts\"",
"lint": "eslint src/**/*.ts --max-warnings 0",
"test": "npm run lint && npm run unittest && npm run karma",
"unittest": "jest",
"reftests-diff": "mkdirp tmp/snapshots && jest --roots=tests --testMatch=**/reftest-diff.ts",
"prekarma": "node scripts/ensure-karma-prereqs.mjs",
"karma": "tsx tests/karma",
"watch": "rollup -c rollup.config.ts -w",
"watch:unittest": "mocha --require tsx/register --watch-extensions ts -w src/**/__tests__/*.ts",
"start": "npx tsx tests/server --port=8080 --cors=8081",
"commitlint": "commitlint --config .commitlintrc.json -e -V",
"tag": "sh scripts/create-tag.sh",
"prepare": "if [ \"$SKIP_PREPARE\" != \"true\" ]; then husky install; fi",
"docs:dev": "vitepress dev docs",
"docs:build": "vitepress build docs",
"docs:preview": "vitepress preview docs"
},
"dependencies": {
"css-line-break": "^2.1.0",
"text-segmentation": "^1.0.3"
},
"lint-staged": {
"{src,tests}/**/*.ts": [
"prettier --write",
"eslint --fix"
]
}
}