Merakam tangkapan skrin halaman web secara pengaturcaraan boleh menjadi sangat berguna untuk menjana pratonton, mencipta laporan berasaskan imej dan banyak lagi. Dalam panduan ini, kami akan membina laluan API Next.js yang mengambil URL dan menjana tangkapan skrin PNG. Persediaan kami menggunakan Puppeteer dan chrome-aws-lambda untuk memanfaatkan penyemak imbas Chrome tanpa kepala, menjadikannya serba boleh dan sedia pengeluaran.
Kami akan bermula dengan menyediakan projek Next.js baharu dan berjalan melalui kod langkah demi langkah untuk memahami cara API menangkap tangkapan skrin.
Prasyarat
Bermula dengan Projek Next.js Baharu
npx create-next-app@latest capture-image-app cd capture-image-app
npm install puppeteer puppeteer-core chrome-aws-lambda busboy
Langkah 2: Cipta Laluan API untuk Menjana Tangkapan Skrin
Sekarang, kami akan menyediakan titik akhir API untuk menangkap dan mengembalikan tangkapan skrin berdasarkan URL yang disediakan.
Dalam folder pages/api, buat fail baharu bernama generate-png.ts dan tambah kod ini:
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"); } }
*Penjelasan: Memilih Puppeteer untuk Persekitaran Tempatan lwn. Pengeluaran
*
Dalam kod ini, kami telah menyediakan import dinamik untuk dalang:
Pembangunan Tempatan: Jika NODE_ENV bukan pengeluaran, ia menggunakan puppeteer, yang lebih mudah untuk disediakan dan tidak memerlukan chrome-aws-lambda.
Pengeluaran: Untuk penggunaan tanpa pelayan, persekitaran akan mengesan NODE_ENV sebagai pengeluaran dan memuatkan teras dalang bersama-sama dengan chrome-aws-lambda, yang membolehkannya berfungsi dalam AWS Lambda dan persekitaran lain yang serupa. Dalam persediaan ini, chrome-aws-lambda menyediakan laluan Chromium yang betul, memastikan keserasian dengan pembekal tanpa pelayan.
Langkah 3: Buat Komponen Reaksi Mudah untuk UI
Di sini, kami akan membuat borang mudah yang membolehkan pengguna memasukkan nilai untuk tangkapan halaman web. Borang ini akan mencetuskan fungsi jana untuk menangkap dan memuat turun tangkapan skrin dalam 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> ); }
Kesimpulan
Tutorial ini merangkumi penyediaan alat tangkapan halaman web dalam Next.js, mengendalikan tangkapan skrin dengan Puppeteer dan mencipta komponen bahagian hadapan interaktif. Ingat untuk menggunakan dalang secara tempatan dan beralih kepada teras dalang dalam pengeluaran untuk mengurangkan saiz berkas dan mengoptimumkan untuk persekitaran tanpa pelayan. Selamat mengekod!
Atas ialah kandungan terperinci Cara Menangkap Tangkapan Skrin Halaman Web dengan Next.js dan Puppeteer. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!