【Web技术】961- 3分钟搞定海报合成

article/2025/8/22 1:42:48

背景

在推广业务中,常常会遇到合成带二维码海报分享功能,并且为了推广力度,需要同时在APP、WEB、小程序都有此功能加大曝光,各端都需要单独编写,复用能力差,效率低。本身合成海报业务并无难度,在此背景下为了提高效率开发了lumu-poster海报合成工具(技术栈:nestjs + react + mysql)

分析

在现有社区中针对海报生成本身已经有很多成熟方案,如下:

  1. html2canvas/canvas插件截图

  2. 服务器(java,node等)绘制

  3. 服务器使用puppteer无头浏览器生成

html2canvas/canvas绘制截图

  • 优势:在于完全解放服务器,由前端独立生成,定制化样式强。

  • 缺点:部分手机兼容性不足,跨端复用能力不足,性能依靠手机自身

服务器插件绘制

  • 优势:在于不需要前端处理,跨端复用能力强,性能优。

  • 缺点:定制化样式能力不足,编码相对复杂

服务器使用puppteer生成

  • 优势:个性定制化强,复用能力强。

  • 缺点:性能上限不足

因为本身我们海报不仅仅是图片,还会有表格,长图等偏个性化的内容,所以看重个性化扩展和跨端复用能力,对比上述方案最终选择使用puppteer生成。

puppteer痛点

社区本身有很多关于puppteer生成图片的方案和文章,但其中发现了几个问题

  1. 社区中使用puppteer常用url访问页面然后生成图片,这样会导致每个合成的图片都需要前端去做一个页面并且对接动态数据,相当于把工作量全部放在前端。

  2. 现有海报方案里,都是通过page.goto方式进行网络页面加载,需要前端每个海报都创建页面,测试,部署,发布;流程复杂且无法解放前端。响应速度则特别依赖页面资源加载和网络状态。前后端分离情况,页面需要请求后台动态加载内容基本在800ms-1500ms左右,采用服务端渲染页面简单的截图页基本上优化到400-500ms的情况,即便如此加之其他业务处理,接口响应基本在800ms左右。

  3. puppteer在每开一个tab页面及需要大约30M内存,并且同时多开tab执行业务会导致cpu负载,这个则决定了puppteer的单机上限,代码上优化程度有限。

解决方案

针对上述3个痛点,我是如何解决这些问题呢?

关于痛点1

主要是需要解放前端双手,本身海报业务并不复杂,一般是图片(背景图,头像,二维码等),文字,表格等这些简单的元素。这种简单特定业务即可使用可视化方式进行生成,这里我选用了React作为UI框架,通过自定义json scheam作为数据存储格式,动态渲染页面。在设计可视化中内在核心是:组件编排和表单编排。

组件编排

在设计组件编排时考虑到可视化在其他项目都有可用性,这里使用了插件化的方式为组件赋能,及组件本身只有渲染能力,如果需要给组件扩展能力,及使用高阶组件的方式进行注入,这里布局统一使用了绝对布局,使用moveable插件进行二次封装成一个高阶组件来实现拖拽。

基础组件的json scheam定义,所有组件需要在此ComponentSchema上进行继承

// 组件类型
export type ComponentTypes = 'TEXT' | 'PICTURE' | 'CANVAS'; 
// 组件配置
export interface ComponentSchema {/** 服务器唯一id */id: string;/** 节点名称 */nodeName: string;/** 组件类型 */name: ComponentTypes;/** 组件外层挂载id */domId: string;/** 组合id */groupId: string;/** 父id */parentId: string;/** 组件样式 */styles: React.CSSProperties;/** 自定义内容 */custom: unknown;/** 子组件内容 */children?: ComponentSchema[];
}
复制代码

文字组件的示例

import React from 'react';
import { ComponentSchema } from '../../interface';export interface TextProps extends ComponentSchema {custom: {text: string}
}const Text: React.FC<TextProps> = ({ custom, styles, domId, groupId, children }) => {return (<divclassName={groupId}id={domId}dangerouslySetInnerHTML={{__html: custom.text}}style={styles}>{children}</div>)
}Text.displayName = 'Text';export default Text;
复制代码

然后通过组件插件系统进行组件注册,在注册的时候进行特定高阶组件注入。流程如下:  页面渲染组件

import React from 'react';
import { ComponentSchema } from './interface';
import { getViewPlugin } from './view-plugins';const StaticViewRender: React.FC<{ dataSource: ComponentSchema[], mock: boolean }> = ({ dataSource, mock }) => {return (<React.Fragment>{dataSource.map((data) => {const plugin = getViewPlugin(data.name);if (plugin) {const Component = plugin.component;return (<Component key={data.domId} {...data} mock={mock}>{ data.children && <StaticViewRender mock={mock} dataSource={data.children} />}</Component>)}return null;})}</React.Fragment>)
}
复制代码

表单编排

在可视化系统中,表单主要是通过组件属性生成对应的表单,有些属性我们并不想用户编辑,所以在设计表单时,我们同样使用自定义json schema的方式定义表单并通过插件注册的方式进行注入系统,通过组件名称进行关联组件。因为antd form本身的规范化得以很容易的写出基础的动态渲染表单。因为海报本身会有动态内容,所以这里做了一个简单的字符串解析,通过规则注入动态数据。在创建海报时通过${变量}的方式告诉编辑器这里是动态数据的key,然后在渲染的时候,使用者通过url query的方式调用进行匹配。如海报完成后会生生一个url http://x.x.x.x/tool/screenshot?id=10&clipWidth=694&clipHeight=684&name=&headImg=&code= 这个url就是图片地址,其中name/headImg/code就是在制作海报时的动态数据key

效果图:

这样基本解放了前端,一般海报涉及4-5个元素,基本在3分钟内就可以完成海报发布。

关于痛点2

上述可视化编辑器已经完成了页面生成,现在通过goto方式直接访问,但是发现本身系统使用react,react包本身不小且如果基础组件越来越多则打包的资源便会增加,而且现代开发方式,需要通过js读取完成后再过去当前模板的json,然后再渲染,这种方式导致渲染时间直接拖长。这里最开始想到使用动态渲染组件来减少组件的资源,但是发现本身海报业务里的组件资源并不多,主要还是react本身资源,这时候想到,本身海报渲染使用react大材小用,不如使用上一代开发方式,页面直接写在html上,完全就避免了js的渲染的性能消耗。但是如果直接写到html上,这个时候一定想到了服务端渲染,但服务端渲染需要搭建一个ssr服务,但也是没有避免加载react依赖,感觉大材小用,毕竟react和reactdom本身包也不小,其实就是渲染html,感觉通过react vue都有点大材小用,这时候发现puppteer中的setContent这个api可以直接注入html进行页面渲染,这样可以最大程度上规避页面网络因素,本身我们海报也不需要js,css。感觉方案很棒,那如何方便的注入html呢?其实我们在做可视化的时候已经就有html,只是没有动态内容,那在完成时通过规则直接把html存入数据库即可。通过这套规则,合成海报基本能在150ms-400ms完成,最大程度上减少了包体积和网络因素。

  @Get('/updateScreenshot')async updateScreenshot(@Res() res: Response,@Query() query: {id: string,clipWidth: string,clipHeight: string,clipType?: 'png' | 'jpeg',clipScale?: string,[key: string]: string}) {const { id, clipWidth, clipHeight, clipScale = 1, clipType = 'png', ...params } = query;const pictureInfo = await this.pictureService.findById(+id);// 通过解析自定义规则注入真实数据const html = renderStr(pictureInfo.html, params);const buffer = await pool.use(async (page: puppeteer.Page) => {await page.setContent(html);const buffer = await page.screenshot({type: clipType,encoding: 'binary',clip: {x: 0,y: 0,width: +clipWidth,height: +clipHeight}}) as Buffer;return buffer;});res.setHeader('Content-Type', 'image/png');res.setHeader('Content-Length', buffer.length);res.status(200).send(buffer);}
复制代码
const renderStr = (str:string, context: object = {}) => {const tokenReg = /\$(\\)?\{([^\{\}\\]+)(\\)?\}/g;//@ts-ignorereturn str.replace(tokenReg, (word, slash1, token, slash2) => {if (slash1 || slash2) {return word.replace('\\', '');}let variables = token.replace(/\s/g, '').split('.');let currentObject: object = context;let i, length, variable;for (i = 0, length = variables.length, variable = variables[i]; i < length; ++i) {//@ts-ignorecurrentObject = currentObject[variable];if (currentObject === undefined || currentObject === null) return '';}return currentObject;})
}
复制代码

关于痛点3

暂时没有特别好的解决方案,可以使用serverless或者在高并发下我们使用队列的方式来平滑输出。

整体流程

最后

已经做过2个NoCode编辑器,发现NoCode并不能做到大而全,反之都是针对特定场景或者细分领域的,只有在这些场景下,前端交互相对简单,才能够沉淀出足够多的组件。不管哪种场景底层都需要依靠组件编排和表单编排,针对这2类的开发就变得尤为重要,希望结识更多对NoCode/LowCode/ProCode感兴趣的朋友相互交流。

附:还可以更近一步,通过上传psd文件解析成组件,这样连拖拉拽都可以直接省掉,达到秒级生成海报。

转自:https://juejin.cn/post/6959774354947178504


http://chatgpt.dhexx.cn/article/HAK0hQk5.shtml

相关文章

如何合成动态海报?手把手教你一键在线合成gif海报

相信大家在平时都见过那种gif动态海报图片吧&#xff01;是不是觉得只有专业的设计师才能制作呢&#xff1f;其实&#xff0c;这种gif动态海报制作起来非常的简单&#xff0c;只需要准备几张图片尺寸相同图文内容不同的图片&#xff0c;再使用**在线动画制作**工具&#xff0c;…

波束形成MATLAB代码

常规的波束形成方法 clc; clear; close all;fs 1000; c 150; N 128; f 100; lambda c / f; d 0.5 * lambda; theta 1: 1: 180; t (0:1:1000-1) / fs;A zeros(1, length(theta)); A(5) 5; A(20) 4; A(25) 5; A(50) 3; % 在这四个方向上有目标 S zeros(length(th…

相移波束形成算法的MATLAB仿真

仿真结果如下&#xff1a; 部分核心程序如下所示&#xff1a; %************************************************************************** % 相移波束形成算法 %************************************************************************** %…

【波束形成】MMSE波束形成,自适应MMSE波束形成以及自适应MBER波束形成

1.软件版本 matlab2013b 2.本算法理论知识 3.部分源码 clc; clear; close all; warning off;SNR_set [0:1:12]; BER 1; nRx 10; nTx 10; frame_length 1000; Bers []; %论文table 2 alpha [0,10,-15,30,-45,50,60,-5…

基于FPGA的波束形成verilog开发

目录 一、理论基础 二、核心程序 三、仿真测试结果 一、理论基础 根据原理可知&#xff0c;整个波束形成的基本结构框图如下图所示&#xff1a; 这里&#xff0c;我们使用的加权函数为&#xff1a; 这个模块&#xff0c;相当于上述结构的&#xff1a; 二、核心程序 timesca…

鲁棒自适应波束形成

鲁棒自适应波束形成 本文是我关于Microphone Arrays Signal Processing Techniques and Applications第五章的整理。 自适应形成其方向性的波束形成器称为自适应波束形成器。它同时进行波束控制和零位控制。然而&#xff0c;在大多数声波束形成器中&#xff0c;只有在目标DOA是…

窄带波束形成——时域与频域常规窄带波束形成

最近学习了一下《最优阵列处理技术》&#xff0c;应老师要求写一个线性均匀水听器阵列的常规波束形成&#xff0c;由于是初学者&#xff0c;写的可能会有点问题&#xff0c;欢迎大家提出修改建议和指导&#xff0c;写这个主要是记录自己的思考&#xff0c;其次是和初学者进行交…

麦克风阵列波束形成

原文转载于&#xff1a;http://blog.csdn.net/shichaog/article/details/74143427 有所修改 感谢作者 波束形成 beamforming 体现的是声源信号的空域选择性&#xff0c;许多传统波束形成方法具有线性处理结构&#xff1b; 波束形成需要考虑三个方面&#xff1a; 1.麦克风…

LMS波束形成

LMS自适应波束形成器 % 标量阵最小均方准则(LMS)自适应波束形成器(ADBF) close all; Sound_velocity1200; %声速 Frequency300; %信号频率 Sample_Frequency100Frequency; %采样频率 Array_num16; %阵元数 Array_distance1/2(Sound_velocity/Frequency); %阵元间距 Signal_Leng…

波束形成(最大信噪比准则、LCMV、MSC、LMS、RLS)

波束形成&#xff08;最大信噪比准则、LCMV、MSC、LMS、RLS&#xff09; 波束形成的基本概念 # 波束形成准则 分别对上面所阐述的最大信噪比准则&#xff0c;旁瓣对消器&#xff0c;线性最小均方误差&#xff0c;以及自适应LMS和RLS算法进行仿真。 最大信噪比准则&…

语音领域的波束形成Beamforming小结

关注、点赞、收藏是对我最大的支持&#xff0c;谢谢^v^ 目录 1. 背景介绍 2. 多通道信号的公式描述 3. 传统波束形成&#xff08;delay-and-sum和filter-and-sum&#xff09; 4. MVDR 4.1 传统MVDR 4.2 融入深度学习的MVDR 5. GEV(Generalized eigenvalue) beamformer …

空间谱专题02:波束形成(Beamforming)

作者&#xff1a;桂。 时间&#xff1a;2017-08-22 10:56:45 链接&#xff1a;http://www.cnblogs.com/xingshansi/p/7410846.html 前言 本文主要记录常见的波束形成问题&#xff0c;可以说空间谱估计是波束形成基础上发展而来&#xff0c;在系统论述空间谱之前&#xff0c;有…

UE4 插件 简单全景播放器

UE4 插件 1分钟完成全景展示项目&#xff08;Simple panorama player and video player&#xff09; 全景图、全景视频播放器&#xff0c;附带列表和热点模板。另附带一个视频播放器。全景播放器可以使用本地资源或Web URL。 完全由蓝图实现&#xff0c;易于扩展和修改。 具有…

Android VR Player(全景视频播放器) [10]: VR全景视频渲染播放的实现(exoplayer,glsurfaceview,opengl es)

前言 此博客的大部分内容来自我的毕业设计论文&#xff0c;因此语言上会偏正式一点&#xff0c;如果您有任何问题或建议&#xff0c;欢迎留言。在此感谢实验室的聂师兄&#xff0c;全景视频render部分的代码设计主要参考了他所编写的代码来完成&#xff0c;他对视频渲染过程的…

VR+全景播放器+头控讲解-07

VR全景播放器头控讲解-01-知识储备VR全景播放器头控讲解-02-创建球体VR全景播放器头控讲解-03-渲染视频VR全景播放器头控讲解-04-滑动手势VR全景播放器头控讲解-05-伸缩画面VR全景播放器头控讲解-06-头控实现VR全景播放器头控讲解-07-分屏技术 学习目标 如何实现分屏 实现思路 …

[OpenGL]从零开始写一个Android平台下的全景视频播放器——目录

Github项目地址 为了方便没有准备好梯子的同学&#xff0c;我把项目在CSDN上打包下载&#xff0c;不过不会继续更新&#xff08;保留在初始版本&#xff09; 先放一张效果图&#xff1a; Youtube 优酷 前言 Android平台下的全景视频&#xff08;360&#xff0c;Panoram…

VR+全景播放器+头控讲解-06

VR全景播放器头控讲解-01-知识储备VR全景播放器头控讲解-02-创建球体VR全景播放器头控讲解-03-渲染视频VR全景播放器头控讲解-04-滑动手势VR全景播放器头控讲解-05-伸缩画面VR全景播放器头控讲解-06-头控实现VR全景播放器头控讲解-07-分屏技术 学习目标 掌握头控部分布局 如何检…

mxreality.js 免费开源的全景图/全景视频/VR 直播播放器介绍

[2018-10-20 重要更新]支持VR直播功能支持全景视频poster支持全景图和视频和场景之间随意切换全景模式切换回默认主视角播放列表 优点&#xff1a; 1、全景图支持全景模式和VR模式 2、支持网页端全景图补天功能&#xff0c;有效去除顶部和底部拼接留下的痕迹、做出真正完美的…

Unity3D制作极简版VR全景视频播放器

自从Unity5.6.4还是2017的版本开始&#xff0c;官方提供了兼容移动端和Windows端的视频播放器控件——Video Player&#xff0c;下面介绍如何使用这个控件&#xff0c;制作VR播放器。 1、新建空白场景&#xff0c;新建球体Sphere&#xff0c;Camera放置球心位置&#xff1b; …

基于threejs,完成一个简单的全景图播放器

直接上代码 CameraControls.js相机控制器 import * as THREE from three;function CameraControls(object, domElement, cb, update) {this.object object;this.domElement domElement ! undefined ? domElement : document;this.enabled true;this.lookSpeed 0.1;this.…