visit
Author:
Today we want to share our experience of implementing the backend tool for the generation of graphical banners from HTML templates. ’s customer was interested in automation of advertisement banners customisation based on the prepared templates. Basically, you have an index.html with fields where you can insert different components e.g. buttons, images or text. So I will tell about approaches we’ve used to implement such functionality. For our project we chose PhantomJS. You may argue that there are more innovative solutions in the npm repository. But when we started, none of these libraries was stable or well documented yet (, for example, — a Node.js library that provides a high-level API to control headless Chromium-based engines; among its most notable features — awesome capabilities for taking screenshots). The project’s specs required a great deal of stability. That’s why we decided to employ the tried and well-documented PhantomJS. The task was to create a function for taking screenshots with headless Chrome as described on Medium in the by .
Ok, enough with introductions. Let’s have a closer look at the framework we’ve decided to use. () is a headless WebKit browser scriptable with a JavaScript API.
A list of features according to creators is as follows:Next, we must set the properties to our page — size of a page using viewportSize
property and clipRect
to define coordinates of the coordinates of the rectangle to render:
Also there it is a possibility of evaluating JavaScript code in the context of the web page. For this, evaluate()
function is used, which accepts another function as an argument. We used evalFunction
to make changes in templates before rendering to images.
Thus, basic functionality for rendering an image from the template is ready and actually, this is already a working example. If you call the generate()
method passing to it the page sizes and config object with keys templatePath (location of HTML template which you want to render into an image) and destinationPath (location of the output image file), this will create the image in the JPEG format. Further, we will extend this example with additional options.
Method page.open()
returns the status of the operation. A good practice is checking if opening the page ended is success
and throw an exception otherwise. This will allow handling situations when Phantom hasn’t been able to open a template.
To control what is happening during page rendering in the headless mode, I’ve added logging to Phantom page instance using onConsoleMessage
property to output the page messages to the terminal console. This peace can be added to our _createPage()
method of ImageGenerator
class.
CSS styles are needed only for the positioning and sizing of textual blocks. All files related to the template are located in ‘static/templates’ directory. When you’ve finished preparing the template, it’s time to extend the class example with evaluate
function and add any JS scripts you might additionally require to the template. Keep in mind that any JavaScript code in the template would be evaluated and executed by Phantom, which doesn’t recognize any of the ES6-specific features (e.g. no string templating and only concatenation). I personally find it not fun. Also, Phantom does not support some CSS properties which are widely used in modern browsers. For example, if you are keen on using Flexbox, you can forget about it when prepare templates - while rendering them with Phantom, all the unsupported features will be ignored or even cause an error.
I think the easiest way to customise page texts in eval function is using getElementById
and then replacing innerHtml
in selected node or changing src
property in the img
tag. Also, I’ve declared this function not in the imageGenerator
class itself but right before calling the generate()
method. This provides the flexibility to pass different functions in different cases. For example, you can pass such function to page.evaluate()
:
As you may already notice, you also should standardise the format of your templates and content IDs because they would be used by the evaluation function. That’s why better to keep naming conventions consistent across all templates so as not to write different evaluate functions for each of them. The second argument of page.evaluate()
is used to pass parameters to evaluateFunc
:
In the following example, 96 is the level of compression, size
is a multiplication of height and width of the output image (known value), and x
is the level of compression we seek:
Next, we extended imageGenerator
with the method for calculating the compression level depending on the picture dimensions:
In the end, we employed another approach, still rather straightforward but working. If the content exceeds the size limit, we start iterating image rendering decrementing quality step by step and returning the found quality value from generate
method:
In the left picture, you can notice the pixel grain, which becomes even more notable when zooming.
That’s why despite the image dimension limits, we also included the possibility to generate bigger pictures but with the same file size restriction. It was possible because some of the pictures did not reach size limit at all in their normal dimensions. By the way, with Phantom, it could be done simply. For this, zoomFactor
property is set, which specifies how many times the image should be scaled:
To make this optional, we passed the flag to switch the zooming mode on to arguments of generate
method of ImageGenerator
class:
Originally published at .