DEV Community

toolzip
toolzip

Posted on Originally published at toolzip.app

Generating Thumbnail Images in the Browser with Canvas API

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

Try it at toolzip.app/tools/thumbnail-maker.

Top comments (0)