I’m using Vercel to host an express app that generates a countdown gif based on url params.
It works locally, but when running on Vercel the font just shows squares. (Using Arial font so it should just work right?)
Below is the whole app, and here’s an example url https://countdown-server.vercel.app/countdown?endTime=2024-06-11T00:00&width=600&bgColor=S000000&textColor=%23FFFFFF&numberSize=24&fontSize=16&font=Arial
const express = require('express');
const { GifEncoder } = require('@skyra/gifenc');
const { createCanvas, registerFont } = require('canvas');
const path = require('path');
const app = express();
const frameCount = 60; // 60 = 1 minute
const imageQuality = 10; // 0 = best quality, 10 = worst quality
const drawFrameCount = false; // Display frame number for debugging
const completedString = 'Out Now!'; // String to display when countdown is over
app.get('/countdown', (req, res) => {
console.log('Generating GIF...'); // Log when GIF generation starts
const genStartTime = new Date(); // Capture start time
const countdownEndTime = new Date(req.query.endTime);
// Extract width, background color, text color, and font settings from query parameters
const width = parseInt(req.query.width) || 600; // default width is 600
const backgroundColor = req.query.bgColor || '#000'; // default background color is black
const textColor = req.query.textColor || '#fff'; // default text color is white
const fontSize = req.query.fontSize || '20px'; // default font size is 20px
const numberSize = req.query.numberSize || '20px'; // default number size is 20px
const font = req.query.font || 'Arial'; // default font is Arial
console.log(font);
const height = 200; // height remains constant, modify as needed
const encoder = new GifEncoder(width, height);
encoder.setRepeat(0); // 0 = infinite loop, 1 = play once
encoder.setDelay(1000); // 1 second delay between frames
encoder.setQuality(imageQuality);
res.writeHead(200, { 'Content-Type': 'image/gif' });
encoder.createReadStream().pipe(res);
encoder.start();
const canvas = createCanvas(width, height);
const ctx = canvas.getContext('2d');
for (let i = 0; i <= frameCount; i++) { // Loop through the total frame count
ctx.fillStyle = backgroundColor;
ctx.fillRect(0, 0, width, height);
ctx.fillStyle = textColor;
let frameTime = new Date(new Date().getTime() + i * 1000);
let remainingTime = Math.max(0, countdownEndTime - frameTime);
let seconds = Math.floor(remainingTime / 1000) % 60;
let minutes = Math.floor(remainingTime / 60000) % 60;
let hours = Math.floor(remainingTime / 3600000) % 24;
let days = Math.floor(remainingTime / 86400000);
if (remainingTime > 0) {
// Function to draw centered text
function drawCenteredText(text, quarterIndex, numSize, fontSize, font) {
// Countdown numbers
ctx.font = `${numSize}px ${font}`;
let textWidth = ctx.measureText(text).width;
let quarterWidth = width / 4;
let xPosition = (quarterWidth * (quarterIndex - 1)) + (quarterWidth / 2) - (textWidth / 2);
let yPosition = (height / 2) - 15; // Center vertically
ctx.fillText(text, xPosition, yPosition);
// Labels
ctx.font = `${fontSize}px ${font}`;
let label = ['Days', 'Hours', 'Minutes', 'Seconds'][quarterIndex - 1];
let labelWidth = ctx.measureText(label).width;
let labelXPosition = (quarterWidth * (quarterIndex - 1)) + (quarterWidth / 2) - (labelWidth / 2);
let labelYPosition = (height / 2) + 10; // Center vertically
ctx.fillText(label, labelXPosition, labelYPosition);
}
// Draw days, hours, minutes, and seconds
drawCenteredText(`${days}`, 1, numberSize, fontSize, font);
drawCenteredText(`${hours}`, 2, numberSize, fontSize, font);
drawCenteredText(`${minutes}`, 3, numberSize, fontSize, font);
drawCenteredText(`${seconds}`, 4, numberSize, fontSize, font);
} else {
ctx.font = `${fontSize}px ${font}`;
let textWidth = ctx.measureText(completedString).width;
ctx.fillText(completedString, (width / 2) - (textWidth / 2), height / 2);
}
if (drawFrameCount) {
// Display frame number
ctx.font = '10px Arial';
let frameInfo = `Frame ${i + 1} of ${frameCount + 1}`;
let frameInfoWidth = ctx.measureText(frameInfo).width;
ctx.fillText(frameInfo, width - frameInfoWidth - 10, height - 10);
}
encoder.addFrame(ctx);
}
encoder.finish();
const genEndTime = new Date(); // Capture end time
const timeTaken = genEndTime - genStartTime; // Calculate time taken
console.log(`GIF Generated, Time Taken: ${timeTaken}ms`); // Log time taken
});
const port = 3000;
app.listen(port, '0.0.0.0', () => console.log(`Server running on port ${port}`));