目录
article
使用 fabirc.js 在服务端生成立体文本图片
使用 fabirc.js 在服务端生成立体文本图片
因项目需要在后端生成文字的立体图片,虽然本人是 Java 开发,还是在 AI 的帮助下,实现了这个功能。
现有的项目是基于 fabric.js 在后端生成图片的,所以在此基础上添加了立体文字的生成功能。
本来 AI 提供的是一个基于 DIV 元素的实现方案,而且感觉效果比 fabric.js 的要好一些,但是在 Node.js 中不知道该如何去实现。
示例代码
相关代码整理了出来,详见代码仓库:3D Text。
其主要逻辑部分如下:
/* POST endpoint for generating text images */
router.post('/generate-text', async function (req, res, next) {
try {
console.log('请求生成 3D 文字图片,参数:', req.body);
const {
text = '',
color = '#ddd',
fontFamily = 'KaiTi',
fontStyle = 'normal',
fontWeight = 'regular',
fontSize = 40,
layerCount = 12, // 3D 层数(越多越立体)
} = req.body;
// 校验参数
if (!text) {
next(createError(500, "文字不能为空"));
return
}
// 处理 fontFamily 字符串
const processedFontFamily = fontFamily.split(',')
.map(font => font.trim())
.map(font => `'${font}'`)
.join(',');
// 处理换行(fabric 的 Text 对象不直接支持\n,拆分成多行)
const lines = text.split('\n');
const lineHeight = fontSize * 1.2; // 行高
const gradient = createGradient(color); // 创建渐变样式
// 计算内边距(以防止部分字体下文本显示不全)
// 显示不全是因为 canvas.width 只是根据平均文字宽度计算的,而是实际显示的宽度
// 暂时没有找到方法获取到实际的宽度
const padding = fontStyle === 'italic' ? fontSize * 0.3 : fontSize * 0.2;
// 创建多层文字模拟 3D 厚度(从后到前绘制)
const textGroups = [];
// 逐行创建文字
lines.forEach((line, lineIndex) => {
// 将每一行文字的多个图层定义为一个组
const lineGroup = new fabric.Group([], {
left: 0,
top: lineIndex * lineHeight,
});
for (let i = 0; i < layerCount; i++) {
// 计算每层的偏移(模拟CSS的translateZ和opacity)
const offset = i * 0.1; // 偏移量,控制3D厚度感
const opacity = 0.8 - (i * 0.05); // 外层透明度降低,和CSS一致
const textObj = new fabric.Text(line, {
left: - offset, // X轴偏移
top: lineIndex * lineHeight - offset, // Y轴偏移
fontSize: fontSize,
fontFamily: processedFontFamily,
fontStyle: fontStyle,
fontWeight: fontWeight,
skewX: fontStyle === 'italic' ? -15 : 0, // 手动设置X轴倾斜,模拟斜体效果(数值越大倾斜越明显)
opacity: opacity,
fill: i === layerCount - 1 ? gradient : color, // 顶层用渐变,底层用深色增强立体感
shadow: {
color: 'rgba(0,0,0,0.2)',
blur: 2,
offsetX: 1,
offsetY: 1
} // 添加阴影效果
});
lineGroup.addWithUpdate(textObj);
}
textGroups.push(lineGroup);
});
// 计算整体宽高
const width = textGroups.reduce((max, obj) => Math.max(max, obj.getScaledWidth()), 0) + padding * 2;
const height = lineHeight * lines.length;
// 创建画布
const canvas = new fabric.StaticCanvas(null, { width, height });
// 添加所有文字对象到画布
textGroups.forEach(textGroup => canvas.add(textGroup));
// 水平居中显示
textGroups.forEach(group => {
group.set({
left: (width - group.getScaledWidth() - padding) / 2,
});
});
// 渲染 canvas
canvas.renderAll();
const dataURL = canvas.toDataURL({
format: 'png',
quality: 1,
multiplier: 4
});
const base64Data = dataURL.replace(/^data:image\/\w+;base64,/, '');
const imgBuffer = Buffer.from(base64Data, 'base64');
res.set('Content-Type', 'image/png');
res.send(imgBuffer);
} catch (e) {
console.error('创建文字图片失败', e);
next(createError(500, "创建文字图片失败"));
}
});
// 创建渐变填充
function createGradient(baseColor = '#111111') {
function hexToRgb(hex) {
hex = hex.replace('#', '');
if (hex.length === 3) {
hex = hex.split('').map(h => h + h).join('');
}
const int = parseInt(hex, 16);
return {
r: (int >> 16) & 255,
g: (int >> 8) & 255,
b: int & 255
};
}
function rgbToHex(r, g, b) {
const toHex = n => n.toString(16).padStart(2, '0');
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
}
// 将基色向白色方向按比例提亮(percent 0-1)
function lighten(hex, percent) {
const { r, g, b } = hexToRgb(hex);
const nr = Math.round(r + (255 - r) * percent);
const ng = Math.round(g + (255 - g) * percent);
const nb = Math.round(b + (255 - b) * percent);
return rgbToHex(nr, ng, nb);
}
const startColor = lighten(baseColor, 0.25); // 25% 提亮,可调整
const gradient = new fabric.Gradient({
type: 'linear',
coords: { x1: 0, y1: 0, x2: 1, y2: 1 }, // 135 度渐变方向
colorStops: [
{ offset: 0, color: startColor },
{ offset: 1, color: baseColor }
]
});
return gradient;
}
图片效果

遗留问题
开发过程中也发现了一些问题:
- 文字的宽度无法精准的计算。
特别是一些特殊字体本身就有一定的倾斜,如果仅使用本身的宽度,会导致生成的文字显示不全。
还有一些字体在一些字母组合下会有相互重叠的部分,也会导致获取到的宽度和实际有差距。
暂时的解决方案是在文字整体的左右两边增加一些边距。虽然不能完全避免文字显示不全的情况,而且有可能会导致文字两边有比较大的空白,但是在大多数情况下效果还可以。 - 暂时仅支持 Node.js 16.x。
更高的版本下安装 canvas 依赖时会报错。不知道是环境的问题?还是别的什么原因导致的?