前端工程化 - 借助 puppeteer 批量生成分享图

Posted Songlcy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了前端工程化 - 借助 puppeteer 批量生成分享图相关的知识,希望对你有一定的参考价值。

需求分析

ToC 的场景中,营销是一件很重要的手段,要让更多的人看到我们的产品,需要覆盖到更大的范围,获取更多的流量,触达和影响更多的用户,从而提升品牌知名度和影响力。

在营销环节有一个关键模块叫分享海报,在营销活动中,无论营销模式有多高明、多接地气、流行甚至创新,单纯靠文字来表达远不如图片来的震感,这种情况在小程序端尤为常见,借助微信的识别二维码功能,可以减少用户的使用成本。

那么如何快速的批量生成分享图就一件比较棘手的事情。

技术选型

市面常用的方案基本有下面 3 种:

  1. 前端直接根据素材使用 canvas 绘图并生成分享图
  2. 前端使用 html 使用 html2canvas 生成分享图
  3. 后端根据素材绘制图层,生成分享图后再返回给前端

其他的一些方案基本也是围绕上述 3 种进行组合、拓展。如果有更好的方案可以再评论区一起探讨下。

canvas 绘图

上手难度最大,对开发要求较高,canvas 和纯 html 布局相去甚远,在绘制图层的时候,样式还原度会比较差,但能够兼容小程序与 web 端,同时在需要转换成 node 服务的情况下,也有 node-canvas 插件支持,目前来看是最通用的解决方案。

html2canvas

从使用角度以及开发难度上来看,是最为便捷且样式还原度最高的一种方式,且相对于其他方案而言,成本是最少的,最大的缺点是在小程序端做分享图的时候,web 与小程序之间的交互会显得比较麻烦。除此之外,它是首推选择。

服务端绘制

服务端可以完成较为简单的需求,生成一些简单的图片,再让前端根据规则获取即可,但是对于服务端的同学来说,可选库能够提供的功能也不会很多,样式还原度也一样会存在一些问题,同时需要考虑并发的问题。

要解决后端并发问题的话,也可以使用批量预生产图片,但这样带来的问题就是生成的图片没办法带上临时的信息,例如分享二维码里面需要携带一些额外的参数,用户信息等内容。

对于前两种选择都有一样缺点,所有的资源依赖都是从服务端获取,在同步生成分享图的时候需要等待资源加载完成,再加上自己绘制的时间,会有一定的延迟。优势在于客户端渲染,渲染成本都嫁接在用户身上,而选择服务端渲染的方案,服务器的成本会增加。

当然如果条件允许的情况下,做预渲染,提前把可推测的资源预先加载,生成分享海报也是一种很好的手段,并且可以降低服务器的一些成本。

最后在多机型、微信版本中可能存在未知兼容、缓存等情况,UI 设计的再完美,客户端渲染也可能出现不可预期的情况。

解决方案

在我们的业务场景中,有大量的商详需要做分享图,而且有些分享图需要携带用户信息,这样就导致如果全靠后端来渲染图片是不太合适的。但是我们也存在长图的情况,全部放在前端渲染也会有一定的性能瓶颈。

目前尝试的方案是后端根据商品属性预渲染完整的图后挂载在 cdn 上,前端根据需求,当需要携带用户信息可以根据生成好的图片当做底图使用 canvas 将二维码绘制上去,如果没有额外的信息的话,就可以直接使用后端渲染的图,同时配合前端预加载内容使得分享海报绘制的效率达到最高。

那么在选择后端渲染的方案上,除了 node-canvas、其他的绘图类库之外,为了保证最好的还原度以及开发成本,最终选择了渲染模板 + 无头浏览器截屏的方式来获取分享图。

上述的方案并不一定适合你目前的场景,具体的解决方案还是需要根据自身的业务情况来选择,或者混合在一起使用。

项目实战

搭建基础环境

  1. 初始化 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 批量生成分享图的主要内容,如果未能解决你的问题,请参考以下文章

前端工程化 - 借助 puppeteer 批量生成分享图

前端测试框架 puppeteer 文档翻译

使用 puppeteer 生成 PDF 而不保存

Puppeteer自动化批量上传抖音视频

Puppeteer之大屏批量截图

前端生成pdf解决方案