How to Build a Chat Screenshot Generator with Node.js and Canvas
This guide walks you through creating a Node.js tool that automatically generates chat screenshots with customizable themes, avatars, and text, complete with code snippets, size calculations, drawing functions, and integration into workflow tools like Alfred and macOS Shortcuts for instant clipboard copying.
Why Build This Tool
Inspired by the inconvenience of manually typing and screenshotting chat messages, the author created a "Redundant Generator" to automatically produce chat screenshots from input text.
Generate Screenshot
Initialize Project
We use Node.js to develop the tool. First, create a working directory and initialize an npm project.
mkdir redundant-tools && cd redundant-tools
npm initInstall the canvas package so Node can draw graphics. npm i canvas Note: The canvas package may require a proper gcc environment matching the local Node version; refer to the official documentation if installation fails.
Analyze Screenshot Composition
A screenshot consists of a background, avatar, bubble, and chat text. The background and bubble colors change based on the selected theme (dark/light).
Calculate Height
The final height equals the vertical padding (20 px × 2) plus bubble padding (17 px × 2) plus the number of lines multiplied by the line height (42 px).
const chatLineHeight = 42;
const globalPadding = 20;
const bubblePadding = [17, 26, 24]; // top, right, left
const canvasHeight = lines.length * chatLineHeight + globalPadding * 2 + bubblePadding[0] * 2;Calculate Width
The final width equals the horizontal padding (20 px × 2) plus the text width plus bubble left/right padding, avatar size, and bubble‑avatar margin.
const bubbleRightMargin = 10;
const headSize = 64;
const canvasWidth = Math.ceil(lineWidth) + globalPadding * 2 + paddingLeft + paddingRight + bubbleRightMargin + headSize;Background
Draw a rectangle the same size as the canvas and fill it with the theme background color.
ctx.fillStyle = bgColor;
ctx.fillRect(0, 0, canvasWidth, canvasHeight);Bubble
Instead of using a stretchable .9.png, the bubble is drawn as a rounded rectangle with an arrow image.
// Draw bubble background
ctx.save();
ctx.translate(globalPadding, globalPadding);
drawRoundRectPath(ctx, bubbleWidth, bubbleHeight, 8);
ctx.clip();
ctx.fillStyle = bubbleBgColor;
ctx.fillRect(0, 0, bubbleWidth, bubbleHeight);
ctx.restore();Load the arrow image (base64) and draw it at the calculated position.
const arrowImage = await loadImage(arrowBg);
ctx.drawImage(arrowImage, 0, 0, 10, 16, arrowX, arrowY, 10, 16);Avatar
The avatar URL is taken from an environment variable, loaded with loadImage, and drawn with rounded corners.
const headX = canvasWidth - globalPadding - headSize;
drawImageWidthRadios(ctx, headImage, headX, globalPadding, headSize, headSize, 4);Chat Text
After wrapping the text into lines, each line is drawn inside the bubble.
function drawChatText(ctx, x, y, lines) {
lines.forEach((line, index) => {
const drawY = y + chatLineHeight * index;
ctx.beginPath();
ctx.font = '28px "PingFangSC-Regular", sans-serif, Arial, SF Pro Display';
ctx.fillStyle = '#05110a';
ctx.textBaseline = 'top';
ctx.fillText(line, x, drawY);
ctx.closePath();
});
}
const textStartX = globalPadding + paddingLeft;
const textStartY = globalPadding + paddingTop;
drawChatText(ctx, textStartX, textStartY, lines);Save File
Export the canvas to a PNG file using toBuffer and Node's fs module.
const fs = require('fs');
fs.writeFileSync(`${projectPath}/output.png`, canvas.toBuffer());Convenient Usage
Run the tool from the project root with the avatar URL, project path, chat text, and theme:
headUrl=https://fmcat-images.oss-cn-hangzhou.aliyuncs.com/head.png projectPath=. node index.js "This is dark theme text" darkTo make the workflow smoother, the author integrates the tool with Alfred and macOS Shortcuts.
Alfred Workflow
Set up environment variables ( headUrl, nodePath, projectPath) and create keyword triggers ( dd for dark, dl for light). Use conditional blocks to ensure required variables are set, then run a script that executes the Node command and copies the generated image to the clipboard via AppleScript.
osascript -e 'set the clipboard to (read (POSIX file "{query}") as JPEG picture)'Notify the user when the image is ready.
macOS Shortcuts
Configure a shortcut that asks for the theme and chat text, runs the same Node script, and copies the resulting PNG to the clipboard using the same AppleScript command.
Conclusion
Although the "Redundant Generator" is a playful project, it demonstrates useful techniques: canvas drawing with Node.js, dynamic size calculation, and seamless integration with workflow tools to turn a multi‑step process into a single shortcut.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
BaiPing Technology
Official account of the BaiPing app technology team. Dedicated to enhancing human productivity through technology. | DRINK FOR FUN!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
