Fixing the potato
In my last post about screenshotting a node with JavaScript I got as far as a fairly low quality image. Since then I’ve figured out how to fix it.
The first part was when I discovered the toDataURL()
method can take a second paramater. I already had the type
in, but the second one is called encoderOptions
and is a number between 0 and 1 that represents the quality of the final image. Setting that to 1 helped a little, but mainly doubled weight of the resulting image.
On top of that, and there’s not much difference between the two but I found JPEG to be marginally better than the original PNG.
So now instead of newImage.src = canvas.toDataURL('image/png');
it’s newImage.src = canvas.toDataURL('image/jpeg', 1.0);
.
The real key to the whole thing was in 3 parts:
- Make the
canvas
element twice the width and twice the height of the node in question. - Make the SVG the same size as the node.
- Draw the SVG onto the
canvas
at the size of the canvas.
The new code is:
const card = document.getElementById('card');
const css = document.querySelector('style');
const screenshot = document.getElementById('screenshot');
// This function takes any DOMDocumentElement as a first parameter.
// And return a Data URL of its generated image.
async function renderElement(element) {
// Get the dimensions of the element
const dimensions = element.getBoundingClientRect();
// The canvas does not need to exist in document body
const canvas = document.createElement('canvas');
// The canvas is adjusted here
canvas.width = dimensions.width * 2;
canvas.height = dimensions.height * 2;
const ctx = canvas.getContext('2d');
// SVG scaffolding with CSS
const imageSVG = `
<svg xmlns="http://www.w3.org/2000/svg" width="${canvas.width / 2}" height="${canvas.height / 2}">
<style>* { font-family: sans-serif } span.weight { color: #fd0d96 }</style> ${css.outerHTML}
<foreignObject width="100%" height="100%">
<div xmlns="http://www.w3.org/1999/xhtml">
${element.outerHTML}
</div>
</foreignObject>
</svg>`;
// The image also does not need to exist in document body
const imageElement = document.createElement('img');
const newImage = document.createElement('img');
// The svg image source is already converted to Data URL (Base64)
imageElement.src = `data:image/svg+xml;base64,${btoa(imageSVG)}`;
imageElement.addEventListener('load', () => {
ctx.drawImage(imageElement, 0, 0, canvas.width, canvas.height);
newImage.src = canvas.toDataURL('image/jpeg', 1.0);
newImage.addEventListener('load', () => {
newImage.width = newImage.naturalWidth;
newImage.height = newImage.naturalHeight;
// Update the screenshot link
screenshot.href = newImage.src;
});
});
}
renderElement(card);
The results
Comparing the images the difference is noticeable.
One final note, the updated code has fixed an issue where there was some space left over at the bottom, and the screenshot is the exact size of the original node.