需求场景
移动端H5生成图片海报分享,是比较常见的交互方式。生成的海报中,往往会有用户的个性化信息,比如头像、昵称等等。
为了实现这种交互,我们探索一下可行的实现方案。
方案选择
方案的选择,按生成图片的场所,我们可以分为前端生成和服务端生成。
前端生成
用html2canvas生成canvas,再由canvas生成base64图片。
服务端生成
在服务端用puppeteer运行无头浏览器,截图后传给前端。
方案对比
html2canvas | puppeteer | |
---|---|---|
速度 | 快,页面加载完就可以生成 | 慢,需要在后台运行无头浏览器和传输base64图片 |
兼容性 | 支持的样式为css的子集,图片有跨域的问题 | 无兼容性问题 |
易用度 | 简单,主要工作在调整样式和解决html2canvas存在的问题 | 较简单,需要额外开发一个用于生成图片的页面 |
下面来对这两种方案的实现进行一个封装,上面表格中,对于易用度的比较,也是基于调用封装好的方法来作比较的。
html2canvas方案
参考文档
html2canvas
html转image
这个方案,作者也是踩了一些坑,最后才总结出一个相对稳定的组件。
我们传入需要生成图片的目标组件,由html2canvas生成canvas,再把canvas转base64图片,设置给img标签的src。
代码语言:javascript复制/**
* Dom转化为图片
* @exports conver
* @param {string} trigger Dom的id
* @param {string} imageElId 需要展示的图片的id
*
* @example
* Dom2Image.conver("app", "appImage");
*/
const conver = function (trigger, imageElId, callback) {
const opts = {
useCORS: true, // (图片跨域相关)
// y: window.pageYOffset // 解决scroll导致截图失败,
backgroundColor: 'transparent',
};
const imgDom = document.getElementById(trigger);
if (imgDom) { // 放大canvas,避免截图模糊
const canvas = document.createElement('canvas');
const scale = 3;
const width = imgDom.offsetWidth;
const height = imgDom.offsetHeight;
canvas.width = width * scale;
canvas.height = height * scale;
opts.scale = scale;
opts.canvas = canvas;
opts.width = width;
opts.height = height;
}
loader(html2canvasJSLink, () => {
html2canvas(document.getElementById(trigger), opts).then((canvas) => {
const base64ImgSrc = canvas.toDataURL('image/png');
const img = document.getElementById(imageElId);
img.src = base64ImgSrc;
if (callback && typeof callback === 'function') {
callback();
}
})
.catch((error) => {
console.log(error);
});
});
};
解决图片链接跨域问题
如果生成图片的dom中,存在图片链接,在移动端可能会报一个图片跨域的错误,这是因为html2canvas是用html的download属性,来请求图片链接的,在移动端这个属性几乎全不支持。
为了解决这个问题,有两个方案:
- 把图片链接改为本地图片。
- 用将图片通过其他方法下载下来,转成base64再赋值给img标签的src。
方案1会增加包的体积,一般情况下优先选择方案2,这里也是对方案2封装了一个方法。
代码语言:javascript复制/**
* 解决图片跨域问题,将网络图片URL转为base64 URL。
* @exports url2Base64
* @param {string} src 网络图片URL
* @returns {Promise} Promise对象返回base64 URL
*
* @example
* Dom2Image.url2Base64("http://test.com/image.png").then(url=>{});
*/
const url2Base64 = src => new Promise((resolve) => {
const img = new Image();
img.setAttribute('crossOrigin', 'anonymous');
img.src = src;
img.onload = () => {
const canvas = convertImageToCanvas(img);
const url = canvas.toDataURL('image/png');
resolve(url);
};
img.onerror = () => {
resolve('');
};
});
通过上面的封装,html2canvas生成海报的方案,我们使用的时候,主要的工作就是去调整样式了,html2canvas不支持的样式,都不能使用。
支持的样式列表在这里:https://html2canvas.hertzen.com/features
如果生成图片出现了样式错乱,优先考虑是否用了不支持的css样式。
其他可能出现的问题,大概率可能是html2canvas的问题,在他仓库的issues能找到大多数的答案。
puppeteer方案
参考文档
express.js
puppeteer
服务端运行无头浏览器,并截图。
这里用express.js来实现服务端代码,实现起来也是比较简单的。
代码语言:javascript复制const express = require('express');
const router = express.Router();
const puppeteer = require('puppeteer');
router.post('/', async (req, res) => {
try {
const { url, width = 375, height = 667 } = req.body;
console.log('url', url);
const browser = await puppeteer.launch({
headless: true,
});
const page = await browser.newPage();
await page.setViewport({ width, height });
await page.goto(url);
await page.waitForTimeout(1000);
const imgBuffer = await page.screenshot({
type: 'png',
});
const base64Str = imgBuffer.toString('base64');
res.json({
code: 200,
data: `data:image/png;base64,${base64Str}`,
msg: '海报生成成功',
});
browser.close();
} catch (error) {
res.status(500).json({
message: error.message,
errors: error.stack,
});
}
});
使用方式
用户端只需要传生成图片的H5链接作为参数。
代码语言:javascript复制axios({
method: 'POST',
url: 'http://localhost:3000/screenshot', // 托管海报的页面
data: {
url: `https://xxx.com/poster`,
}
})
总结
前端生成的方案,作者这边已经在多次活动中使用过了,优点是不需要服务端,一开始的时候确实遍地是坑,但是慢慢摊平后,其实还是一个比较方便的方案。
兼容性其实也还可以,至少遇到的问题,最后都能通过各种调试解决,当然这也是很费时间的,而且不知道在没遇到的机型系统上,是否还存在不为人知的兼容性问题。
服务端生成的方案,作者最近才接触到,尚未用到正式的业务上,其优点是不需要考虑兼容性问题,如果正式使用,还需要考虑服务端性能等等问题。