通过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

效果图

1711705505655_image.png

最后修改:2024 年 04 月 02 日
如果觉得我的文章对你有用,请随意赞赏