前端生成海报方案以及优化

生成海报在日常的 H5开发中已经很常见了,为了更好的用户体验大家都有不同的解决方案。不管是微信还是朋友圈或者其他的社交平台,生成海报的目的主要是用来分享出去。下面是生成海报并分享的主要流程:

image.png

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

下面是两种上传方式的过程:

image.png

虽然上述流程看着交互比较多,但是每次请求携带的数据都比较少,所以要快一些。

image.png

上述使用业务服务器做中转,相当于上传了两遍,相对于直传效率会略低。

适当的使用缓存

其实上传过程还是比较慢的,即使比较快也需要几百毫秒。所以在这里也可以做一些优化,如果图片内容变动不大,我们可以将上传后的图片地址缓存在 localStorage 中,下次分享就不需要生成并上传,直接从缓存获取到图片地址分享即可。

我们也可以将生成过海报的数据缓存在 localStorage,等到下次分享的时候对比生成海报的数据是否已经存在,已存在的就使用之前的图片的 url 即可。

下面实现一个简单的缓存,主要流程如下

image.png

缓存部分实现如下

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,
        });
    }
}

留下回复