Skip to content

Instantly share code, notes, and snippets.

@matsuyama-k1
Last active October 14, 2024 04:16
Show Gist options
  • Save matsuyama-k1/34188bfe4483e46b87e2c4a4716b54c3 to your computer and use it in GitHub Desktop.
Save matsuyama-k1/34188bfe4483e46b87e2c4a4716b54c3 to your computer and use it in GitHub Desktop.
one solution for taking screen shot larger than 16384px with puppeteer. https://github.com/puppeteer/puppeteer/issues/359
import { Page } from "puppeteer";
import sharp from "sharp";
// Max texture size of the software GL backand of chronium. (16384px or 4096px)
// https://issues.chromium.org/issues/41347676
export const MAX_SIZE_PX = 16384;
const takeFullPageScreenshot = async (page: Page) => {
const pageHeight = await getPageHeight(page);
const deviceScaleFactor = page.viewport()?.deviceScaleFactor ?? 1;
const scaledHeight = pageHeight * deviceScaleFactor;
let screenshot: Uint8Array;
if (scaledHeight > MAX_SIZE_PX) {
screenshot = await captureLargeScreenshot(page); // here!
} else {
screenshot = await page.screenshot({
fullPage: true,
});
}
return screenshot;
};
export default takeFullPageScreenshot;
const captureLargeScreenshot = async (page: Page) => {
const viewport = page.viewport()!;
const deviceScaleFactor = viewport.deviceScaleFactor ?? 1;
const width = viewport.width;
const pageHeight = await getPageHeight(page);
const screenshots: Uint8Array[] = [];
const screenshotPromises = [];
let currentYPosition = 0;
const scaledPageHeight = pageHeight * deviceScaleFactor;
while (currentYPosition < scaledPageHeight) {
const clipHeight = Math.min(
Math.floor(MAX_SIZE_PX / deviceScaleFactor),
scaledPageHeight - currentYPosition
);
const screenshotPromise = page.screenshot({
clip: {
x: 0,
y: currentYPosition,
width: width,
height: clipHeight,
},
omitBackground: true,
type: "png",
});
screenshotPromises.push(screenshotPromise);
currentYPosition += clipHeight;
}
const values = await Promise.all(screenshotPromises);
screenshots.push(...values);
try {
const screenshotBuffer = await stitchImages(
width * deviceScaleFactor,
scaledPageHeight,
screenshots
);
const uint8Array = new Uint8Array(screenshotBuffer);
return uint8Array;
} catch (err) {
console.error("Error stitching screenshots:", err);
throw err; // Propagate the error
}
};
const stitchImages = async (
w: number,
h: number,
screenshots: Uint8Array[]
): Promise<Buffer> => {
let currentHeight = 0;
const compositeOperations = [];
for (let i = 0; i < screenshots.length; i++) {
const screenshot = screenshots[i];
try {
const img = sharp(screenshot);
const { height: imgHeight } = await img.metadata();
// Convert Uint8Array to Buffer
const bufferInput = Buffer.from(screenshot);
// Collect composite operations
compositeOperations.push({
input: bufferInput,
top: currentHeight,
left: 0,
});
currentHeight += imgHeight ?? 0;
} catch (err) {
console.error(`Error processing screenshot ${i}:`, err);
throw err;
}
}
const img = sharp({
create: {
width: w,
height: h,
channels: 4,
background: { r: 255, g: 255, b: 255, alpha: 0 },
},
limitInputPixels: h * w,
});
const result = img.composite(compositeOperations);
return await result.png().toBuffer();
};
const getPageHeight = async (page: Page): Promise<number> => {
return await page.evaluate(() => document.documentElement.scrollHeight);
};
@matsuyama-k1
Copy link
Author

matsuyama-k1 commented Oct 3, 2024

example usage.

import fs from "fs";
import puppeteer from "puppeteer";

import takeFullPageScreenshot from "./index";

const url =
  "https://nextjs.org/docs/app/building-your-application/routing/route-handlers";
const width = 1500;
const deviceScaleFactor = 2;

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  await page.goto(url, { waitUntil: "load" });

  await page.setViewport({
    width: width,
    height: 500,
    deviceScaleFactor,
  });

  await new Promise((resolve) => setTimeout(resolve, 500));

  const screenshot = await takeFullPageScreenshot(page); // here!

  const defaultScreenshot = await page.screenshot({
    fullPage: true,
  });

  browser.close();
  fs.writeFileSync("./results/screenshot.png", screenshot);
  fs.writeFileSync("./results/defaultScreenshot.png", defaultScreenshot);
})();

result

369992439-7b7e717f-14d2-4c94-969a-f3b637123429

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment