Capturing DOM Elements’ Screenshots: Server Side VS. Client Side Approaches

Daniel Sternlicht
5 min readDec 24, 2017

--

A few weeks ago, I wrote about Chatilyzer — a new side project I’ve been working on in past month.

A quick overview about the app and the app’s flow: Chatilyzer allows you to get statistics about your WhatsApp chats. In order the app to work, you need to upload an exported *.txt file of a WhatsApp chat. Chatilyzer is parsing and analyzing the text, and extracting some interesting data from it. Then, you’re being redirected to a results page where you see a nice visualization of your chat’s activity.

An example of Chatilyzer results page

One of the challenges I had while developing this web app was to allow users to create a screenshot of the results page, and download it so they could share it in their group — rather than share a url to the page.

Server Side (NodeJS) Approach

I started with the server side approach, using PhantomJS and a NPM module called “Node Webshot”. It has a very simple api that allows you to create screenshots from a given url:

import webshot from 'webshot';

webshot('https://chatilyzer.com', 'chat.png', function(err) {
// screenshot now saved to chat.png
});

After creating the screenshot, I needed to upload it to a cloud storage, and send back the url of the image to the client side so I could allow the user to download the image.

Pros:

  • A ready-to-use module.
  • Screenshot is being saved in the cloud so it’s available any time.
  • Flexible api which allows you to set the desired width and height of the screenshot.

Cons:

  • All or nothing — the screenshot captures all page, and you can’t control a specific element you want to capture.
  • Rendering — If you need to wait for async JS to load before capturing the screenshot, you might run into problems.
  • Environment — Given that you’re not in a real browser environment, PhantomJS doesn’t handle custom / Google fonts as proper, which leads to a default font that’s being rendered in your screenshot.
  • Storage — After creating the screenshot, you must save it in a cloud storage in order to return a valid image url to the user. Meaning, you need a cloud storage provider which potentially could cost you more money.
  • Security — If the screenshot’s data is sensitive, saving a copy in the cloud isn’t a good approach.

EDIT: After getting some feedback from the community, it turns out there’s another server side solution using a NPM module called “Puppeteer” which runs on top of headless Chrome. Here’s how to create a screenshot with Puppeteer:

const puppeteer = require('puppeteer');

(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://chatilyzer.com');
await page.screenshot({path: 'chatilyzer.png'});

await browser.close();
})();

This module would solve the All or nothing, rendering, and environment issues, as it provides the same render engine as Chrome.

Client Side Approach

I didn’t find the server side approach’s result good enough for my use case so I started to explore other options.

I found an extremely cool JS library called “html2canvas”, which as the name suggest, will turn an HTML element into a canvas element.

html2canvas website

As you can see, it’s also very easy to use. Basically you’re calling the html2canvas method, and passing a DOM element as an argument. This method returns a promise that you may use to extract a canvas element.

html2canvas(document.querySelector("#capture")).then(canvasElm => {
document.body.appendChild(canvasElm);
});

Next, you want to enable your user to download the screenshot.

The way to do it is by using the JS toDataURL method as you may see below:

// Get a base64 data string
var imageType = 'image/png';
var imageData = canvasElm.toDataURL(imageType);
// Open the data string in the current window
document.location.href = imageData.replace(imageType, 'image/octet-stream');

There’s another JS library called “canvas2image”. I recommend using it rather writing it by yourself as it provides more flexibility and also allows you to generate an image tag out of the canvas element. So you may get the same result by using:

Canvas2Image.saveAsPNG(canvasElm, width, height);

Pros:

  • WYSIWYG — The result is 1:1 of what you’re seeing on the screen.
  • No server side — All running in the browser.
  • Security — If the screenshot’s data is sensitive, capturing it on client side in much more secure as the server is not aware of the results, and not saving a copy elsewhere.

Cons:

  • Compatibility — html2canvas is not being supported in all browsers
  • Expensive — Although html2canvas returns a promise, it’s still an expensive action, and for big and complicated components, it could takes a few seconds before getting results.
  • Narrow View — If you’re using a narrow screen (in mobile for example), the screenshot will be narrow as well as it’s capturing the screenshot from the actual view (that could be solved by either passing a fix width as an argument to canvas2html, or by opening an iframe with the desired width in the background, capture the screenshot there, and send back the image data with postMessage).
  • Elements Support — Not all html elements are supported in html2canvas. For example, other canvas elements, iframes, and flash, won’t be rendered at all.

In Conclusion

There are two approaches of how to capture a screenshot from html elements, and you should think what suits your app needs.

The server side approach is the one you choose when you need a cross browser / platform experience, and not sure your users will have the right browser in order to capture the screenshot. The price you’ll need to pay is in both physical payment for the cloud storage service, and the inaccurate results.

The client side approach is good when you sure your users will have a browser that is capable to capture the screenshot (or you’re ready to pay the price for those who aren’t), and when accuracy is a must for the screenshot.

Quick thought — I guess the best approach will be to combine between them. You may check if the user’s browser compatibility have the support you need in order to capture a screenshot, and if not, fallback to the server side approach.

Hope you enjoyed the article. Clap it you liked it :)

Do you know about other ways to create screenshots of web pages with NodeJS? Share in comments!

--

--

Daniel Sternlicht

Frontend & Web technologies addict, founder of Common Ninja and There is a bot for that. Check out my personal website at http://danielsternlicht.com :)