Last active
October 14, 2024 04:16
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
example usage.
result