customer-chat-sdk 1.0.33 → 1.0.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/ScreenshotManager.d.ts +43 -1
- package/dist/core/ScreenshotManager.d.ts.map +1 -1
- package/dist/customer-sdk.cjs.js +364 -31
- package/dist/customer-sdk.esm.js +364 -31
- package/dist/customer-sdk.min.js +1 -1
- package/dist/types/index.d.ts +3 -1
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +3 -2
|
@@ -13,7 +13,7 @@ export interface ScreenshotOptions {
|
|
|
13
13
|
enableCORS?: boolean;
|
|
14
14
|
proxyUrl?: string;
|
|
15
15
|
useProxy?: boolean;
|
|
16
|
-
engine?: 'modern-screenshot' | 'snapdom';
|
|
16
|
+
engine?: 'modern-screenshot' | 'snapdom' | 'html2canvas';
|
|
17
17
|
corsMode?: 'simple' | 'smart' | 'proxy' | 'blob' | 'canvas-proxy' | 'ignore' | 'test-first';
|
|
18
18
|
silentMode?: boolean;
|
|
19
19
|
maxRetries?: number;
|
|
@@ -28,6 +28,8 @@ export interface ScreenshotOptions {
|
|
|
28
28
|
fetchPriority?: 'high' | 'low' | 'auto';
|
|
29
29
|
maxCacheSize?: number;
|
|
30
30
|
maxCacheAge?: number;
|
|
31
|
+
maxImageSize?: number;
|
|
32
|
+
skipLargeImages?: boolean;
|
|
31
33
|
}
|
|
32
34
|
/**
|
|
33
35
|
* 上传配置接口
|
|
@@ -69,6 +71,9 @@ export declare class ScreenshotManager {
|
|
|
69
71
|
private intersectionObserver;
|
|
70
72
|
private visibleElementsCache;
|
|
71
73
|
private preconnected;
|
|
74
|
+
private imageDownloadQueue;
|
|
75
|
+
private activeDownloads;
|
|
76
|
+
private maxConcurrentImageDownloads;
|
|
72
77
|
private globalErrorHandler;
|
|
73
78
|
private globalRejectionHandler;
|
|
74
79
|
constructor(targetElement: HTMLElement | null, options?: ScreenshotOptions);
|
|
@@ -120,8 +125,41 @@ export declare class ScreenshotManager {
|
|
|
120
125
|
* 注意:snapdom 内部使用 worker 进行截图处理,会在后台线程执行,不会阻塞主线程
|
|
121
126
|
*/
|
|
122
127
|
private takeScreenshotWithSnapdom;
|
|
128
|
+
/**
|
|
129
|
+
* 使用 html2canvas 截图
|
|
130
|
+
*
|
|
131
|
+
* 优势:
|
|
132
|
+
* - 处理 SVG 和本地资源更快(不需要复杂的 Worker 通信)
|
|
133
|
+
* - 兼容性好,支持更多 CSS 特性
|
|
134
|
+
* - 跨域处理相对简单
|
|
135
|
+
*
|
|
136
|
+
* 劣势:
|
|
137
|
+
* - 在主线程执行,可能阻塞 UI(但处理速度快,影响较小)
|
|
138
|
+
* - 不支持 Worker 模式
|
|
139
|
+
*
|
|
140
|
+
* 适用场景:
|
|
141
|
+
* - 页面包含大量 SVG 图标
|
|
142
|
+
* - 本地资源较多
|
|
143
|
+
* - 需要快速截图
|
|
144
|
+
*/
|
|
145
|
+
private takeScreenshotWithHtml2Canvas;
|
|
123
146
|
/**
|
|
124
147
|
* 使用 modern-screenshot 截图(启用 Worker)
|
|
148
|
+
*
|
|
149
|
+
* 优势:
|
|
150
|
+
* - 使用 Worker,不阻塞主线程 UI
|
|
151
|
+
* - 支持并发处理
|
|
152
|
+
* - 适合复杂页面
|
|
153
|
+
*
|
|
154
|
+
* 劣势:
|
|
155
|
+
* - 处理 SVG 和本地资源较慢(Worker 通信开销)
|
|
156
|
+
* - 配置相对复杂
|
|
157
|
+
* - 需要处理 Worker URL
|
|
158
|
+
*
|
|
159
|
+
* 适用场景:
|
|
160
|
+
* - 复杂页面,需要不阻塞 UI
|
|
161
|
+
* - 需要高质量截图
|
|
162
|
+
* - 页面资源较少
|
|
125
163
|
*/
|
|
126
164
|
private takeScreenshotWithModernScreenshot;
|
|
127
165
|
/**
|
|
@@ -176,6 +214,10 @@ export declare class ScreenshotManager {
|
|
|
176
214
|
* 通过代理服务器获取图片并转换为 data URL
|
|
177
215
|
*/
|
|
178
216
|
private proxyImage;
|
|
217
|
+
/**
|
|
218
|
+
* 不使用代理时下载图片(带内存保护)
|
|
219
|
+
*/
|
|
220
|
+
private downloadImageWithoutProxy;
|
|
179
221
|
/**
|
|
180
222
|
* 将 blob 转换为 data URL
|
|
181
223
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ScreenshotManager.d.ts","sourceRoot":"","sources":["../../src/core/ScreenshotManager.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ScreenshotManager.d.ts","sourceRoot":"","sources":["../../src/core/ScreenshotManager.ts"],"names":[],"mappings":"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"}
|
package/dist/customer-sdk.cjs.js
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
|
+
var html2canvas = require('html2canvas');
|
|
6
|
+
|
|
5
7
|
// 直接使用base64字符串,避免打包后路径问题
|
|
6
8
|
const iconImage = 'data:image/png;base64,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';
|
|
7
9
|
class IconManager {
|
|
@@ -6507,13 +6509,17 @@ class ScreenshotManager {
|
|
|
6507
6509
|
this.visibleElementsCache = new Set();
|
|
6508
6510
|
// 预连接状态
|
|
6509
6511
|
this.preconnected = false;
|
|
6512
|
+
// 图片下载队列和并发控制(防止频繁截图时重复下载)
|
|
6513
|
+
this.imageDownloadQueue = new Map(); // URL -> Promise,避免重复下载
|
|
6514
|
+
this.activeDownloads = new Set(); // 正在下载的 URL
|
|
6515
|
+
this.maxConcurrentImageDownloads = 5; // 最大并发下载数(降低,避免内存问题)
|
|
6510
6516
|
// 全局错误处理器
|
|
6511
6517
|
this.globalErrorHandler = null;
|
|
6512
6518
|
this.globalRejectionHandler = null;
|
|
6513
6519
|
this.targetElement = targetElement;
|
|
6514
6520
|
this.options = {
|
|
6515
6521
|
interval: options.interval ?? 5000,
|
|
6516
|
-
quality: options.quality ?? 0.4
|
|
6522
|
+
quality: options.quality ?? 0.3, // 降低默认质量:0.4 -> 0.3,减少 base64 大小
|
|
6517
6523
|
scale: options.scale ?? 1,
|
|
6518
6524
|
maxHistory: options.maxHistory ?? 10,
|
|
6519
6525
|
compress: options.compress ?? false,
|
|
@@ -6537,7 +6543,9 @@ class ScreenshotManager {
|
|
|
6537
6543
|
useIntersectionObserver: options.useIntersectionObserver ?? true, // 默认使用 Intersection Observer
|
|
6538
6544
|
fetchPriority: options.fetchPriority ?? 'high', // 默认高优先级
|
|
6539
6545
|
maxCacheSize: options.maxCacheSize ?? 50, // 默认最大50MB
|
|
6540
|
-
maxCacheAge: options.maxCacheAge ?? 86400000 // 默认24小时(86400000ms)
|
|
6546
|
+
maxCacheAge: options.maxCacheAge ?? 86400000, // 默认24小时(86400000ms)
|
|
6547
|
+
maxImageSize: options.maxImageSize ?? 5, // 不使用代理时,单个图片最大尺寸(MB),默认5MB
|
|
6548
|
+
skipLargeImages: options.skipLargeImages ?? true // 不使用代理时,是否跳过过大的图片,默认true(跳过)
|
|
6541
6549
|
};
|
|
6542
6550
|
this.setupMessageListener();
|
|
6543
6551
|
this.setupVisibilityChangeListener();
|
|
@@ -6898,6 +6906,9 @@ class ScreenshotManager {
|
|
|
6898
6906
|
if (selectedEngine === 'snapdom') {
|
|
6899
6907
|
dataUrl = await this.takeScreenshotWithSnapdom(this.targetElement);
|
|
6900
6908
|
}
|
|
6909
|
+
else if (selectedEngine === 'html2canvas') {
|
|
6910
|
+
dataUrl = await this.takeScreenshotWithHtml2Canvas(this.targetElement);
|
|
6911
|
+
}
|
|
6901
6912
|
else {
|
|
6902
6913
|
// 默认使用 modern-screenshot
|
|
6903
6914
|
dataUrl = await this.takeScreenshotWithModernScreenshot(this.targetElement);
|
|
@@ -7060,8 +7071,173 @@ class ScreenshotManager {
|
|
|
7060
7071
|
throw error;
|
|
7061
7072
|
}
|
|
7062
7073
|
}
|
|
7074
|
+
/**
|
|
7075
|
+
* 使用 html2canvas 截图
|
|
7076
|
+
*
|
|
7077
|
+
* 优势:
|
|
7078
|
+
* - 处理 SVG 和本地资源更快(不需要复杂的 Worker 通信)
|
|
7079
|
+
* - 兼容性好,支持更多 CSS 特性
|
|
7080
|
+
* - 跨域处理相对简单
|
|
7081
|
+
*
|
|
7082
|
+
* 劣势:
|
|
7083
|
+
* - 在主线程执行,可能阻塞 UI(但处理速度快,影响较小)
|
|
7084
|
+
* - 不支持 Worker 模式
|
|
7085
|
+
*
|
|
7086
|
+
* 适用场景:
|
|
7087
|
+
* - 页面包含大量 SVG 图标
|
|
7088
|
+
* - 本地资源较多
|
|
7089
|
+
* - 需要快速截图
|
|
7090
|
+
*/
|
|
7091
|
+
async takeScreenshotWithHtml2Canvas(element) {
|
|
7092
|
+
if (!this.options.silentMode) {
|
|
7093
|
+
console.log('📸 使用 html2canvas 引擎截图...');
|
|
7094
|
+
}
|
|
7095
|
+
try {
|
|
7096
|
+
// 检查元素是否存在和可见
|
|
7097
|
+
const rect = element.getBoundingClientRect();
|
|
7098
|
+
if (rect.width === 0 || rect.height === 0) {
|
|
7099
|
+
throw new Error('元素尺寸为 0,无法截图');
|
|
7100
|
+
}
|
|
7101
|
+
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
|
7102
|
+
const isLowEndDevice = navigator.hardwareConcurrency && navigator.hardwareConcurrency <= 4;
|
|
7103
|
+
// 计算压缩后的尺寸
|
|
7104
|
+
let elementWidth = element.scrollWidth || element.clientWidth || element.offsetWidth;
|
|
7105
|
+
let elementHeight = element.scrollHeight || element.clientHeight || element.offsetHeight;
|
|
7106
|
+
if (element === document.body || element === document.documentElement) {
|
|
7107
|
+
elementWidth = Math.max(element.scrollWidth, element.offsetWidth, document.documentElement.scrollWidth, document.documentElement.offsetWidth, window.innerWidth);
|
|
7108
|
+
elementHeight = Math.max(element.scrollHeight, element.offsetHeight, document.documentElement.scrollHeight, document.documentElement.offsetHeight, window.innerHeight);
|
|
7109
|
+
}
|
|
7110
|
+
const { width, height } = this.calculateCompressedSize(elementWidth, elementHeight, this.options.maxWidth, this.options.maxHeight);
|
|
7111
|
+
const finalWidth = width < elementWidth ? width : Math.min(elementWidth, this.options.maxWidth);
|
|
7112
|
+
const finalHeight = height < elementHeight ? height : Math.min(elementHeight, this.options.maxHeight);
|
|
7113
|
+
// html2canvas 质量设置(0-1)
|
|
7114
|
+
const finalQuality = isMobile || isLowEndDevice
|
|
7115
|
+
? Math.max(this.options.quality * 0.65, 0.2)
|
|
7116
|
+
: this.options.quality;
|
|
7117
|
+
// html2canvas 配置选项
|
|
7118
|
+
const options = {
|
|
7119
|
+
// 基本配置
|
|
7120
|
+
backgroundColor: '#ffffff',
|
|
7121
|
+
scale: this.options.scale !== 1 ? (isMobile ? 0.7 : this.options.scale) : (isMobile ? 0.7 : 1),
|
|
7122
|
+
useCORS: this.options.enableCORS,
|
|
7123
|
+
allowTaint: !this.options.enableCORS, // 如果启用 CORS,不允许 taint
|
|
7124
|
+
logging: !this.options.silentMode,
|
|
7125
|
+
width: finalWidth,
|
|
7126
|
+
height: finalHeight,
|
|
7127
|
+
// 性能优化
|
|
7128
|
+
removeContainer: true, // 截图后移除临时容器
|
|
7129
|
+
imageTimeout: this.options.imageLoadTimeout || 5000,
|
|
7130
|
+
// 忽略某些元素(可选,提升性能)
|
|
7131
|
+
ignoreElements: (element) => {
|
|
7132
|
+
// 忽略隐藏元素
|
|
7133
|
+
const style = window.getComputedStyle(element);
|
|
7134
|
+
if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') {
|
|
7135
|
+
return true;
|
|
7136
|
+
}
|
|
7137
|
+
return false;
|
|
7138
|
+
},
|
|
7139
|
+
};
|
|
7140
|
+
// html2canvas 不支持直接的 proxy 选项,需要通过 onclone 钩子处理图片
|
|
7141
|
+
// 如果配置了代理服务器,在克隆时替换图片 URL
|
|
7142
|
+
if (this.options.useProxy && this.options.proxyUrl && this.options.proxyUrl.trim() !== '') {
|
|
7143
|
+
options.onclone = (clonedDoc) => {
|
|
7144
|
+
// 在克隆的文档中,替换所有跨域图片的 src
|
|
7145
|
+
const images = clonedDoc.querySelectorAll('img');
|
|
7146
|
+
images.forEach((img) => {
|
|
7147
|
+
const originalSrc = img.getAttribute('src');
|
|
7148
|
+
if (!originalSrc)
|
|
7149
|
+
return;
|
|
7150
|
+
// 检查是否是跨域图片
|
|
7151
|
+
try {
|
|
7152
|
+
const imgUrl = new URL(originalSrc, window.location.href);
|
|
7153
|
+
if (imgUrl.origin === window.location.origin) {
|
|
7154
|
+
return; // 同源图片,不需要处理
|
|
7155
|
+
}
|
|
7156
|
+
}
|
|
7157
|
+
catch {
|
|
7158
|
+
// URL 解析失败,可能是相对路径,继续处理
|
|
7159
|
+
}
|
|
7160
|
+
// 检查缓存
|
|
7161
|
+
const cachedDataUrl = this.getCachedImage(originalSrc);
|
|
7162
|
+
if (cachedDataUrl) {
|
|
7163
|
+
img.src = cachedDataUrl;
|
|
7164
|
+
return;
|
|
7165
|
+
}
|
|
7166
|
+
// 对于跨域图片,使用代理 URL
|
|
7167
|
+
// html2canvas 会自动处理,但我们可以预先处理
|
|
7168
|
+
// 注意:html2canvas 会自己处理图片加载,这里主要是为了缓存
|
|
7169
|
+
});
|
|
7170
|
+
};
|
|
7171
|
+
}
|
|
7172
|
+
if (!this.options.silentMode) {
|
|
7173
|
+
console.log(`📸 html2canvas 配置: 尺寸 ${finalWidth}x${finalHeight}, 质量 ${finalQuality.toFixed(2)}, 缩放 ${options.scale}`);
|
|
7174
|
+
}
|
|
7175
|
+
// 执行截图
|
|
7176
|
+
const canvas = await html2canvas(element, options);
|
|
7177
|
+
// 根据输出格式转换
|
|
7178
|
+
let mimeType = 'image/png';
|
|
7179
|
+
let finalQualityForExport = undefined;
|
|
7180
|
+
if (this.options.outputFormat === 'webp' && !isMobile) {
|
|
7181
|
+
try {
|
|
7182
|
+
const testCanvas = document.createElement('canvas');
|
|
7183
|
+
testCanvas.width = 1;
|
|
7184
|
+
testCanvas.height = 1;
|
|
7185
|
+
const testDataUrl = testCanvas.toDataURL('image/webp');
|
|
7186
|
+
if (testDataUrl.indexOf('webp') !== -1) {
|
|
7187
|
+
mimeType = 'image/webp';
|
|
7188
|
+
finalQualityForExport = finalQuality;
|
|
7189
|
+
}
|
|
7190
|
+
}
|
|
7191
|
+
catch {
|
|
7192
|
+
mimeType = 'image/jpeg';
|
|
7193
|
+
finalQualityForExport = finalQuality;
|
|
7194
|
+
}
|
|
7195
|
+
}
|
|
7196
|
+
else if (this.options.outputFormat === 'jpeg') {
|
|
7197
|
+
mimeType = 'image/jpeg';
|
|
7198
|
+
finalQualityForExport = finalQuality;
|
|
7199
|
+
}
|
|
7200
|
+
// 转换为 data URL
|
|
7201
|
+
const dataUrl = mimeType === 'image/png'
|
|
7202
|
+
? canvas.toDataURL(mimeType)
|
|
7203
|
+
: canvas.toDataURL(mimeType, finalQualityForExport);
|
|
7204
|
+
// 验证结果
|
|
7205
|
+
if (!dataUrl || dataUrl.length < 100) {
|
|
7206
|
+
throw new Error('生成的截图数据无效或过短');
|
|
7207
|
+
}
|
|
7208
|
+
if (!this.options.silentMode) {
|
|
7209
|
+
console.log(`📸 html2canvas 截图成功!格式: ${this.options.outputFormat}, 尺寸: ${canvas.width}x${canvas.height}`);
|
|
7210
|
+
}
|
|
7211
|
+
return dataUrl;
|
|
7212
|
+
}
|
|
7213
|
+
catch (error) {
|
|
7214
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
7215
|
+
if (!this.options.silentMode) {
|
|
7216
|
+
console.error('📸 html2canvas 截图失败:', errorMessage);
|
|
7217
|
+
if (errorMessage.includes('CORS') || errorMessage.includes('cross-origin')) {
|
|
7218
|
+
console.warn('📸 💡 建议:配置 proxyUrl 选项处理跨域图片');
|
|
7219
|
+
}
|
|
7220
|
+
}
|
|
7221
|
+
throw error;
|
|
7222
|
+
}
|
|
7223
|
+
}
|
|
7063
7224
|
/**
|
|
7064
7225
|
* 使用 modern-screenshot 截图(启用 Worker)
|
|
7226
|
+
*
|
|
7227
|
+
* 优势:
|
|
7228
|
+
* - 使用 Worker,不阻塞主线程 UI
|
|
7229
|
+
* - 支持并发处理
|
|
7230
|
+
* - 适合复杂页面
|
|
7231
|
+
*
|
|
7232
|
+
* 劣势:
|
|
7233
|
+
* - 处理 SVG 和本地资源较慢(Worker 通信开销)
|
|
7234
|
+
* - 配置相对复杂
|
|
7235
|
+
* - 需要处理 Worker URL
|
|
7236
|
+
*
|
|
7237
|
+
* 适用场景:
|
|
7238
|
+
* - 复杂页面,需要不阻塞 UI
|
|
7239
|
+
* - 需要高质量截图
|
|
7240
|
+
* - 页面资源较少
|
|
7065
7241
|
*/
|
|
7066
7242
|
async takeScreenshotWithModernScreenshot(element) {
|
|
7067
7243
|
if (!this.options.silentMode) {
|
|
@@ -7094,13 +7270,19 @@ class ScreenshotManager {
|
|
|
7094
7270
|
}
|
|
7095
7271
|
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
|
7096
7272
|
const isLowEndDevice = navigator.hardwareConcurrency && navigator.hardwareConcurrency <= 4;
|
|
7097
|
-
|
|
7098
|
-
//
|
|
7099
|
-
//
|
|
7273
|
+
// 进一步降低质量以减少 base64 大小
|
|
7274
|
+
// 桌面设备:使用配置的质量(默认 0.3)
|
|
7275
|
+
// 移动设备/低端设备:进一步降低到 0.2(最低)
|
|
7276
|
+
const finalQuality = isMobile || isLowEndDevice
|
|
7277
|
+
? Math.max(this.options.quality * 0.65, 0.2) // 移动设备:质量 * 0.65,最低 0.2
|
|
7278
|
+
: this.options.quality; // 桌面设备:使用配置的质量(默认 0.3)
|
|
7279
|
+
// 计算压缩后的尺寸(对所有元素都应用,包括 document.body)
|
|
7280
|
+
// 这样可以避免生成过大的截图,减少 base64 大小
|
|
7100
7281
|
const { width, height } = this.calculateCompressedSize(elementWidth, elementHeight, this.options.maxWidth, this.options.maxHeight);
|
|
7101
|
-
//
|
|
7102
|
-
|
|
7103
|
-
const
|
|
7282
|
+
// 对于所有元素都应用尺寸限制(包括 body),避免截图过大
|
|
7283
|
+
// 如果计算后的尺寸小于元素实际尺寸,使用压缩尺寸;否则使用元素实际尺寸(但不超过最大值)
|
|
7284
|
+
const finalWidth = width < elementWidth ? width : Math.min(elementWidth, this.options.maxWidth);
|
|
7285
|
+
const finalHeight = height < elementHeight ? height : Math.min(elementHeight, this.options.maxHeight);
|
|
7104
7286
|
// 处理跨域图片的函数
|
|
7105
7287
|
const handleCrossOriginImage = async (url) => {
|
|
7106
7288
|
// 如果是 data URL 或 blob URL,直接返回
|
|
@@ -7199,9 +7381,64 @@ class ScreenshotManager {
|
|
|
7199
7381
|
return url;
|
|
7200
7382
|
}
|
|
7201
7383
|
}
|
|
7202
|
-
//
|
|
7384
|
+
// 如果没有配置代理,需要添加内存保护机制
|
|
7385
|
+
// 不使用代理时,modern-screenshot 会直接下载图片,可能导致内存问题
|
|
7386
|
+
// 由于已配置 CORS,可以直接下载并检查大小
|
|
7203
7387
|
if (this.options.enableCORS) {
|
|
7204
|
-
|
|
7388
|
+
// 对于不使用代理的情况,添加内存保护和缓存机制:
|
|
7389
|
+
// 1. 先检查内存缓存(避免重复下载)
|
|
7390
|
+
// 2. 检查 IndexedDB 缓存
|
|
7391
|
+
// 3. 使用下载队列避免并发重复下载
|
|
7392
|
+
// 4. 下载时检查大小,如果过大则使用占位符
|
|
7393
|
+
// 先检查内存缓存(优先使用缓存,避免重复下载)
|
|
7394
|
+
const cachedDataUrl = this.getCachedImage(url);
|
|
7395
|
+
if (cachedDataUrl) {
|
|
7396
|
+
if (!this.options.silentMode) {
|
|
7397
|
+
console.log(`📸 ✅ 使用内存缓存图片(无代理模式): ${url.substring(0, 50)}...`);
|
|
7398
|
+
}
|
|
7399
|
+
return cachedDataUrl;
|
|
7400
|
+
}
|
|
7401
|
+
// 检查 IndexedDB 缓存(如果启用)
|
|
7402
|
+
if (this.options.useIndexedDB) {
|
|
7403
|
+
const indexedDBCache = await this.getIndexedDBCache(url);
|
|
7404
|
+
if (indexedDBCache) {
|
|
7405
|
+
// 同步到内存缓存
|
|
7406
|
+
this.setCachedImage(url, indexedDBCache);
|
|
7407
|
+
if (!this.options.silentMode) {
|
|
7408
|
+
console.log(`📸 ✅ 使用 IndexedDB 缓存图片(无代理模式): ${url.substring(0, 50)}...`);
|
|
7409
|
+
}
|
|
7410
|
+
return indexedDBCache;
|
|
7411
|
+
}
|
|
7412
|
+
}
|
|
7413
|
+
// 检查是否正在下载(避免重复下载)
|
|
7414
|
+
if (this.imageDownloadQueue.has(url)) {
|
|
7415
|
+
// 如果已经在下载队列中,等待现有下载完成
|
|
7416
|
+
if (!this.options.silentMode) {
|
|
7417
|
+
console.log(`📸 ⏳ 等待图片下载完成: ${url.substring(0, 50)}...`);
|
|
7418
|
+
}
|
|
7419
|
+
return await this.imageDownloadQueue.get(url);
|
|
7420
|
+
}
|
|
7421
|
+
// 检查并发下载数限制
|
|
7422
|
+
if (this.activeDownloads.size >= this.maxConcurrentImageDownloads) {
|
|
7423
|
+
// 并发数已满,返回原 URL,让 modern-screenshot 自己处理(可能会失败,但不阻塞)
|
|
7424
|
+
if (!this.options.silentMode) {
|
|
7425
|
+
console.warn(`📸 ⚠️ 并发下载数已满(${this.activeDownloads.size}/${this.maxConcurrentImageDownloads}),跳过: ${url.substring(0, 50)}...`);
|
|
7426
|
+
}
|
|
7427
|
+
return url;
|
|
7428
|
+
}
|
|
7429
|
+
// 创建下载 Promise 并加入队列
|
|
7430
|
+
const downloadPromise = this.downloadImageWithoutProxy(url);
|
|
7431
|
+
this.imageDownloadQueue.set(url, downloadPromise);
|
|
7432
|
+
this.activeDownloads.add(url);
|
|
7433
|
+
try {
|
|
7434
|
+
const result = await downloadPromise;
|
|
7435
|
+
return result;
|
|
7436
|
+
}
|
|
7437
|
+
finally {
|
|
7438
|
+
// 下载完成后清理
|
|
7439
|
+
this.imageDownloadQueue.delete(url);
|
|
7440
|
+
this.activeDownloads.delete(url);
|
|
7441
|
+
}
|
|
7205
7442
|
}
|
|
7206
7443
|
// 默认返回原 URL
|
|
7207
7444
|
return url;
|
|
@@ -7222,11 +7459,14 @@ class ScreenshotManager {
|
|
|
7222
7459
|
}
|
|
7223
7460
|
this.screenshotContext = null;
|
|
7224
7461
|
}
|
|
7462
|
+
// Worker 数量配置:移动设备/低端设备使用 1 个 Worker,桌面设备使用 2 个
|
|
7463
|
+
// workerNumber > 0 会启用 Worker 模式,截图处理在后台线程执行,不会阻塞主线程 UI
|
|
7225
7464
|
const workerNumber = isMobile || isLowEndDevice ? 1 : 2;
|
|
7226
7465
|
// 构建 createContext 配置
|
|
7466
|
+
// 参考: https://github.com/qq15725/modern-screenshot/blob/main/src/options.ts
|
|
7227
7467
|
const contextOptions = {
|
|
7228
|
-
workerNumber,
|
|
7229
|
-
quality:
|
|
7468
|
+
workerNumber, // Worker 数量,> 0 启用 Worker 模式
|
|
7469
|
+
quality: finalQuality, // 图片质量(0-1),已优化为更低的值以减少 base64 大小
|
|
7230
7470
|
fetchFn: handleCrossOriginImage, // 使用代理服务器处理跨域图片
|
|
7231
7471
|
fetch: {
|
|
7232
7472
|
requestInit: {
|
|
@@ -7234,35 +7474,45 @@ class ScreenshotManager {
|
|
|
7234
7474
|
},
|
|
7235
7475
|
bypassingCache: true,
|
|
7236
7476
|
},
|
|
7477
|
+
// 设置最大 canvas 尺寸,防止生成过大的 canvas(避免内存问题)
|
|
7478
|
+
// 参考: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/canvas#maximum_canvas_size
|
|
7479
|
+
// 大多数浏览器限制为 16,777,216 像素(4096x4096),这里设置为更保守的值
|
|
7480
|
+
maximumCanvasSize: 16777216, // 16M 像素(约 4096x4096)
|
|
7237
7481
|
};
|
|
7238
|
-
//
|
|
7239
|
-
//
|
|
7240
|
-
if (
|
|
7241
|
-
|
|
7242
|
-
|
|
7243
|
-
|
|
7244
|
-
if (
|
|
7245
|
-
console.log(`📸
|
|
7482
|
+
// 对所有元素都设置尺寸限制(包括 document.body),避免截图过大
|
|
7483
|
+
// 这样可以减少 base64 大小,提高性能
|
|
7484
|
+
if (finalWidth && finalHeight) {
|
|
7485
|
+
contextOptions.width = finalWidth;
|
|
7486
|
+
contextOptions.height = finalHeight;
|
|
7487
|
+
if (!this.options.silentMode) {
|
|
7488
|
+
if (element === document.body || element === document.documentElement) {
|
|
7489
|
+
console.log(`📸 截取完整页面(document.body),使用压缩尺寸: ${finalWidth}x${finalHeight}`);
|
|
7246
7490
|
}
|
|
7247
|
-
|
|
7248
|
-
|
|
7249
|
-
if (!this.options.silentMode) {
|
|
7250
|
-
console.log(`📸 使用元素实际尺寸: ${elementWidth}x${elementHeight}`);
|
|
7491
|
+
else {
|
|
7492
|
+
console.log(`📸 使用压缩尺寸: ${finalWidth}x${finalHeight}`);
|
|
7251
7493
|
}
|
|
7252
7494
|
}
|
|
7253
7495
|
}
|
|
7254
7496
|
else {
|
|
7255
|
-
// 对于 body,不设置尺寸限制,让 modern-screenshot 自动截取完整页面
|
|
7256
7497
|
if (!this.options.silentMode) {
|
|
7257
|
-
console.log(`📸
|
|
7498
|
+
console.log(`📸 使用元素实际尺寸: ${elementWidth}x${elementHeight}`);
|
|
7258
7499
|
}
|
|
7259
7500
|
}
|
|
7260
|
-
//
|
|
7501
|
+
// 缩放配置:移动设备使用更低的缩放比例,进一步减少图片大小
|
|
7502
|
+
// scale < 1 会降低图片分辨率,减少 base64 大小
|
|
7261
7503
|
if (this.options.scale !== 1) {
|
|
7262
|
-
contextOptions.scale = isMobile ? 0.
|
|
7504
|
+
contextOptions.scale = isMobile ? 0.7 : this.options.scale; // 移动设备:0.8 -> 0.7
|
|
7505
|
+
}
|
|
7506
|
+
else if (isMobile) {
|
|
7507
|
+
// 如果未指定 scale,移动设备默认使用 0.7
|
|
7508
|
+
contextOptions.scale = 0.7;
|
|
7263
7509
|
}
|
|
7264
|
-
// modern-screenshot 会自动处理 worker URL,不需要手动设置
|
|
7510
|
+
// modern-screenshot 会自动处理 worker URL,不需要手动设置 workerUrl
|
|
7511
|
+
// 当 workerNumber > 0 时,截图处理会在 Worker 线程中执行,不会阻塞主线程 UI
|
|
7265
7512
|
// 创建 Worker 上下文(每次截图都创建新的,确保元素状态最新)
|
|
7513
|
+
if (!this.options.silentMode) {
|
|
7514
|
+
console.log(`📸 Worker 模式: ${workerNumber} 个 Worker,质量: ${finalQuality.toFixed(2)},缩放: ${contextOptions.scale || 1}`);
|
|
7515
|
+
}
|
|
7266
7516
|
this.screenshotContext = await createContext$1(element, contextOptions);
|
|
7267
7517
|
try {
|
|
7268
7518
|
// 使用 Worker 上下文进行截图
|
|
@@ -7291,7 +7541,8 @@ class ScreenshotManager {
|
|
|
7291
7541
|
img.src = dataUrl;
|
|
7292
7542
|
});
|
|
7293
7543
|
let mimeType = 'image/jpeg';
|
|
7294
|
-
|
|
7544
|
+
// 使用与 createContext 相同的质量设置
|
|
7545
|
+
let conversionQuality = finalQuality;
|
|
7295
7546
|
if (this.options.outputFormat === 'webp' && !isMobile) {
|
|
7296
7547
|
try {
|
|
7297
7548
|
const testCanvas = document.createElement('canvas');
|
|
@@ -7306,9 +7557,10 @@ class ScreenshotManager {
|
|
|
7306
7557
|
mimeType = 'image/jpeg';
|
|
7307
7558
|
}
|
|
7308
7559
|
}
|
|
7560
|
+
// 使用优化后的质量进行格式转换
|
|
7309
7561
|
const convertedDataUrl = mimeType === 'image/png'
|
|
7310
7562
|
? canvas.toDataURL(mimeType)
|
|
7311
|
-
: canvas.toDataURL(mimeType,
|
|
7563
|
+
: canvas.toDataURL(mimeType, conversionQuality);
|
|
7312
7564
|
return convertedDataUrl;
|
|
7313
7565
|
}
|
|
7314
7566
|
return dataUrl;
|
|
@@ -7784,6 +8036,87 @@ class ScreenshotManager {
|
|
|
7784
8036
|
}
|
|
7785
8037
|
return dataUrl;
|
|
7786
8038
|
}
|
|
8039
|
+
/**
|
|
8040
|
+
* 不使用代理时下载图片(带内存保护)
|
|
8041
|
+
*/
|
|
8042
|
+
async downloadImageWithoutProxy(url) {
|
|
8043
|
+
try {
|
|
8044
|
+
// 直接下载图片并检查大小
|
|
8045
|
+
const controller = new AbortController();
|
|
8046
|
+
const timeoutId = setTimeout(() => controller.abort(), this.options.imageLoadTimeout || 5000);
|
|
8047
|
+
const response = await fetch(url, {
|
|
8048
|
+
method: 'GET',
|
|
8049
|
+
mode: 'cors',
|
|
8050
|
+
credentials: 'omit',
|
|
8051
|
+
cache: 'no-cache',
|
|
8052
|
+
signal: controller.signal
|
|
8053
|
+
}).catch(() => null).finally(() => {
|
|
8054
|
+
clearTimeout(timeoutId);
|
|
8055
|
+
});
|
|
8056
|
+
if (response && response.ok) {
|
|
8057
|
+
// 检查 Content-Length(如果可用)
|
|
8058
|
+
const contentLength = response.headers.get('content-length');
|
|
8059
|
+
if (contentLength) {
|
|
8060
|
+
const sizeMB = parseInt(contentLength, 10) / (1024 * 1024);
|
|
8061
|
+
const maxSizeMB = this.options.maxImageSize || 5;
|
|
8062
|
+
if (sizeMB > maxSizeMB) {
|
|
8063
|
+
if (this.options.skipLargeImages) {
|
|
8064
|
+
// 跳过过大的图片,返回占位符
|
|
8065
|
+
if (!this.options.silentMode) {
|
|
8066
|
+
console.warn(`📸 ⚠️ 跳过过大图片(${sizeMB.toFixed(2)}MB > ${maxSizeMB}MB): ${url.substring(0, 100)}...`);
|
|
8067
|
+
}
|
|
8068
|
+
// 返回一个 1x1 的透明占位符,避免截图失败
|
|
8069
|
+
return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==';
|
|
8070
|
+
}
|
|
8071
|
+
else {
|
|
8072
|
+
// 不跳过,但添加警告
|
|
8073
|
+
if (!this.options.silentMode) {
|
|
8074
|
+
console.warn(`📸 ⚠️ 图片较大(${sizeMB.toFixed(2)}MB),可能导致内存问题: ${url.substring(0, 100)}...`);
|
|
8075
|
+
}
|
|
8076
|
+
}
|
|
8077
|
+
}
|
|
8078
|
+
}
|
|
8079
|
+
// 如果大小检查通过或无法获取大小,下载图片并转换为 data URL
|
|
8080
|
+
// 但为了进一步保护内存,在下载 blob 后也检查实际大小
|
|
8081
|
+
const blob = await response.blob();
|
|
8082
|
+
const blobSizeMB = blob.size / (1024 * 1024);
|
|
8083
|
+
const maxSizeMB = this.options.maxImageSize || 5;
|
|
8084
|
+
if (blobSizeMB > maxSizeMB) {
|
|
8085
|
+
if (this.options.skipLargeImages) {
|
|
8086
|
+
// 跳过过大的图片,返回占位符
|
|
8087
|
+
if (!this.options.silentMode) {
|
|
8088
|
+
console.warn(`📸 ⚠️ 跳过过大图片(实际大小 ${blobSizeMB.toFixed(2)}MB > ${maxSizeMB}MB): ${url.substring(0, 100)}...`);
|
|
8089
|
+
}
|
|
8090
|
+
return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==';
|
|
8091
|
+
}
|
|
8092
|
+
else {
|
|
8093
|
+
// 不跳过,但添加警告
|
|
8094
|
+
if (!this.options.silentMode) {
|
|
8095
|
+
console.warn(`📸 ⚠️ 图片较大(实际大小 ${blobSizeMB.toFixed(2)}MB),可能导致内存问题: ${url.substring(0, 100)}...`);
|
|
8096
|
+
}
|
|
8097
|
+
}
|
|
8098
|
+
}
|
|
8099
|
+
// 转换为 data URL
|
|
8100
|
+
const dataUrl = await this.blobToDataUrl(blob);
|
|
8101
|
+
// 缓存结果(带时间戳,10分钟有效)
|
|
8102
|
+
this.setCachedImage(url, dataUrl);
|
|
8103
|
+
// 如果启用 IndexedDB,也保存到 IndexedDB
|
|
8104
|
+
if (this.options.useIndexedDB) {
|
|
8105
|
+
await this.setIndexedDBCache(url, dataUrl);
|
|
8106
|
+
}
|
|
8107
|
+
return dataUrl;
|
|
8108
|
+
}
|
|
8109
|
+
// 如果下载失败,返回原 URL,让 modern-screenshot 自己处理
|
|
8110
|
+
return url;
|
|
8111
|
+
}
|
|
8112
|
+
catch (error) {
|
|
8113
|
+
// 下载失败,返回原 URL,让 modern-screenshot 自己处理
|
|
8114
|
+
if (!this.options.silentMode) {
|
|
8115
|
+
console.warn(`📸 ⚠️ 下载图片失败: ${url.substring(0, 100)}...`, error);
|
|
8116
|
+
}
|
|
8117
|
+
return url;
|
|
8118
|
+
}
|
|
8119
|
+
}
|
|
7787
8120
|
/**
|
|
7788
8121
|
* 将 blob 转换为 data URL
|
|
7789
8122
|
*/
|