SnapshotJS is a full stack Vue.js webapp that allows users to take screenshots of any websites: above the fold or full page.
It can take pictures of most lazy loaded sites and SPA (single page application) sites (e.g. React.js, Vue.js, etc.)
The problem with most website screenshot webapps out there is they don't handle lazy loaded sites or SPA sites very well which
inspired me to make this tool. Plus, it is convenient for me to have since I need to use it whenever I want to make screenshots
for my portfolio.
SnapshotJS is also available
for download as open source software on my GitHub repository. You can check it out by clicking the button below.
I created the entire SnapshotJS full stack webapp using Vue.js on the frontend with a Node.js and Express.js setup on the backend.
There were a few challenges that I faced while making SnapshotJS:
From my past experience making a web crawler/scraper using Node.js, I recall stumbling on a library which I didn't use for that project that would be a perfect fit for this one, and that is the Google Puppeteer library. What Puppeteer does is it allows developers to do everything you can do with a normal browser, specifically Google Chrome. It is considered as a "headless" browser. On top of that, it also comes with the ability to take screenshots of almost any websites! However, that doesn't mean it will solve all the challenges I faced. Which leads me to the next challenge.
Luckily, because Google Puppeteer comes with full page screenshot support, this made it easy to take care of this project's requirement. All I had to do was toggle it via a boolean value inside the screenshot() function which I received from a checkbox state from the frontend via a fullPage variable.
await page.screenshot({ path: screenshotFileName + fileFormat, fullPage: fullPage });
However, lazy loaded sites like latimes.com wouldn't load images in properly for full page screenshots, which leads me to the next challenge. It did load the content in and everything all the way up to the footer. Lazy loaded websites are sites that only load certain pieces of content only once they become visible within the viewport (aka once the user scrolls that section within viewing distance). A simple 30 second delay didn't solve the issue either.
await page.waitFor(30000);
This leads me to the next challenge.
To solve this issue, I decided to fake the activity of a user scrolling by using the window.scrollBy() function. Because the scrollBy() function takes in a parameter for height in pixels, I had to get the height of the entire webpage. I obtained this value by calling the document.body.scrollHeight property. To handle infinite scrolling sites, the total timeout I gave to this webapp helps create a "stopping point" so it helps avoid a infinite scroll state. Moving on, because images are large files, and large files takes time to load, I added a delay via the await page.waitFor() function. However, I didn't pass in the scrollHeight directly into the scrollBy() function because of this very reason. So to allow for the images to load in properly, I divided the scrollHeight by the viewport's height which gives me a number which represents the amount of scroll increments I need it to do per delay. Once it reaches the bottom, everything should be loaded in properly and ready for the screenshot. To play it safe, I added an additional amount of delay to take care of any unforeseen scenarios. Then I have the program call the Google Puppeteer's screenshot() function which generates a image file of the screenshot. Perfect!
if(fullPage) {
await page.evaluate((viewHeight) => {
let screenHeight = document.documentElement.clientHeight;
let scrollHeight = document.body.scrollHeight;
let screenHeightScrollIncrements = Math.ceil(scrollHeight / screenHeight);
var tempScrolledAmount = screenHeight;
(function() {
var counter = 0;
var myVar = setInterval(myTimer, 3000);
function myTimer() {
window.scrollBy(0, screenHeight);
counter++;
if(counter > screenHeightScrollIncrements) {
clearInterval(myVar);
}
}
})();
});
}
One of the things I wasn't so proud of for this project was my frontend UI design. I wanted to keep it within the size of the viewport without scrolling, but it seems like it wasn't the best approach. What I should've done was let the screenshot preview section be below the inputs so that both divs fill up the entire screen's width. Not only that, I should add some kind of loading indicator when the user presses the screenshot button.
Interested in working with me? Let's have a quick chat, and see how we can make your project come to life. :)