前言
笔者在项目中需要用到 node 绘图并返回,查询了市面上的 node 的绘图库
绘图库 | 不想用的理由 |
---|---|
canvas | 这个库吧安装要求,太多了,Python 版本要求 2.7,还要安装 GTK,gyp 什么的,因为已经安装了 py3.0 了改来改去也太麻烦了~。 |
gm | 因为没找到这个库的渐变怎么做的资料,其他看起来挺好用的 |
找来找去我想到可以利用 puppeteer进行绘图
介绍
puppeteer 是谷歌官方出品的一个通过 DevTools 协议控制 headless Chrome 的 Node 库。
可以通过 puppeteer 的提供的 api 直接控制 Chrome 模拟大部分用户操作来进行 UI Test 或者作为爬虫访问页面来收集数据。
首先 Puppeteer 提供了很多的函数能在 Page DOM Environment 中执行代码,
其次 Puppeteer 提供了 ElementHandle 和 JsHandle 将 Page DOM Environment 中元素和对象封装成对应的 Node.js 对象,这样可以直接这些对象的封装函数进行操作 Page DOM
大白话就是一个跑在 node 内部的浏览器,可以在这个浏览器中进行一些 node 不能做的操作,比如:生成网页的 PDF, 网页性能测试,模拟表单提交,爬取网页数据等等,
当然还有这篇文章的主题用puppeteer在 node 中绘图
gogogo
npm i puppeteer
1.封装一下获取浏览器实例
const puppeteer = require('puppeteer')
const mainConfig = require('./mainConfig')
class BrowserManage {
browserDestructionTimeout //清理浏览器实例
browserInstance //浏览器实例
browserState = 'closed' //浏览器状态
/**
* 用于长时间未进行操作时关闭浏览器实例
*/
scheduleBrowserForDestruction() {
clearTimeout(this.browserDestructionTimeout)
this.browserDestructionTimeout = setTimeout(async () => {
if (this.browserInstance) {
this.browserState = 'closed'
await this.browserInstance.close() //关闭浏览器实例
}
}, 5000)
}
/**
* 用于长时间未进行操作时关闭浏览器实例
*/
async getBrowser() {
return new Promise(async (resolve, reject) => {
if (this.browserState === 'closed') {
this.browserInstance = await puppeteer.launch(mainConfig.config.puppeteer) //开启浏览器实例
this.browserState = 'open'
resolve(this.browserInstance)
}
if (this.browserState === 'open') {
if (this.browserInstance) {
resolve(this.browserInstance)
}
}
})
}
}
module.exports = new BrowserManage()
这里的 mainConfig.config.puppeteer
为
{
args: ['--no-sandbox', '--disable-setuid-sandbox']
}
因为只用绘制功能所以,不会进行网页的跳转所以这里没有配置沙箱,如果你涉及了页面的跳转强烈建议你配置沙箱
参考
https://developers.google.com/web/tools/puppeteer/troubleshooting
https://stackoverflow.com/questions/66998228/why-is-no-sandbox-a-unsafe-arg-in-puppeteer
2.定义 绘图函数
这里以绘制文字为例
exports.toImgData = async (options) => {
let fontConfig = {
baseLine: 5
}
function getColorX(i, colors) {
if (colors instanceof Array) {
return (i / (colors.length - 1)).toFixed(2)
}
}
return new Promise((resolve, reject) => {
let canvas = document.createElement('canvas')
let ctx = canvas.getContext('2d'),
font = options.text || '阿巴阿巴',
fontSize = Number(options.fontSize || 32),
fontFamily = options.fontFamily,
lineHeight = fontSize + fontSize / fontConfig.baseLine
canvas.width = fontSize * font.length
canvas.height = lineHeight
gradient = ctx.createLinearGradient(0, 0, canvas.width, 0)
ctx.shadowColor = options.shadowColor || ''
ctx.shadowOffsetX = options.shadowOffsetX || 2
ctx.shadowOffsetY = options.shadowOffsetY || 2
if (options.colors instanceof Array) {
//实现渐变颜色
for (let i = 0; i < options.colors.length; i++) {
gradient.addColorStop(getColorX(i, options.colors), options.colors[i])
}
} else if (typeof options.colors == 'string') {
gradient.addColorStop(0, options.colors)
} else {
// 默认颜色
gradient.addColorStop(0, 'rgba(241,158,194,1)')
}
ctx.font = fontSize + 'px ' + fontFamily
if (!options.mode || options.mode == '1') {
//mode ==1为实线
ctx.fillStyle = gradient
ctx.fillText(font, 0, fontSize)
} else if (options.mode == '2') {
//mode ==2为字体镂空效果
ctx.strokeStyle = gradient
ctx.strokeText(font, 0, fontSize)
}
ctx.restore()
const dataURI = canvas.toDataURL('image/png')
const base64 = dataURI.substr(22) // 22 =`data:image/png;base64,`.length
resolve(base64)
})
}
3.获取并使用浏览器页面实例
const browser = await browserManage.getBrowser()
const page = (await browser.pages())[0]
await page.setOfflineMode(true) //因为我们不需要进行网络资源读取,这样可以节省我可怜服务器的带宽
page.on('console', (msg) => console.log(msg.type(), msg.text())) //监听页面console事件
const base64 = await page.evaluate(toImgData, passedOptions)
browserManage.scheduleBrowserForDestruction()
const buffer = Buffer.from(base64, 'base64')
return buffer //返回Base64 编码的图片
这里注意 page.evaluate
函数
page.evaluate(pageFunction[, …args])
pageFunction <function|string> 要在页面实例上下文中执行的方法
…args <…Serializable|JSHandle> 要传给 pageFunction 的参数
返回: <Promise> pageFunction 执行的结果
这个函数就相当于在获取的页面实例中内部 eval 调用 js 函数
所以toImgData
中的getColorX
写在了内部
当然也可以把getColorX
函数抽离然后这样调用
await page.evaluate(getColorX) //就相当于把getColorX声明并定义
const base64 = await page.evaluate(toImgData, passedOptions)
- 效果如下
总结
本文介绍了 node 使用 puppeteer 进行绘图。要注意的地方是如果在 linux 中绘制中文会出现空白的情况
因为在 linux 中默认是没有安装中文字符的进行安装即可
安装可以参考文章 https://www.huaweicloud.com/articles/f618dd03bebe00f7edc9ceb4214c5254.html
并且在 linux 中直接 npm i puppeteer
是启动不了的
需要自己安装 chromium
或者
sudo apt-get install gconf-service libasound2 libatk1.0-0 libatk-bridge2.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget
然后
const browser = await puppeteer.launch({
executablePath: "/usr/bin/chromium-browser", //通过executablePath配置Chromium 的路径
......
报错这个
(node:28469) UnhandledPromiseRejectionWarning: Error: Failed to launch chrome!
[1025/150325.817887:ERROR:zygote_host_impl_linux.cc(89)]
是因为没配置沙箱,这样以无沙箱模式启动
const browser = await puppeteer.launch({
args: ['--no-sandbox', '--disable-setuid-sandbox']}
......
建议配置沙箱
参考 https://github.com/puppeteer/puppeteer/issues/3443
例子 git 地址 https://github.com/lgldlk/puppeteer-draw-example/
欢迎加入前端学习讨论群 qq 群号: 530496237