CatPtain commited on
Commit
21da54e
·
verified ·
1 Parent(s): 08a6186

Upload screenshotService.js

Browse files
backend/src/services/screenshotService.js CHANGED
@@ -1,19 +1,15 @@
1
  import puppeteer from 'puppeteer';
2
 
3
  class ScreenshotService {
4
- async generateScreenshot(htmlContent, options = {}) {
5
- let browser = null;
6
- let pptDimensions = { width: 1000, height: 750 }; // 默认值
7
-
8
- try {
9
- console.log('Starting Puppeteer browser...');
10
-
11
- // 提取PPT的实际尺寸和长宽比
12
- pptDimensions = this.extractPptDimensions(htmlContent);
13
- console.log('Extracted PPT dimensions:', pptDimensions);
14
-
15
- // 更适合容器环境的Puppeteer配置
16
- browser = await puppeteer.launch({
17
  headless: 'new',
18
  args: [
19
  '--no-sandbox',
@@ -22,333 +18,406 @@ class ScreenshotService {
22
  '--disable-accelerated-2d-canvas',
23
  '--no-first-run',
24
  '--no-zygote',
 
25
  '--disable-gpu',
26
  '--disable-background-timer-throttling',
27
  '--disable-backgrounding-occluded-windows',
28
  '--disable-renderer-backgrounding',
29
- '--disable-web-security',
30
  '--disable-features=TranslateUI',
31
  '--disable-extensions',
32
- '--disable-component-extensions-with-background-pages',
33
- '--disable-default-apps',
34
  '--mute-audio',
35
  '--no-default-browser-check',
36
- '--autoplay-policy=user-gesture-required',
37
- '--disable-background-mode',
38
- '--disable-plugins',
39
- '--disable-translate',
40
- '--disable-ipc-flooding-protection',
41
- '--memory-pressure-off',
42
- '--max_old_space_size=4096'
43
- ],
44
- timeout: 30000,
45
- protocolTimeout: 30000
46
- });
47
-
48
- console.log('Browser launched successfully');
49
-
50
- const page = await browser.newPage();
51
- console.log('New page created');
52
-
53
- // 设置页面视窗大小为PPT的精确尺寸
54
- await page.setViewport({
55
- width: pptDimensions.width,
56
- height: pptDimensions.height,
57
- deviceScaleFactor: 2 // 高分辨率
58
- });
59
-
60
- console.log(`Viewport set to exact PPT size: ${pptDimensions.width}x${pptDimensions.height}`);
61
-
62
- // 修改HTML内容,确保PPT容器精确匹配视窗尺寸,消除白边
63
- const modifiedHtmlContent = this.modifyHtmlForScreenshot(htmlContent, pptDimensions);
64
-
65
- // 设置HTML内容
66
- await page.setContent(modifiedHtmlContent, {
67
- waitUntil: 'domcontentloaded',
68
- timeout: 10000
69
- });
70
-
71
- console.log('HTML content set');
72
-
73
- // 等待渲染完成
74
- await page.waitForTimeout(1500);
75
-
76
- // 验证页面中的PPT容器尺寸是否精确匹配
77
- const actualDimensions = await page.evaluate(() => {
78
- const container = document.getElementById('slideContainer');
79
- const body = document.body;
80
- const html = document.documentElement;
81
-
82
- if (container) {
83
- const rect = container.getBoundingClientRect();
84
- return {
85
- container: {
86
- width: rect.width,
87
- height: rect.height,
88
- left: rect.left,
89
- top: rect.top
90
- },
91
- body: {
92
- width: body.offsetWidth,
93
- height: body.offsetHeight
94
- },
95
- html: {
96
- width: html.offsetWidth,
97
- height: html.offsetHeight
98
- },
99
- viewport: {
100
- width: window.innerWidth,
101
- height: window.innerHeight
102
- }
103
- };
104
- }
105
- return null;
106
- });
107
-
108
- console.log('Page dimensions verification:', actualDimensions);
109
-
110
- // 确保页面尺寸完全匹配PPT尺寸
111
- if (actualDimensions && (
112
- Math.abs(actualDimensions.viewport.width - pptDimensions.width) > 1 ||
113
- Math.abs(actualDimensions.viewport.height - pptDimensions.height) > 1
114
- )) {
115
- console.warn('Page dimensions do not match PPT dimensions exactly, adjusting...');
116
- await page.setViewport({
117
- width: pptDimensions.width,
118
- height: pptDimensions.height,
119
- deviceScaleFactor: 2
120
- });
121
- await page.waitForTimeout(500);
122
- }
123
-
124
- console.log('Taking precise screenshot...');
125
-
126
- // 截取精确的PPT内容区域,不包含任何白边
127
- const screenshot = await page.screenshot({
128
- type: 'jpeg',
129
- quality: 95,
130
- // 使用clip精确截取PPT内容区域
131
- clip: {
132
- x: 0,
133
- y: 0,
134
- width: pptDimensions.width,
135
- height: pptDimensions.height
136
- },
137
- timeout: 10000
138
  });
139
-
140
- console.log('Screenshot taken successfully, size:', screenshot.length);
141
-
142
- return screenshot;
143
- } catch (error) {
144
- console.error('Screenshot generation error:', error);
145
-
146
- // 返回一个错误图片而不是抛出异常
147
- return this.generateErrorImage(error.message, pptDimensions);
148
- } finally {
149
- if (browser) {
150
- try {
151
- await browser.close();
152
- console.log('Browser closed');
153
- } catch (closeError) {
154
- console.error('Error closing browser:', closeError);
155
- }
156
- }
157
  }
 
158
  }
159
-
160
- // 从HTML内容中提取PPT尺寸信息和长宽比
161
- extractPptDimensions(htmlContent) {
162
- // 方法1:从JavaScript变量中提取尺寸 - 这是最准确的方法
163
- const dimensionsMatch = htmlContent.match(/window\.PPT_DIMENSIONS\s*=\s*{\s*width:\s*(\d+),\s*height:\s*(\d+)\s*}/);
164
- if (dimensionsMatch) {
165
- const width = parseInt(dimensionsMatch[1]);
166
- const height = parseInt(dimensionsMatch[2]);
167
- console.log('Extracted dimensions from JavaScript:', { width, height });
168
- return { width, height };
169
- }
170
-
171
- // 方法2:从CSS样式中提取尺寸
172
- const cssMatch = htmlContent.match(/\.slide-container\s*{\s*[^}]*width:\s*(\d+)px;[^}]*height:\s*(\d+)px;/s);
173
- if (cssMatch) {
174
- const width = parseInt(cssMatch[1]);
175
- const height = parseInt(cssMatch[2]);
176
- console.log('Extracted dimensions from CSS:', { width, height });
177
- return { width, height };
178
- }
179
-
180
- // 方法3:从内联样式中提取尺寸
181
- const inlineMatch = htmlContent.match(/width:\s*(\d+)px[^;]*;\s*height:\s*(\d+)px/);
182
- if (inlineMatch) {
183
- const width = parseInt(inlineMatch[1]);
184
- const height = parseInt(inlineMatch[2]);
185
- console.log('Extracted dimensions from inline styles:', { width, height });
186
- return { width, height };
187
  }
188
-
189
- // 默认尺寸 - 使用标准PPT 16:9 比例
190
- console.warn('Could not extract PPT dimensions from HTML, using default 16:9 ratio');
191
- return { width: 1280, height: 720 }; // 16:9 标准比例
192
  }
193
-
194
- // 修改HTML内容以适应截图需求,确保零白边
195
- modifyHtmlForScreenshot(htmlContent, pptDimensions) {
196
- const { width, height } = pptDimensions;
197
-
198
- // 创建完全匹配PPT尺寸的HTML,消除所有可能的白边
199
- let modifiedHtml = htmlContent;
200
-
201
- // 设置严格的CSS样式,确保页面尺寸完全匹配PPT尺寸
202
- modifiedHtml = modifiedHtml.replace(
203
- /<style>[\s\S]*?<\/style>/,
204
- `<style>
205
- * {
206
- margin: 0 !important;
207
- padding: 0 !important;
208
- box-sizing: border-box !important;
209
- border: none !important;
210
- }
211
-
212
- html {
213
- width: ${width}px !important;
214
- height: ${height}px !important;
215
- overflow: hidden !important;
216
- background: transparent !important;
217
- min-width: ${width}px !important;
218
- min-height: ${height}px !important;
219
- max-width: ${width}px !important;
220
- max-height: ${height}px !important;
221
- }
222
-
223
- body {
224
- width: ${width}px !important;
225
- height: ${height}px !important;
226
- overflow: hidden !important;
227
- font-family: 'Microsoft YaHei', Arial, sans-serif !important;
228
- background: transparent !important;
229
- position: absolute !important;
230
- top: 0 !important;
231
- left: 0 !important;
232
- margin: 0 !important;
233
- padding: 0 !important;
234
- min-width: ${width}px !important;
235
- min-height: ${height}px !important;
236
- max-width: ${width}px !important;
237
- max-height: ${height}px !important;
238
- }
239
-
240
- .slide-container {
241
- width: ${width}px !important;
242
- height: ${height}px !important;
243
- position: absolute !important;
244
- top: 0 !important;
245
- left: 0 !important;
246
- margin: 0 !important;
247
- padding: 0 !important;
248
- overflow: hidden !important;
249
- background-color: var(--slide-bg-color, #ffffff) !important;
250
- transform: none !important;
251
- transform-origin: top left !important;
252
- min-width: ${width}px !important;
253
- min-height: ${height}px !important;
254
- max-width: ${width}px !important;
255
- max-height: ${height}px !important;
256
- }
257
-
258
- /* 确保所有元素都相对于slide-container定位 */
259
- .slide-container > * {
260
- position: absolute !important;
261
- }
262
-
263
- /* 确保背景图片也严格匹配尺寸 */
264
- .slide-container::before {
265
- width: ${width}px !important;
266
- height: ${height}px !important;
267
- top: 0 !important;
268
- left: 0 !important;
269
- margin: 0 !important;
270
- padding: 0 !important;
271
- }
272
- </style>`
273
- );
274
 
275
- // 移除所有可能影响尺寸的JavaScript,确保固定尺寸
276
- modifiedHtml = modifiedHtml.replace(
277
- /<script>[\s\S]*?<\/script>/g,
278
- `<script>
279
- // 截图模式 - 固定尺寸,消除白边
280
- console.log('Screenshot mode - exact dimensions: ${width}x${height}');
281
-
282
- // 确保页面加载完成后设置精确的尺寸
283
- window.addEventListener('load', function() {
284
- // 设置容器精确尺寸
285
- const container = document.getElementById('slideContainer');
286
- if (container) {
287
- container.style.cssText = \`
288
- width: ${width}px !important;
289
- height: ${height}px !important;
290
- position: absolute !important;
291
- top: 0 !important;
292
- left: 0 !important;
293
- margin: 0 !important;
294
- padding: 0 !important;
295
- transform: none !important;
296
- overflow: hidden !important;
297
- \`;
298
  }
299
 
300
- // 设置body和html精确尺寸
301
- document.body.style.cssText = \`
302
- width: ${width}px !important;
303
- height: ${height}px !important;
 
 
 
 
 
 
 
 
304
  margin: 0 !important;
305
  padding: 0 !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
306
  overflow: hidden !important;
307
- position: absolute !important;
308
  top: 0 !important;
309
  left: 0 !important;
310
- \`;
311
-
312
- document.documentElement.style.cssText = \`
313
- width: ${width}px !important;
314
- height: ${height}px !important;
315
  margin: 0 !important;
316
  padding: 0 !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
317
  overflow: hidden !important;
318
- \`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
319
 
320
- console.log('Exact dimensions applied for screenshot');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
321
  });
322
 
323
- // 禁用所有可能的缩放和调整
324
- window.addEventListener('resize', function(e) {
325
- e.preventDefault();
326
- e.stopPropagation();
327
  });
328
- </script>`
329
- );
330
-
331
- return modifiedHtml;
332
- }
333
-
334
- // 生成错误提示图片 - 使用动态尺寸
335
- generateErrorImage(errorMessage = '截图生成失败', dimensions = { width: 1280, height: 720 }) {
336
- const { width, height } = dimensions;
337
-
338
- const canvas = `
339
- <svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg">
340
- <rect width="${width}" height="${height}" fill="#f8f9fa"/>
341
- <rect x="50" y="50" width="${width - 100}" height="${height - 100}" fill="white" stroke="#dee2e6" stroke-width="2"/>
342
- <text x="${width / 2}" y="${height / 2 - 20}" text-anchor="middle" font-family="Arial" font-size="24" fill="#6c757d">
343
- 截图生成失败
344
- </text>
345
- <text x="${width / 2}" y="${height / 2 + 20}" text-anchor="middle" font-family="Arial" font-size="16" fill="#868e96">
346
- ${errorMessage}
347
- </text>
348
- </svg>
349
- `;
350
-
351
- return Buffer.from(canvas);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
352
  }
353
  }
354
 
 
1
  import puppeteer from 'puppeteer';
2
 
3
  class ScreenshotService {
4
+ constructor() {
5
+ this.browser = null;
6
+ this.isInitialized = false;
7
+ }
8
+
9
+ async initBrowser() {
10
+ if (!this.browser) {
11
+ console.log('初始化Puppeteer浏览器...');
12
+ this.browser = await puppeteer.launch({
 
 
 
 
13
  headless: 'new',
14
  args: [
15
  '--no-sandbox',
 
18
  '--disable-accelerated-2d-canvas',
19
  '--no-first-run',
20
  '--no-zygote',
21
+ '--single-process',
22
  '--disable-gpu',
23
  '--disable-background-timer-throttling',
24
  '--disable-backgrounding-occluded-windows',
25
  '--disable-renderer-backgrounding',
 
26
  '--disable-features=TranslateUI',
27
  '--disable-extensions',
28
+ '--hide-scrollbars',
 
29
  '--mute-audio',
30
  '--no-default-browser-check',
31
+ '--disable-default-apps',
32
+ '--disable-background-networking',
33
+ '--disable-sync',
34
+ '--metrics-recording-only',
35
+ '--disable-domain-reliability',
36
+ '--disable-component-extensions-with-background-pages',
37
+ '--force-device-scale-factor=1',
38
+ '--enable-precise-memory-info'
39
+ ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  });
41
+ console.log('Puppeteer浏览器初始化完成');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  }
43
+ return this.browser;
44
  }
45
+
46
+ async closeBrowser() {
47
+ if (this.browser) {
48
+ await this.browser.close();
49
+ this.browser = null;
50
+ console.log('Puppeteer浏览器已关闭');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  }
 
 
 
 
52
  }
53
+
54
+ async modifyHtmlForScreenshot(htmlContent, targetWidth, targetHeight) {
55
+ console.log(`开始修改HTML for截图, 目标尺寸: ${targetWidth}x${targetHeight}`);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
 
57
+ // 创建完全针对截图优化的HTML版本
58
+ const optimizedHtml = htmlContent.replace(
59
+ /<head>/i,
60
+ `<head>
61
+ <meta charset="UTF-8">
62
+ <meta name="viewport" content="width=${targetWidth}, height=${targetHeight}, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no, viewport-fit=cover">
63
+ <!-- 截图模式标识 -->
64
+ <meta name="screenshot-mode" content="true">
65
+ <!-- 强制精确尺寸控制 -->
66
+ <style id="screenshot-optimization">
67
+ /* 全局重置 - 完全消除边距 */
68
+ *, *::before, *::after {
69
+ margin: 0 !important;
70
+ padding: 0 !important;
71
+ box-sizing: border-box !important;
72
+ border: none !important;
73
+ outline: none !important;
 
 
 
 
 
 
74
  }
75
 
76
+ /* 根元素强制设置 */
77
+ html {
78
+ width: ${targetWidth}px !important;
79
+ height: ${targetHeight}px !important;
80
+ min-width: ${targetWidth}px !important;
81
+ min-height: ${targetHeight}px !important;
82
+ max-width: ${targetWidth}px !important;
83
+ max-height: ${targetHeight}px !important;
84
+ overflow: hidden !important;
85
+ position: fixed !important;
86
+ top: 0 !important;
87
+ left: 0 !important;
88
  margin: 0 !important;
89
  padding: 0 !important;
90
+ border: none !important;
91
+ outline: none !important;
92
+ transform: none !important;
93
+ transform-origin: top left !important;
94
+ }
95
+
96
+ /* Body元素强制设置 */
97
+ body {
98
+ width: ${targetWidth}px !important;
99
+ height: ${targetHeight}px !important;
100
+ min-width: ${targetWidth}px !important;
101
+ min-height: ${targetHeight}px !important;
102
+ max-width: ${targetWidth}px !important;
103
+ max-height: ${targetHeight}px !important;
104
  overflow: hidden !important;
105
+ position: fixed !important;
106
  top: 0 !important;
107
  left: 0 !important;
 
 
 
 
 
108
  margin: 0 !important;
109
  padding: 0 !important;
110
+ border: none !important;
111
+ outline: none !important;
112
+ transform: none !important;
113
+ transform-origin: top left !important;
114
+ }
115
+
116
+ /* 幻灯片容器强制设置 */
117
+ .slide-container {
118
+ width: ${targetWidth}px !important;
119
+ height: ${targetHeight}px !important;
120
+ min-width: ${targetWidth}px !important;
121
+ min-height: ${targetHeight}px !important;
122
+ max-width: ${targetWidth}px !important;
123
+ max-height: ${targetHeight}px !important;
124
+ position: fixed !important;
125
+ top: 0 !important;
126
+ left: 0 !important;
127
  overflow: hidden !important;
128
+ transform: none !important;
129
+ transform-origin: top left !important;
130
+ margin: 0 !important;
131
+ padding: 0 !important;
132
+ border: none !important;
133
+ outline: none !important;
134
+ box-shadow: none !important;
135
+ z-index: 1 !important;
136
+ }
137
+
138
+ /* 隐藏所有滚动条 */
139
+ html::-webkit-scrollbar,
140
+ body::-webkit-scrollbar,
141
+ *::-webkit-scrollbar {
142
+ display: none !important;
143
+ width: 0 !important;
144
+ height: 0 !important;
145
+ }
146
+
147
+ /* Firefox */
148
+ html {
149
+ scrollbar-width: none !important;
150
+ }
151
+
152
+ /* 禁用用户交互 */
153
+ * {
154
+ -webkit-user-select: none !important;
155
+ -moz-user-select: none !important;
156
+ -ms-user-select: none !important;
157
+ user-select: none !important;
158
+ -webkit-user-drag: none !important;
159
+ -khtml-user-drag: none !important;
160
+ -moz-user-drag: none !important;
161
+ -o-user-drag: none !important;
162
+ user-drag: none !important;
163
+ pointer-events: none !important;
164
+ }
165
+
166
+ /* 移除响应式样式 */
167
+ .browse-mode * {
168
+ display: none !important;
169
+ }
170
+ </style>`
171
+ );
172
+
173
+ // 注入截图专用JavaScript
174
+ const finalHtml = optimizedHtml.replace(
175
+ /<\/body>/i,
176
+ `
177
+ <script id="screenshot-finalizer">
178
+ // 截图模式最终设置
179
+ window.SCREENSHOT_EXACT_MODE = true;
180
+ window.PPT_TARGET_WIDTH = ${targetWidth};
181
+ window.PPT_TARGET_HEIGHT = ${targetHeight};
182
+
183
+ // 立即执行尺寸强制设置
184
+ (function() {
185
+ const exactWidth = ${targetWidth};
186
+ const exactHeight = ${targetHeight};
187
+
188
+ console.log('截图模式强制设置:', exactWidth + 'x' + exactHeight);
189
+
190
+ // 强制设置根元素
191
+ const html = document.documentElement;
192
+ const body = document.body;
193
+
194
+ html.style.cssText = \`
195
+ width: \${exactWidth}px !important;
196
+ height: \${exactHeight}px !important;
197
+ min-width: \${exactWidth}px !important;
198
+ min-height: \${exactHeight}px !important;
199
+ max-width: \${exactWidth}px !important;
200
+ max-height: \${exactHeight}px !important;
201
+ overflow: hidden !important;
202
+ margin: 0 !important;
203
+ padding: 0 !important;
204
+ position: fixed !important;
205
+ top: 0 !important;
206
+ left: 0 !important;
207
+ border: none !important;
208
+ outline: none !important;
209
+ transform: none !important;
210
+ transform-origin: top left !important;
211
+ \`;
212
+
213
+ body.style.cssText = \`
214
+ width: \${exactWidth}px !important;
215
+ height: \${exactHeight}px !important;
216
+ min-width: \${exactWidth}px !important;
217
+ min-height: \${exactHeight}px !important;
218
+ max-width: \${exactWidth}px !important;
219
+ max-height: \${exactHeight}px !important;
220
+ overflow: hidden !important;
221
+ margin: 0 !important;
222
+ padding: 0 !important;
223
+ position: fixed !important;
224
+ top: 0 !important;
225
+ left: 0 !important;
226
+ border: none !important;
227
+ outline: none !important;
228
+ transform: none !important;
229
+ transform-origin: top left !important;
230
+ \`;
231
+
232
+ // 设置容器
233
+ const container = document.querySelector('.slide-container');
234
+ if (container) {
235
+ container.style.cssText = \`
236
+ width: \${exactWidth}px !important;
237
+ height: \${exactHeight}px !important;
238
+ min-width: \${exactWidth}px !important;
239
+ min-height: \${exactHeight}px !important;
240
+ max-width: \${exactWidth}px !important;
241
+ max-height: \${exactHeight}px !important;
242
+ position: fixed !important;
243
+ top: 0 !important;
244
+ left: 0 !important;
245
+ overflow: hidden !important;
246
+ margin: 0 !important;
247
+ padding: 0 !important;
248
+ border: none !important;
249
+ outline: none !important;
250
+ transform: none !important;
251
+ box-shadow: none !important;
252
+ z-index: 1 !important;
253
+ \`;
254
+ }
255
+
256
+ // 移除响应式类
257
+ html.classList.remove('browse-mode');
258
+ body.classList.remove('browse-mode');
259
+
260
+ console.log('截图模式设置完成');
261
+ })();
262
 
263
+ // DOM加载完成后再次确认
264
+ document.addEventListener('DOMContentLoaded', function() {
265
+ console.log('DOM加载完成,最终确认尺寸设置');
266
+
267
+ // 再次强制设置,确保完全生效
268
+ const html = document.documentElement;
269
+ const body = document.body;
270
+ const container = document.querySelector('.slide-container');
271
+
272
+ [html, body, container].forEach(element => {
273
+ if (element) {
274
+ element.style.width = '${targetWidth}px';
275
+ element.style.height = '${targetHeight}px';
276
+ element.style.minWidth = '${targetWidth}px';
277
+ element.style.minHeight = '${targetHeight}px';
278
+ element.style.maxWidth = '${targetWidth}px';
279
+ element.style.maxHeight = '${targetHeight}px';
280
+ element.style.margin = '0';
281
+ element.style.padding = '0';
282
+ element.style.border = 'none';
283
+ element.style.outline = 'none';
284
+ element.style.overflow = 'hidden';
285
+
286
+ if (element !== container) {
287
+ element.style.position = 'fixed';
288
+ element.style.top = '0';
289
+ element.style.left = '0';
290
+ }
291
+ }
292
+ });
293
+
294
+ // 最终验证
295
+ setTimeout(() => {
296
+ console.log('最终尺寸验证:', {
297
+ html: html.offsetWidth + 'x' + html.offsetHeight,
298
+ body: body.offsetWidth + 'x' + body.offsetHeight,
299
+ container: container ? container.offsetWidth + 'x' + container.offsetHeight : 'none',
300
+ target: '${targetWidth}x${targetHeight}'
301
+ });
302
+ }, 100);
303
+ });
304
+ </script>
305
+ </body>`
306
+ );
307
+
308
+ console.log('HTML修改完成,已注入截图优化代码');
309
+ return finalHtml;
310
+ }
311
+
312
+ async captureScreenshot(htmlContent, width, height, options = {}) {
313
+ try {
314
+ await this.initBrowser();
315
+
316
+ console.log(`开始截图, 目标尺寸: ${width}x${height}`);
317
+
318
+ // 修改HTML内容以适应截图
319
+ const optimizedHtml = await this.modifyHtmlForScreenshot(htmlContent, width, height);
320
+
321
+ // 创建新页面
322
+ const page = await this.browser.newPage();
323
+
324
+ try {
325
+ // 设置精确的viewport尺寸
326
+ await page.setViewport({
327
+ width: width,
328
+ height: height,
329
+ deviceScaleFactor: options.deviceScaleFactor || 2, // 高清截图
330
  });
331
 
332
+ // 设置页面内容
333
+ await page.setContent(optimizedHtml, {
334
+ waitUntil: ['load', 'domcontentloaded', 'networkidle0'],
335
+ timeout: 30000
336
  });
337
+
338
+ // 等待页面完全渲染
339
+ await page.waitForTimeout(2000);
340
+
341
+ // 执行最终的尺寸验证和调整
342
+ await page.evaluate((targetWidth, targetHeight) => {
343
+ const html = document.documentElement;
344
+ const body = document.body;
345
+ const container = document.querySelector('.slide-container');
346
+
347
+ // 最终强制设置
348
+ [html, body].forEach(element => {
349
+ if (element) {
350
+ element.style.width = targetWidth + 'px';
351
+ element.style.height = targetHeight + 'px';
352
+ element.style.minWidth = targetWidth + 'px';
353
+ element.style.minHeight = targetHeight + 'px';
354
+ element.style.maxWidth = targetWidth + 'px';
355
+ element.style.maxHeight = targetHeight + 'px';
356
+ element.style.margin = '0';
357
+ element.style.padding = '0';
358
+ element.style.border = 'none';
359
+ element.style.outline = 'none';
360
+ element.style.overflow = 'hidden';
361
+ element.style.position = 'fixed';
362
+ element.style.top = '0';
363
+ element.style.left = '0';
364
+ }
365
+ });
366
+
367
+ if (container) {
368
+ container.style.width = targetWidth + 'px';
369
+ container.style.height = targetHeight + 'px';
370
+ container.style.minWidth = targetWidth + 'px';
371
+ container.style.minHeight = targetHeight + 'px';
372
+ container.style.maxWidth = targetWidth + 'px';
373
+ container.style.maxHeight = targetHeight + 'px';
374
+ container.style.position = 'fixed';
375
+ container.style.top = '0';
376
+ container.style.left = '0';
377
+ container.style.margin = '0';
378
+ container.style.padding = '0';
379
+ container.style.border = 'none';
380
+ container.style.outline = 'none';
381
+ container.style.overflow = 'hidden';
382
+ container.style.transform = 'none';
383
+ container.style.boxShadow = 'none';
384
+ }
385
+
386
+ console.log('最终页面尺寸确认:', {
387
+ html: html.offsetWidth + 'x' + html.offsetHeight,
388
+ body: body.offsetWidth + 'x' + body.offsetHeight,
389
+ container: container ? container.offsetWidth + 'x' + container.offsetHeight : 'none'
390
+ });
391
+ }, width, height);
392
+
393
+ // 再次等待以确保所有更改生效
394
+ await page.waitForTimeout(1000);
395
+
396
+ // 执行截图,使用精确的剪裁区域
397
+ const screenshot = await page.screenshot({
398
+ type: options.format || 'png',
399
+ quality: options.quality || 100,
400
+ clip: {
401
+ x: 0,
402
+ y: 0,
403
+ width: width,
404
+ height: height
405
+ },
406
+ omitBackground: false, // 包含背景
407
+ captureBeyondViewport: false, // 不截取视口外内容
408
+ });
409
+
410
+ console.log(`截图完成, 生成了 ${screenshot.length} 字节的图片数据`);
411
+ return screenshot;
412
+
413
+ } finally {
414
+ await page.close();
415
+ }
416
+
417
+ } catch (error) {
418
+ console.error('截图失败:', error);
419
+ throw new Error(`截图失败: ${error.message}`);
420
+ }
421
  }
422
  }
423