Motivation
It's been four months since I started building a SaaS product, Annotate.site. There were many obstacles; one of them arose when I needed to capture screenshots automatically. I felt pretty confident when I decided to add that feature. However, within just a few hours, my excitement turned into frustration as I couldn't find a perfect solution to my problem.
I tried a lot of alternative but lot of then missed some styles. one approch was to deep clone the dom but there we have to make each reference like to absolute like for each img, stylesheet, any other assest but doing it manually we always miss some edge case. When tring to take screenshot in next js app images are not captured and this list goes on and on.
What i wanted is just a simple screenshot with all styles, fonts, images and the overall layout on point
Ever been in a situation where you're looking for answers on Stack Overflow and it feels like finding a needle in a haystack? Yeah, I've been there too.
I thought I could rely on popular tools like html2canvas and dom-to-image to do the job, and they worked okay in some cases. But here's the kicker—they didn't quite meet my expectations. So, in this blog, I want to take you through the alternatives I explored, tell you what worked and what didn't, and share the solution I eventually came up with using JavaScript, Puppeteer, and rrweb-snapshot.
Why No Default Browser API?
The Inspect menu in Chrome gives us a taste of the power to capture node screenshots. The question remains: Why don't browsers provide this functionality by default?
Let's explore the limitations and possibilities.
Alternative 1: HTML to Canvas
Internal Working:
HTML to Canvas involves rendering the content of an HTML element onto an HTML5 canvas. Libraries like html2canvas simplify this process.
function capture() {
html2canvas(document.getElementById('capture')).then(canvas => {
document.body.appendChild(canvas);
});
}
Advantages:
Dynamic Content Capture.
Client-Side Rendering.
Drawbacks:
Cross-Origin Limitations.
Rendering Accuracy Challenges.
Alternative 2: Dom to Image
Internal Working:
Dom to Image converts a DOM element into an image using the HTML5 canvas.
function capture() {
const node = document.getElementById('capture');
domtoimage.toPng(node)
.then(dataUrl => {
const img = new Image();
img.src = dataUrl;
document.body.appendChild(img);
})
.catch(error => console.error('Error capturing to image:', error));
}
Advantages:
Ease of Use.
Styling Preservation.
Drawbacks:
Security Restrictions.
Performance Concerns.
Paid Screenshot APIs:
Several paid screenshot APIs, like url2png, offer high-resolution captures, customization options, and reliability. Considerations include ease of use, customization, reliability, cost, and dependency on external services.
Example Usage (using curl):curl -o screenshot.png "https://api.url2png.com/v6/PNG/?url=https://www.example.com&viewport=1280x1024&fullpage=true&thumbnail_max_width=250&delay=2000&ttl=3600&api_key=YOUR_API_KEY&private_key=YOUR_PRIVATE_KEY"
Advantages:
Ease of Use.
Customization Options.
Reliability.
Drawbacks:
Cost.
Dependency on External Service.
Using WebRTC’s getDisplayMedia Method:
This approach leverages the JavaScript MediaServices class to capture a screenshot of the page content using the getDisplayMedia method. It allows dynamic content capture, real-time updates, and permission-based access.
const takeScreenshot = async () => {
const captureStream = await navigator.mediaDevices.getDisplayMedia();
// Capture and customization
// ...
captureStream.getTracks().forEach(track => track.stop());
};
takeScreenshot();
Advantages:
Dynamic Content Capture.
Real-time Updates.
Permission-based Access.
Disadvantages:
Browser Compatibility.
User Permission UX.
My Approach: Puppeteer Power Unleashed
Requirements:
Javascript.
Puppeteer.
rrweb-snapshot (https://www.npmjs.com/package/rrweb-snapshot).
Setup:
Step 1: Snapshot the DOM
const snapShot = snapshot(window.document, { /* options */ });
Step 2: Create a New Document
const Document_ = document.implementation.createHTMLDocument("New Document");
Step 3: Rebuild the Document
const rebuilt = rebuild(snapShot, {
doc: Document_,
mirror: createMirror(),
cache: { stylesWithHoverClass: new Map() },
});
Step 4: API Call to Serverless Puppeteer
const data = JSON.stringify({
scrollY: Math.floor(window.scrollY),
width: document.body.clientWidth,
height: document.body.clientHeight,
html: screenShotHtml,
});
const config = { method: "post", url: "<YOUR_API_URL>", headers: { /* headers */ }, data };
const uploadedImg = await axios.request(config);
Step 5: Puppeteer Magic
const browser = await puppeteer.launch({ /* puppeteer options */ });
const page = await browser.newPage();
// Set up the page and take the screenshot
await page.setViewport({ /* viewport options */ });
await page.setContent(body.html, { waitUntil: ["networkidle0"] });
// Simulate scrolling if needed
await page.evaluate(async (scrollY) => {
console.log("Scrolling to bottom of page...", scrollY);
window.scrollBy(0, scrollY);
}, scrollY);
// Capture the screenshot
const screenshotBuffer = await page.screenshot({ type: "png", fullPage: false, captureBeyondViewport: false });
// And you've got the screenshot buffer!
Puppeteer Pitfalls: Unmasking the Disadvantages
But wait, every hero has its kryptonite. What are the disadvantages of using Puppeteer? Share your thoughts!
Conclusion:
Conclusion:
Alright, fellow developers, we've wrapped up our journey through screenshots, APIs, and the power of Puppeteer. May your web adventures be like a smooth sail, with perfect captures and code free of bugs!
Just a quick reminder: in the coding universe, there's always a solution waiting to be discovered. It's like finding the right magic spell. So, keep exploring, keep coding, and may your spells always work like a charm! ✨
Top comments (0)