/**
* 文本轉(zhuǎn)圖片
* @param {Object} options - 配置項
* @param {string} text - 文本
* @param {number} [width=200] - 圖片寬度
* @param {number} [height=200] - 圖片高度
* @param {string} [backgroundColor='transparent'] - 背景色
* @param {string} [fontFamily='Arial'] - 字體
* @param {number} [fontSize=16] - 字體大小
* @param {string} [fontColor='black'] - 字體顏色
* @param {number} [lineHeight=1.2] - 行高
* @param {boolean} [horizontalCenter=false] - 水平居中
* @param {boolean} [verticalCenter=false] - 垂直居中
* @param {number} [padding=10] - 邊距
* @returns {string} base64
*/
function textToImage(options) {
const {
text,
width = 200,
height = 200,
backgroundColor = 'transparent',
fontFamily = 'Arial',
fontSize = 16,
fontColor = 'black',
lineHeight = 1.2,
horizontalCenter = false,
verticalCenter = false,
padding = 10,
} = options;
const dpr = devicePixelRatio || 1;
// const canvas = new OffscreenCanvas(width, height)
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = width * dpr;
canvas.height = height * dpr;
canvas.style.width = `${width}px`;
canvas.style.height = `${height}px`;
ctx.scale(dpr, dpr);
// 設(shè)置背景色
ctx.fillStyle = backgroundColor;
ctx.fillRect(0, 0, width, height);
// 設(shè)置字體
ctx.font = `${fontSize}px ${fontFamily}`;
ctx.fillStyle = fontColor;
const words = text.split(' ');
let line = '';
const lines = [];
// 計算每行的最大寬度
const maxWidth = width - padding * 2;
for (const word of words) {
const testLine = line + word + ' ';
const metrics = ctx.measureText(testLine);
// 如果超出最大寬度,則換行
if (metrics.width > maxWidth) {
lines.push(line);
line = word + ' ';
} else {
line = testLine;
}
}
lines.push(line);
// 計算文本位置
const drawableWidth = width - padding * 2;
const drawableHeight = height - padding * 2;
// const totalTextHeight = lines.length * fontSize * lineHeight;
// 限制文本行數(shù)
const maxLines = Math.floor(drawableHeight / (fontSize * lineHeight));
const displayLines = lines.slice(0, maxLines);
let startX = horizontalCenter ? width / 2 : padding;
let startY = verticalCenter
? (height - displayLines.length * fontSize * lineHeight) / 2 + (fontSize * lineHeight) / 2
: padding;
// 設(shè)置文本對齊方式
ctx.textAlign = horizontalCenter ? 'center' : 'left';
ctx.textBaseline = verticalCenter ? 'middle' : 'top';
// 繪制文本
displayLines.forEach((line, index) => {
let truncatedLine = line.trim();
while (ctx.measureText(truncatedLine).width > drawableWidth && truncatedLine.length > 0) {
truncatedLine = truncatedLine.slice(0, -1);
}
ctx.fillText(truncatedLine, startX, startY + index * fontSize * lineHeight);
});
queueMicrotask(() => {
canvas.remove();
});
return canvas.toDataURL('image/png');
// const blob = await canvas.convertToBlob();
// return URL.createObjectURL(blob);
}
const base64 = textToImage({
text: 'Hello, World! This is a best d
width: 200,
height: 200,
backgroundColor: 'pink',
fontSize: 24,
lineHeight: 1.25,
horizontalCenter: false,
verticalCenter: false,
padding: 10,
});
let img = new Image();
img.onload = () => {
document.body.appendChild(img);
};
img.src = base64;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Text to Image</title>
<style>
img {
width: 100px;
height: 100px;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
function textToImage({
text,
font = '14px Arial', // 默認(rèn)字體
width = 200, // 默認(rèn)畫布寬度
height = 200, // 默認(rèn)畫布高度
backgroundColor = '#FFFFFF', // 默認(rèn)背景色
textColor = '#000000', // 默認(rèn)文字顏色
padding = 10, // 默認(rèn)內(nèi)邊距
align = 'left', // 默認(rèn)水平居中
valign = 'top', // 默認(rèn)垂直居中
}) {
/** @type {HTMLCanvasElement} */
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const maxWidth = width - 2 * padding;
const fontSize = parseInt(font.match(/\d+/)[0] ?? 14); // 字體大小
const dpr = window.devicePixelRatio || 1; // 默認(rèn)設(shè)備像素比
// 根據(jù) DPR 設(shè)置實際畫布大小
canvas.width = width * dpr;
canvas.height = height * dpr;
ctx.scale(dpr, dpr); // 縮放畫布
// 設(shè)置背景色
ctx.fillStyle = backgroundColor;
ctx.fillRect(0, 0, width, height);
// 設(shè)置字體
ctx.font = font;
ctx.fillStyle = textColor;
ctx.textBaseline = 'top'; // 設(shè)置文字基線
// 計算最大寬度
let lineWidth = maxWidth || width - 2 * padding;
// 創(chuàng)建緩存 Map 存儲非中文的單詞寬度
const wordCache = new Map();
// 自動換行文本
const lines = wrapText(ctx, text, lineWidth, wordCache);
// 計算文本總高度
let textHeight = lines.length * fontSize + (lines.length - 1) * 5; // 字體高度 + 行間距
if (textHeight > height - 2 * padding) {
// 如果文本高度超出,截斷文本
lines.length = Math.floor((height - 2 * padding) / (fontSize + 5));
textHeight = lines.length * fontSize + (lines.length - 1) * 5;
}
// 計算文字的起始位置,支持水平和垂直居中
const x = align === 'center' ? (width - lineWidth) / 2 : padding;
const y = valign === 'middle' ? (height - textHeight) / 2 : padding;
// 繪制文本
for (let i = 0; i < lines.length; i++) {
ctx.fillText(lines[i], x, y + i * (fontSize + 5));
}
}
function wrapText(ctx, text, maxWidth, wordCache) {
let lines = [];
let currentLine = '';
let words = text.split(' ');
// 遍歷每個單詞
for (let i = 0; i < words.length; i++) {
const word = words[i];
// 判斷當(dāng)前單詞是否為中文
const isChinese = /[\u4e00-\u9fa5]/.test(word);
// 獲取單詞的寬度,優(yōu)先從緩存獲取
let wordWidth;
if (isChinese) {
// 中文字符逐個測量
wordWidth = ctx.measureText(word).width;
} else {
// 非中文字符從緩存獲取
if (wordCache.has(word)) {
wordWidth = wordCache.get(word);
} else {
wordWidth = ctx.measureText(word).width;
wordCache.set(word, wordWidth); // 緩存非中文單詞的寬度
}
}
// 檢查當(dāng)前行加上該單詞后是否超出最大寬度
if (ctx.measureText(currentLine).width + wordWidth > maxWidth && currentLine !== '') {
lines.push(currentLine); // 當(dāng)前行寬度超出,換行
currentLine = word; // 新的一行從當(dāng)前單詞開始
} else {
currentLine += (currentLine ? ' ' : '') + word; // 當(dāng)前行繼續(xù)添加該單詞
}
}
// 最后一行文本
if (currentLine) {
lines.push(currentLine);
}
return lines;
}
// 示例調(diào)用
textToImage({
text: 'This is a long text example that will wrap when necessary. 中文的文本也可以處理。 This is a long text example that will wrap when necessary. 中文的文本也可以處理。 This is a long text example that will wrap when necessary. 中文的文本也可以處理。 This is a long text example that will wrap when necessary. 中文的文本也可以處理。',
font: '14px Arial',
width: 100,
height: 100,
backgroundColor: '#e0e0e0',
textColor: '#333',
padding: 10,
// align: 'left',
// valign: 'top',
});
</script>
</body>
</html>