Generating Thumbnail Images in the Browser with Canvas API
Blog thumbnail generators are usually server-side — send text, get back an image. I built one that runs entirely in the browser using Canvas. Here's what's actually hard about it.
Korean Web Font Loading
Korean fonts are large (often 3–10MB for a complete character set). Loading them for Canvas requires waiting for them to be ready:
async function loadWebFonts() {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = 'https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@700;900&display=swap';
document.head.appendChild(link);
await document.fonts.ready;
}
document.fonts.ready waits for all pending font loads to complete. Without this, Canvas renders text with the fallback font.
Text Wrapping
Canvas fillText() doesn't wrap. You have to implement it:
function getWrappedLines(ctx, text, maxWidth) {
const words = text.split(' ');
const lines = [];
let current = '';
for (const word of words) {
const test = current ? `${current} ${word}` : word;
if (ctx.measureText(test).width > maxWidth && current) {
lines.push(current);
current = word;
} else {
current = test;
}
}
if (current) lines.push(current);
return lines;
}
Important: measure line count before drawing the background box, not after. Otherwise the text overflows the box.
Blurred Image Background
Taking a user's photo and using it as a blurred background is the most visually impressive feature:
async function drawBlurredBackground(ctx, imageFile, width, height, blur, dimOpacity) {
const img = await loadImage(imageFile);
const scale = Math.max(width / img.width, height / img.height);
const sw = img.width * scale;
const sh = img.height * scale;
const sx = (width - sw) / 2;
const sy = (height - sh) / 2;
ctx.filter = `blur(${blur}px)`;
ctx.drawImage(img, sx, sy, sw, sh);
ctx.filter = 'none';
// Darken overlay
ctx.fillStyle = `rgba(0,0,0,${dimOpacity})`;
ctx.fillRect(0, 0, width, height);
}
High-Resolution Download
The canvas preview is scaled down for display (0.5x). For download, render at full resolution:
function downloadFullRes(previewCanvas, targetWidth, targetHeight, format) {
const out = document.createElement('canvas');
out.width = targetWidth;
out.height = targetHeight;
const ctx = out.getContext('2d');
// Scale up from preview
ctx.scale(targetWidth / previewCanvas.width, targetHeight / previewCanvas.height);
ctx.drawImage(previewCanvas, 0, 0);
ctx.setTransform(1, 0, 0, 1, 0, 0);
out.toBlob(blob => {
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = `thumbnail.${format}`;
a.click();
}, format === 'jpg' ? 'image/jpeg' : 'image/png', 0.95);
}
Try it at toolzip.app/tools/thumbnail-maker.
Top comments (0)