Maison >interface Web >js tutoriel >Comment capturer des captures d'écran de pages Web avec Next.js et Puppeteer
La capture de captures d'écran de pages Web par programmation peut être incroyablement utile pour générer des aperçus, créer des rapports basés sur des images, et bien plus encore. Dans ce guide, nous allons créer une route API Next.js qui prend une URL et génère une capture d'écran PNG. Notre configuration utilise Puppeteer et chrome-aws-lambda pour exploiter un navigateur Chrome sans tête, ce qui le rend polyvalent et prêt pour la production.
Nous allons commencer par configurer un nouveau projet Next.js et parcourir le code étape par étape pour comprendre comment l'API capture des captures d'écran.
Prérequis
Démarrer avec un nouveau projet Next.js
npx create-next-app@latest capture-image-app cd capture-image-app
npm install puppeteer puppeteer-core chrome-aws-lambda busboy
Étape 2 : Créer la route API pour générer des captures d'écran
Maintenant, nous allons configurer un point de terminaison d'API pour capturer et renvoyer des captures d'écran en fonction d'une URL fournie.
Dans le dossier pages/api, créez un nouveau fichier nommé generate-png.ts et ajoutez ce code :
import { NextApiRequest, NextApiResponse } from "next"; import busboy, { Busboy } from "busboy"; // Use busboy for multipart parsing import chromium from "chrome-aws-lambda"; import puppeteerCore from "puppeteer-core"; // Import puppeteer-core directly import puppeteer from "puppeteer"; // Import puppeteer directly // Conditional import for Puppeteer based on the environment const puppeteerModule = process.env.NODE_ENV === "production" ? puppeteerCore : puppeteer; export const config = { api: { bodyParser: false, // Disable default body parsing to handle raw binary data (Blob) }, }; const delay = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms)); export default async function handler( req: NextApiRequest, res: NextApiResponse ): Promise<void> { try { if (req.method === "POST") { const bb: Busboy = busboy({ headers: req.headers }); let width: number = 1920; // Default width let height: number = 0; // Default height let delayTime: number = 6000; const buffers: Buffer[] = []; bb.on("file", (_name: string, file: NodeJS.ReadableStream) => { file.on("data", (data: Buffer) => buffers.push(data)); }); bb.on("field", (name: string, value: string) => { if (name === "width") width = parseInt(value, 10) || 1920; if (name === "height") height = parseInt(value, 10) || 0; if (name === "delay") delayTime = parseInt(value, 10) || 6000; }); bb.on("finish", async () => { const blobBuffer: Buffer = Buffer.concat(buffers); const htmlContent: string = blobBuffer.toString("utf-8"); const browser = await puppeteerModule.launch({ args: ["--start-maximized"], executablePath: process.env.NODE_ENV === "production" ? await chromium.executablePath || "/usr/bin/chromium-browser" : undefined, // No custom executable path needed for local headless: true, }); const page = await browser.newPage(); // Load the HTML content directly await page.setContent(htmlContent, { waitUntil: "networkidle0" }); //@ts-expect-error todo const bodyHeight = await page.evaluate(() => { return document.body.scrollHeight; // Get the full scrollable height of the body }); await page.setViewport({ width: Number(width), height: height || bodyHeight, // Use the provided height or fallback to the full body height deviceScaleFactor: 2, }); await delay(delayTime); const screenshotBuffer = await page.screenshot({ fullPage: !height, type: "png", omitBackground: false, }); await browser.close(); res.setHeader("Content-Type", "image/png"); res.setHeader( "Content-Disposition", "attachment; filename=screenshot.png" ); res.status(200).end(screenshotBuffer); }); req.pipe(bb); // Pipe the request stream to busboy } else { res.setHeader("Allow", ["POST"]); res.status(405).end(`Method ${req.method} Not Allowed`); } } catch (error) { console.error("ERROR", error); res.status(500).end("Internal Server Error"); } }
*Explication : Choisir Marionnettiste pour les environnements locaux ou de production
*
Dans ce code, nous avons mis en place un import dynamique pour le marionnettiste :
Développement local : si NODE_ENV n'est pas en production, il utilise Puppeteer, qui est plus simple à mettre en place et ne nécessite pas chrome-aws-lambda.
Production : pour les déploiements sans serveur, l'environnement détectera NODE_ENV comme production et chargera Puppeteer-core avec chrome-aws-lambda, ce qui lui permettra de fonctionner dans AWS Lambda et d'autres environnements similaires. Dans cette configuration, chrome-aws-lambda fournit le chemin Chromium correct, garantissant la compatibilité avec les fournisseurs sans serveur.
Étape 3 : Créer un composant React simple pour l'interface utilisateur
Ici, nous allons créer un formulaire simple qui permet aux utilisateurs de saisir des valeurs pour la capture de la page Web. Ce formulaire déclenchera la fonction de génération pour capturer et télécharger la capture d'écran au format PDF.
import { useState } from "react"; export default function ScreenCaptureComponent() { const [isProcessing, setProcessing] = useState(false); const [width, setWidth] = useState<string>("1920"); const [height, setHeight] = useState<string>("1000"); const [delay, setDelay] = useState<string>("6000"); // Function to clone HTML and prepare for capture function takeScreenshot() { const clonedElement = document.body.cloneNode(true) as HTMLElement; const blob = new Blob([clonedElement.outerHTML], { type: "text/html" }); return blob; } // Function to capture screenshot by sending cloned HTML to API async function generateCapture() { setProcessing(true); const htmlBlob = takeScreenshot(); if (!htmlBlob) { setProcessing(false); return; } try { const formData = new FormData(); formData.append("file", htmlBlob); formData.append("width", width); formData.append("height", height); formData.append("delay", delay); const response = await fetch("/api/generate-png", { method: "POST", body: formData, }); if (!response.ok) throw new Error("Capture failed"); const blob = await response.blob(); const downloadUrl = URL.createObjectURL(blob); const link = document.createElement("a"); link.href = downloadUrl; link.download = "capture.png"; link.click(); URL.revokeObjectURL(downloadUrl); } catch (error) { console.error("Failed to capture screenshot", error); } finally { setProcessing(false); } } return ( <div style={{ maxWidth: "400px", margin: "50px auto", padding: "24px", backgroundColor: "white", borderRadius: "8px", width: "100%", boxShadow: "0 4px 6px rgba(0, 0, 0, 0.1)", }} > <h2 style={{ fontSize: "24px", fontWeight: "600", textAlign: "center", marginBottom: "16px", }} > Webpage Screenshot Capture </h2> <form onSubmit={(e) => { e.preventDefault(); generateCapture(); }} style={{ display: "flex", flexDirection: "column", alignItems: "center", marginBottom: "16px", }} > <label style={{ marginBottom: "8px", fontWeight: "500" }} htmlFor="width" > Width (px) </label> <select id="width" value={width} onChange={(e) => setWidth(e.target.value)} style={{ width: "100%", padding: "8px", marginBottom: "16px", borderRadius: "4px", border: "1px solid #ccc", outline: "none", }} > <option value="1920">1920 (Full HD)</option> <option value="1366">1366 (Laptop)</option> <option value="1280">1280 (Desktop)</option> <option value="1024">1024 (Tablet Landscape)</option> <option value="768">768 (Tablet Portrait)</option> <option value="375">375 (Mobile)</option> </select> <label style={{ marginBottom: "8px", fontWeight: "500" }} htmlFor="height" > Height (px) </label> <input type="number" id="height" value={height} onChange={(e) => setHeight(e.target.value)} required style={{ width: "100%", padding: "8px", marginBottom: "16px", borderRadius: "4px", border: "1px solid #ccc", outline: "none", }} /> <label style={{ marginBottom: "8px", fontWeight: "500" }} htmlFor="delay" > Delay (ms) </label> <input type="number" id="delay" value={delay} onChange={(e) => setDelay(e.target.value)} required style={{ width: "100%", padding: "8px", marginBottom: "16px", borderRadius: "4px", border: "1px solid #ccc", outline: "none", }} /> <button type="submit" disabled={isProcessing} style={{ padding: "8px 16px", color: "white", borderRadius: "4px", transition: "background-color 0.3s", backgroundColor: isProcessing ? "#b0bec5" : "#2196F3", cursor: isProcessing ? "not-allowed" : "pointer", }} > {isProcessing ? "Capturing..." : "Capture Screenshot"} </button> </form> {/* Example HTML Element to Capture */} <div id="capture-area" style={{ display: "none" }}> <h3 style={{ fontSize: "20px", fontWeight: "600", }} > Content to Capture </h3> <p>This is an example of the HTML content that will be captured.</p> </div> </div> ); }
Conclusion
Ce tutoriel couvre la configuration d'un outil de capture de page Web dans Next.js, la gestion des captures d'écran avec Puppeteer et la création d'un composant frontal interactif. N'oubliez pas d'utiliser Puppeteer localement et de passer à Puppeteer-Core en production pour réduire la taille du bundle et optimiser les environnements sans serveur. Bon codage !
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!