通过EJS模板生成图片的过程是先使用EJS生成模板内容,然后利用Puppeteer将该模板内容渲染成图片。
此方法仅在Win系统上使用
安装依赖
npm install koa koa-router koa-views koa-static ejs puppeteer
目录结构
-views
-template.ejs
-public
-images
-fonts
index.js
代码
template.ejs 代码
代码中字体必须保证在非跨域状态下才可以使用
<!-- views/template.ejs -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Image Generation</title>
</head>
<style>
* {
margin: 0;
padding: 0;
}
@font-face {
font-family: 'YouSheBiaoTiHei';
src: url('https://webapi.domcer.com/font/YouSheBiaoTiHei-2.ttf');
}
@font-face {
font-family: 'ZhanKuWenYiTi';
src: url('https://webapi.domcer.com/font/ZhanKuWenYiTi-2.ttf');
}
.ZhanKuWenYiTi {
font-family: ZhanKuWenYiTi;
}
.YouSheBiaoTiHei {
font-family: YouSheBiaoTiHei;
}
.box{
position: relative;
}
.dateBox {
font-size: 60px;
color: rgba(56, 56, 56, 1);
font-family: ZhanKuWenYiTi;
font-weight: 400;
letter-spacing: 0px;
line-height: 86.88px;
text-align: left;
vertical-align: top;
position: fixed;
top: 38px;
left: 56px;
}
div.box {
width: 2020px;
/* filter: blur(25px);*/
height: 1000px;
background: url(https://img.js.design/assets/img/66027a8d4a1c4bf1a98b9df9.png) no-repeat;
background-size: 100% 100%;
}
.userBox img {
border-radius: 50%;
border: 3px solid rgba(46, 46, 46, 0.9);
width: 150px;
height: 150px;
position: fixed;
top: 158px;
left: 56px;
}
.userBox .userMsg {
position: fixed;
top: 155px;
left: 243px;
font-weight: 400;
letter-spacing: 0px;
line-height: 78px;
color: rgba(0, 0, 0, 1);
margin: 0;
padding: 0;
font-size: 60px;
font-family: YouSheBiaoTiHei;
}
.fortuneBox {
font-size: 60px;
font-weight: 400;
letter-spacing: 0px;
line-height: 59.82px;
}
.fortuneBox .fortuneTitle {
position: fixed;
top: 440px;
left: 281px;
color: rgba(0, 0, 0, 1);
}
.fortuneBox .fortuneMsg {
position: fixed;
top: 527px;
left: 50px;
font-size: 50px;
color: rgba(128, 128, 128, 1);
width: 945px;
height: 75px;
text-align: center;
padding-top: 13px;
padding-bottom: 12px;
}
.characterBox {
font-size: 60px;
font-weight: 400;
letter-spacing: 0px;
line-height: 59.82px;
}
.characterBox .characterTitle {
position: fixed;
top: 659px;
left: 281px;
color: rgba(0, 0, 0, 1);
}
.characterBox .characterMsg {
position: fixed;
top: 755px;
left: 50px;
font-size: 50px;
color: rgba(128, 128, 128, 1);
width: 945px;
height: 75px;
padding-top: 13px;
padding-bottom: 12px;
text-align: center;
}
.boxRight {
font-family: YouSheBiaoTiHei;
}
.signInBoxTitle {
font-size: 60px;
font-weight: 400;
letter-spacing: 0px;
line-height: 78px;
color: rgba(0, 0, 0, 1);
text-align: center;
vertical-align: top;
position: fixed;
top: 198px;
left: 1196px;
}
.signInBoxMsg {
height: 99px;
width: 945px;
position: fixed;
top: 281px;
left: 929px;
text-align: center;
padding: 11px 0;
font-size: 60px;
font-weight: 400;
letter-spacing: 0px;
line-height: 78px;
color: rgba(128, 128, 128, 1);
}
.awardBoxTitle {
font-size: 60px;
font-weight: 400;
letter-spacing: 0px;
line-height: 78px;
color: rgba(0, 0, 0, 1);
text-align: center;
vertical-align: top;
position: fixed;
top: 447px;
left: 1195px;
}
.awardBoxMsg p {
margin: 0;
padding: 0;
}
.awardBoxMsg {
position: fixed;
top: 540px;
left: 1126px;
font-size: 50px;
font-weight: 400;
letter-spacing: 0px;
line-height: 65px;
color: rgba(128, 128, 128, 1);
text-align: left;
}
.balanceBox {
position: absolute;
top: 737px;
right: 80px;
font-size: 50px;
font-weight: 400;
letter-spacing: 0px;
line-height: 65px;
color: rgba(56, 56, 56, 1);
text-align: left;
vertical-align: top;
}
</style>
<body style="background-color: black;position: relative;">
<div class="bgImg">
</div>
<div class="box">
<div class="boxLeft">
<div class="dateBox">
<%= obj.date %> <%= obj.week %> 已签到<%= obj.userSignInDays%>天
</div>
<div class="userBox">
<img src="<%= obj.userAvatar %>" alt="">
<div class="userMsg">
<div>
HI <%= obj.userName %>
</div>
<div>
<%= obj.helloMsg %>
</div>
</div>
</div>
<div class="fortuneBox ZhanKuWenYiTi">
<div class="fortuneTitle">
查收您的今日运势
</div>
<div class="fortuneMsg">
<%= obj.fortuneMsg %>
</div>
</div>
<div class="characterBox ZhanKuWenYiTi">
<div class="characterTitle">
查收您的今日人品
</div>
<div class="characterMsg">
<%= obj.characterMsg %>
</div>
</div>
</div>
<div class="boxRight">
<div class="signInBox">
<div class="signInBoxTitle">
--- 签到成功 ---
</div>
<div class="signInBoxMsg">
<%= obj.signInMsg %>
</div>
</div>
<div class="awardBox">
<div class="awardBoxTitle">
--- 额外奖励---
</div>
<div class="awardBoxMsg">
<% for (const key in obj.awardList) { %>
<p><%= obj.awardList[key] %></p>
<% } %>
</div>
</div>
<div class="balanceBox">
<div class="balanceBoxTitle">
余额:
</div>
<div class="balanceBoxMsg">
<p><%= obj.balance.coin %>硬币</p>
<p><%= obj.balance.flower %>小花</p>
<p><%= obj.balance.point %>点数</p>
</div>
</div>
</div>
</div>
</body>
</html>
index.js 代码
大部分代码是由chatgpt生成
const Koa = require('koa');
const Router = require('koa-router');
const views = require('koa-views');
const path = require('path');
const {writeFileSync} = require('fs');
const static = require('koa-static');
const app = new Koa();
const router = new Router();
// 使用koa-views中间件来配置模板引擎
app.use(views(__dirname + '/views', {
extension: 'ejs', // 使用你喜欢的模板引擎,这里以ejs为例
}));
const staticDir = path.join(__dirname, 'public');
app.use(static(staticDir));
router.get('/generate-image', async (ctx) => {
// 获取请求参数
const today = new Date();
const month = today.getMonth() + 1; // 月份是从 0 开始计数的,因此需要加 1
const day = today.getDate();
let week=today.toLocaleDateString("zh",{weekday:'long'});
let obj = {
date: `${month}/${day}`,
week: '周'+week.charAt(week.length - 1),
userSignInDays: 30,
userAvatar:"http://localhost:3000/images/1.jpg",
userName:"TongHui",
helloMsg:"早上好,今天心情如何?",
fortuneMsg:"宜 出门 忌 躺平",
characterMsg:"[53] 运气一般般呢",
signInMsg:"+3 硬币(每日签到)",
awardList:[
"+1 小花 (每月首次签到)",
"+10 硬币 (每月首次签到)",
"+8 硬币 (每周首次签到)",
"+6 硬币 (连续签到)"
],
balance:{
coin:300,
flower:1,
point:0
}
}
// 渲染HTML模板,并将参数传递给模板
await ctx.render('template', {obj});
// 使用puppeteer导出HTML为图片
const puppeteer = require('puppeteer');
const browser = await puppeteer.launch();
const page = await browser.newPage();
// 设置页面为透明背景
await page.setViewport({width: 2020, height: 1000, deviceScaleFactor: 0.8});
await page.evaluate(() => {
document.body.style.background = 'transparent';
});
// 替换成你的HTML内容
const htmlContent = await ctx.render('template', {obj});
await page.setContent(htmlContent);
const screenshotBuffer = await page.screenshot({
clip: {x: 0, y: 0, width: 2020, height: 1000},
omitBackground: true
});
// 生成图片名字
const filename = `${Date.now()}_image.png`;
// 图片路径
const filePath = `./public/images/${filename}`;
// 生成图片
writeFileSync(filePath, screenshotBuffer);
// 关闭浏览器
await browser.close();
// 返回地址
ctx.body = {imagePath: `/images/${filename}`};
});
// 将路由应用到Koa实例
app.use(router.routes());
// 启动服务器
const PORT = 3000;
app.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`);
});
获取Api
http://127.0.0.1:3000/generate-image
返回
{
"imagePath": "/images/1711952806672_image.png"
}
获取图片
http://127.0.0.1:3000/images/1711952806672_image.png