前端工程化 - 借助 puppeteer 批量生成分享图
Posted Songlcy
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了前端工程化 - 借助 puppeteer 批量生成分享图相关的知识,希望对你有一定的参考价值。
需求分析
在 ToC 的场景中,营销是一件很重要的手段,要让更多的人看到我们的产品,需要覆盖到更大的范围,获取更多的流量,触达和影响更多的用户,从而提升品牌知名度和影响力。
在营销环节有一个关键模块叫分享海报,在营销活动中,无论营销模式有多高明、多接地气、流行甚至创新,单纯靠文字来表达远不如图片来的震感,这种情况在小程序端尤为常见,借助微信的识别二维码功能,可以减少用户的使用成本。
那么如何快速的批量生成分享图就一件比较棘手的事情。
技术选型
市面常用的方案基本有下面 3 种:
- 前端直接根据素材使用 canvas 绘图并生成分享图
- 前端使用 html 使用 html2canvas 生成分享图
- 后端根据素材绘制图层,生成分享图后再返回给前端
其他的一些方案基本也是围绕上述 3 种进行组合、拓展。如果有更好的方案可以再评论区一起探讨下。
canvas 绘图
上手难度最大,对开发要求较高,canvas 和纯 html 布局相去甚远,在绘制图层的时候,样式还原度会比较差,但能够兼容小程序与 web 端,同时在需要转换成 node 服务的情况下,也有 node-canvas 插件支持,目前来看是最通用的解决方案。
html2canvas
从使用角度以及开发难度上来看,是最为便捷且样式还原度最高的一种方式,且相对于其他方案而言,成本是最少的,最大的缺点是在小程序端做分享图的时候,web 与小程序之间的交互会显得比较麻烦。除此之外,它是首推选择。
服务端绘制
服务端可以完成较为简单的需求,生成一些简单的图片,再让前端根据规则获取即可,但是对于服务端的同学来说,可选库能够提供的功能也不会很多,样式还原度也一样会存在一些问题,同时需要考虑并发的问题。
要解决后端并发问题的话,也可以使用批量预生产图片,但这样带来的问题就是生成的图片没办法带上临时的信息,例如分享二维码里面需要携带一些额外的参数,用户信息等内容。
对于前两种选择都有一样缺点,所有的资源依赖都是从服务端获取,在同步生成分享图的时候需要等待资源加载完成,再加上自己绘制的时间,会有一定的延迟。优势在于客户端渲染,渲染成本都嫁接在用户身上,而选择服务端渲染的方案,服务器的成本会增加。
当然如果条件允许的情况下,做预渲染,提前把可推测的资源预先加载,生成分享海报也是一种很好的手段,并且可以降低服务器的一些成本。
最后在多机型、微信版本中可能存在未知兼容、缓存等情况,UI 设计的再完美,客户端渲染也可能出现不可预期的情况。
解决方案
在我们的业务场景中,有大量的商详需要做分享图,而且有些分享图需要携带用户信息,这样就导致如果全靠后端来渲染图片是不太合适的。但是我们也存在长图的情况,全部放在前端渲染也会有一定的性能瓶颈。
目前尝试的方案是后端根据商品属性预渲染完整的图后挂载在 cdn 上,前端根据需求,当需要携带用户信息可以根据生成好的图片当做底图使用 canvas 将二维码绘制上去,如果没有额外的信息的话,就可以直接使用后端渲染的图,同时配合前端预加载内容使得分享海报绘制的效率达到最高。
那么在选择后端渲染的方案上,除了 node-canvas、其他的绘图类库之外,为了保证最好的还原度以及开发成本,最终选择了渲染模板 + 无头浏览器截屏的方式来获取分享图。
上述的方案并不一定适合你目前的场景,具体的解决方案还是需要根据自身的业务情况来选择,或者混合在一起使用。
项目实战
搭建基础环境
- 初始化 koa 项目
现在的 TS 项目比较流行,所以接下来使用 TS 来搭建一下 koa 环境方便测试。
使用下述命令初始化项目并安装对应依赖
// 安装依赖
npm init // 初始化 package.json
npm i koa koa-router
npm i --save-dev typescript ts-node nodemon cross-env
npm i --save-dev @types/koa @types/koa-router
复制代码
新建 tsconfig.json
"compilerOptions":
"module": "commonjs",
"target": "es2017",
"noImplicitAny": true,
"moduleResolution": "node",
"sourceMap": true,
"outDir": "dist", // TS 文件编译产物会放在此处
"baseUrl": ".",
"paths":
"*": [
"node_modules/*",
"src/types/*"
]
,
"include": [
"src/**/*"
]
复制代码
package.json
添加对应的 scripts
脚本命令
"scripts":
"start": "tsc && node dist/server.js",
"start:dev": "cross-env nodemon --watch 'src/**/*' -e ts,tsx --exec 'ts-node' ./src/server.ts",
"tsc": "tsc",
"test": "echo \\"Error: no test specified\\" && exit 1"
,
复制代码
安装 cross-env 是为了跨平台,主要是兼容 windows,如果是 mac 用户可以不安装这个模块
新建 src/server.ts
import * as Koa from 'koa';
import * as Router from 'koa-router';
import renderHtml from './renderHtml'
import screenShoot from './screenShoot'
import resolve from 'path'
const app = new Koa();
const router = new Router();
router.get('/', async (ctx) =>
ctx.body = 'Hello World!';
);
app.use(router.routes());
app.listen(3000);
console.log('Server running on port 3000');
复制代码
接下来启动命令即可:npm run start:dev
浏览器打开窗口输入 http://localhost:3000/
,得到如下反馈则表示 koa 项目创建成功
截屏功能
在截屏功能的选择上,我们选择了 puppeteer 作为无头浏览器,模板插件选择了更贴近 vue 语法的 nunjucks。
安装对应的依赖
npm i koa koa-router
npm i --save-dev nunjucks puppeteer
npm i --save-dev @types/nunjucks
复制代码
创建 src/screenShoot.ts
const puppeteer = require('puppeteer');
interface ILink
contain: string,
name: string,
export const screenShoot = async (links: ILink[]) =>
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.emulate(puppeteer.devices['iPhone X']);
for (let link of links)
await page.setContent(link.contain);
await page.screenshot( path: `./$link.name.png`, fullPage: true );
await browser.close();
复制代码
创建 renderHtml.ts
const nunjucks = require('nunjucks')
export const renderHtml = (tpl: string, params: object) =>
const res = nunjucks.render(tpl, params);
return res
复制代码
创建 tpl/test.njk
<html>
<head>
<style type="text/css">
.share
text-align: center;
</style>
</head>
<body>
<div class='shareImage' id="shareImage">
<p style='font-size:200px;'>username</p>
<p style='font-size:50px;color:red;'>email</p>
</div>
<div class="share" id="shareContain">
<p>我是一段很长的描述</p>
<img class="shareImge" src="https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fpic.616pic.com%2Fys_bnew_img%2F00%2F24%2F13%2FTvzVABfVpn.jpg&refer=http%3A%2F%2Fpic.616pic.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1649742353&t=ab74e701f176154b6d03195f138bc6dd" class="lazyload-img hotzone-pic more fadein fat" data-resize="0">
</div>
</body>
</html>
复制代码
再添新的路由请求即可:
router.get('/screenShoot', async (ctx) =>
const html = renderHtml(resolve(__dirname, './tpl/test.njk'), username: 'cookie', email: 'cookieboty@qq.com' )
screenShoot([
contain: html,
name: 'cookie',
])
ctx.body = 'true!';
);
复制代码
模板在直接渲染在浏览器的样式:
通过上述代码使用 puppeteer 截图出来的样式:
通过对比不难看出,使用 puppeteer 截图出来的样式基本上能够保证较高的还原度。
但是截图中还是有空白区域,以及我们要截图可能只有详情的区域,所以我们可以稍微改造一下截屏代码,添加选择器来限制截屏区域。
for (let link of links)
await page.setContent(link.contain);
if (link.el)
const element = await page.$(link.el);
await element.screenshot( path: `./$link.name.png`, fullPage: false );
else
await page.screenshot( path: `./$link.name.png`, fullPage: true );
复制代码
传入参数添加选择器:
screenShoot([
contain: html,
name: 'cookie',
el: '#shareContain'
])
复制代码
最后截屏出来的内容如下所示,非常干净,还原度基本达到 1 比 1 的程度。
由于使用的是高清截屏,图片的 size 会比较大,大家在使用的时候,可以对其进行一定比例的压缩,根据自己对图片质量的要求将图片压缩至可接受的范围即可。
写在最后
本文到此结束,借助于 Nodejs 完成一个常见的营销分享图的方案,而这只是 Nodejs 的一块很小的应用,另外 Nodejs 也不仅仅是用作于服务端,上述的方案即使不使用 koa 来作为服务,我们依然可以把它组装成工具库提供给其他端或者工具来使用。
如果想对自己的技术精进有更高的追求,不妨借助 Nodejs 来攻克一下目前手上的业务难点、繁点。
作者:CookieBoty
链接:https://juejin.cn/post/7074469127393378340
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
以上是关于前端工程化 - 借助 puppeteer 批量生成分享图的主要内容,如果未能解决你的问题,请参考以下文章