前端生成海报方案以及优化
生成海报在日常的 H5开发中已经很常见了,为了更好的用户体验大家都有不同的解决方案。不管是微信还是朋友圈或者其他的社交平台,生成海报的目的主要是用来分享出去。下面是生成海报并分享的主要流程:
1、获取数据
在服务器端获取海报上展示的数据,例如头像、背景、统计数据等;
2、H5绘制海报
通过服务器返回的数据在 canvas 上绘制海报,有下面的两种方式实现:
- 创建 canvas 画布,手动绘制元素到画布上
- 使用 html2canvas 将 dom 元素直接转换成 canvas 对象
对于简单的海报设计我们可以只用 canvas 原生绘制,如果海报上有很多动态的文案或样式并且过于复杂,建议使用 html2canvas 实现;html2canvas 转换图片的时候可以选择缩放倍数,按照自己的需求进行缩放即可。
如果引用了其他域名的图片,可以通过下面两种方式处理:
- 需要让目标域名设置当前域名可以跨域访问;
- 在业务服务器上做一层代理,通过业务服务器去请求图片并返回给前端(如果对方的图片是 CDN 上的,这样设置可能会失去图片的缓存功能,这就需要在代理的时候将对方的响应头透传过来)。
3、上传获取图片 url
分享时我们需要将图片上传到服务器,获取到一个 url 才可以分享到社交媒体上,接下来会对上传文件每个阶段方案选择做个简单的对比。
文件类型的选择
生成的 canvas 可以将画布上展示的图像转化成各种图片格式,常见的有 png、webp、jpeg。对于这三种格式做一个简单的对比:
格式 | 压缩 | 透明度 | 浏览器支持 |
---|---|---|---|
png | 不支持 | 支持 | 好 |
jpeg | 支持 | 不支持 | 好 |
webp | 支持 | 支持 | Safari 不支持 |
综上所述,使用 png 是比较好的选择。
上传文件类型选择
canvas 可以将画布上展示的内容转换成 blob 和 base64两种方式,base64是一种字符串的方式存储图像,相对于 blob 要大一些。有实例发现,1.1M 的 base64如果使用 blob 只需要800多 K,所以在大小方面 blob 要优于 base64;转换为 base64和 blob 可以分别查看 MDN – toDataURL文档 和 MDN – toBlob文档。
所以我们最终使用了 blob 的形式上传文件。
上传方式的选择
如果文件是存储在业务服务器就不用关注这一块了,如果使用的 OSS 存储文件(使用 OSS 的优点就是可以选择使用 CDN 加速资源访问,也可以利用好 HTTP 缓存减少服务器压力),上传方式有两种:
- 通过业务服务器上传到 OSS
- 使用前端将文件直传到 OSS
下面是两种上传方式的过程:
虽然上述流程看着交互比较多,但是每次请求携带的数据都比较少,所以要快一些。
上述使用业务服务器做中转,相当于上传了两遍,相对于直传效率会略低。
适当的使用缓存
其实上传过程还是比较慢的,即使比较快也需要几百毫秒。所以在这里也可以做一些优化,如果图片内容变动不大,我们可以将上传后的图片地址缓存在 localStorage 中,下次分享就不需要生成并上传,直接从缓存获取到图片地址分享即可。
我们也可以将生成过海报的数据缓存在 localStorage,等到下次分享的时候对比生成海报的数据是否已经存在,已存在的就使用之前的图片的 url 即可。
下面实现一个简单的缓存,主要流程如下
缓存部分实现如下
import MD5 from 'md5.js';
const md5 = new MD5();
const encryption = (text: string) => {
return md5.update(text).digest('hex');
};
/**
* @example
* const cache = new PosterCache("project1");
* const data = {"name":"John", "age": 21};
* const url = "https://yoursite.com/user1.png";
* cache.setImageUrl(data, url);
*
* const data2 = {"name":"John", "age": 21};
* const cachedUrl = cache.getImageUrl(data2);
*
* console.log(url === cachedUrl); // true
*
* */
class PosterCache {
private cacheUrlKey: string = '';
private cacheKey: string = '';
/**
* @param cacheKey 存储在localStorage中key
*/
constructor(cacheKey: string) {
this.setCacheKey(cacheKey);
}
/**
* 设置前缀
* @param cacheKey
*/
setCacheKey(cacheKey: string) {
this.cacheKey = cacheKey;
this.cacheUrlKey = cacheKey + '/post';
}
/**
* 获取图片
* @param compareData 当前对比的数据
*/
getImageUrl(compareData: any) {
const shareDataHash = localStorage.getItem(this.cacheKey);
const sharePostImg = localStorage.getItem(this.cacheUrlKey);
const cacheDataHash = encryption(JSON.stringify(compareData));
if (shareDataHash && shareDataHash === cacheDataHash && sharePostImg) {
return sharePostImg;
}
return null;
}
/**
* 保存图片链接
* @param cacheData 链接对应的数据
* @param url 上传后的图片链接
*/
setImageUrl(cacheData: any, url: string) {
const cacheDataHash = encryption(JSON.stringify(cacheData));
localStorage.setItem(this.cacheKey, cacheDataHash);
localStorage.setItem(this.cacheUrlKey, url);
}
/**
* 清除当前key的缓存数据
*/
clear() {
const keys = Object.keys(localStorage);
for (const key of keys) {
if (key.indexOf(this.cacheKey) > -1) {
localStorage.removeItem(key);
}
}
}
}
export default PosterCache;
4、分享到社交平台
将得到的图片 url 通过社交媒体提供的 JSAPI 发布,每个社交媒体对应的 API 各有差异,有些可能需要通过 app 去调用。
其他
可以使用 nodejs 生成临时凭证,授权给客户端上传文件,服务器生成签名和 url,客户端 put 文件到 oss 中;
import OSS from 'ali-oss';
// 签名参数
interface ISignatureParams {
name: string; // 文件名称
type: string; // 文件mimeType,必传,否则提示SignatureDoesNotMatch错误,例如:image/gif
dir: string; // 存储目录
}
export class AliOSSController {
async signature(data: ISignatureParams) {
const store = new OSS({
region: 'oss-cn-beijing',
accessKeyId: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxx',
accessKeySecret: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxx',
bucket: 'assets',
});
// 需要提供文件路径和文件类型,这里会生成一个url,客户端直接put文件即可
return store.signatureUrl(`${data.dir}/${data.name}`, {
method: 'PUT',
expires: 3600,
'Content-Type': data.type,
});
}
}