当前位置 博文首页 > 前端开发博客:前端海报生成的不同方案和优劣
点击上方?前端开发博客,回复“加群”
加入我们一起学习,天天进步
工作中做了很多生成海报的功能,不同需求,不同场景,使用了几种方案,各有优劣。一直想要整理一下,但这个过程中的思考和遇到的问题没有记录下来,比如图片的跨域问题,文字的问题,做完没有记录,无迹可寻,以至于很难开始。最近重新回顾了一下,整理了一篇文档,或有疏漏和不准确之处,欢迎交流。
1.分享海报
1.分享优惠券
2.年度账单
3.分享海报
版本 1.0.0rc7
https://github.com/niklasvh/h...
star:22k
时间 2021-01-06
首页测试demo:https://html2canvas.hertzen.com/
? 6.0 koobee
? 5.0.2 vovoY51A
? 7.0.3
? 7.0.22
? 14.2
? 11.2.1
? 7.0.20
? 7.0.1
兼容性更好,官方描述如下:
Browser?compatibility
The?library?should?work?fine?on?the?following?browsers?(with?Promise?polyfill):
Firefox?3.5+
Google?Chrome
Opera?12+
IE9+
Safari?6+
As?each?CSS?property?needs?to?be?manually?built?to?be?supported,?there?are?a?number?of?properties?that?are?not?yet?supported.
一些属性不支持转化:如不支持伪类,border不支持dash,不支持text-shadow,(即还原程度需要和设计沟通,或者考虑采用图片等形式去替换显示元素)
features:https://html2canvas.hertzen.c...
rc5出现过ios生成不了图片的问题,还原到rc4就可以了
https://github.com/niklasvh/h...
看到issue里有出现ios某些版本下微信失败的情况,暂没有复现
版本2.6.0
https://github.com/tsayen/dom...
star:6.9k
元素齐全,还原度高
不兼容safari,所以建议只在Chrome下使用
没有维护更新了
1.作者明确表示不支持safari,因为foreignObject的安全性问题(明说了不支持,我仔细看文档 => 白作工 )
链接:
https://github.com/tsayen/dom...
https://html.spec.whatwg.org/...
SVG在Web攻击中的应用: https://www.anquanke.com/post...
2.iOS14.2下,图片丢失问题
其实在测的时候,发现ios14.2下生成图片第一次始终会出现图片丢失,第二次或第三次正常
解决方案,2~3次调用,取最后一次(看issue里有些机型还是不支持的)
3.低端安卓机上会出现失败情况,主要是文字的问题(这里是我写过的旧的记录,不是很确定)
清晰度问题:\(看到很多人说要去改源码,其实不用的,作者是提供了参数了\)
通过scale放大设备的DPR倍数
let?elm?=?document.getElementById('my-node')
const?scale?=?window.devicePixelRatio
this.loader.open()
await?this.$nextTick()
domtoimage
????.toBlob(elm,?{
?????????quantity:?1,
?????????width:?elm.offsetWidth?*?scale,?//?画布放大
?????????height:?elm.offsetHeight?*?scale,
????????style:?{
????????????transform:?`scale(${scale})`,?//?元素放大
????????????transformOrigin:?'0?0',
????????},
????})
????.then(this.upload)?//?处理结果
????.catch(function?(error)?{
????????this.loader.close()
????????console.error('oops,?something?went?wrong!',?error)
????})
let?canvasBox?=?document.createElement('canvas')
let?ctx?=?canvasBox.getContext(
????????????????????'2d'
????????????????)?as?CanvasRenderingContext2D
let?bgImag?=?''
ctx.drawImage(bgImg,?0,?0)
解决办法?:
1)使用 crossOrigin 属性。
解决canvas图片getImageData,toDataURL跨域问题
2)图片本身也需要允许跨域
3)设置 useCORS:true,原理相同,但使用以上跨域方法,若同时设置为 allowTaint:true?,仍然会认为画布已被污染而不可用;
核心:计算所有文字,根据每行可显示的最大宽度,来拆分成每行渲染
参考:https://www.zhangxinxu.com/wordpress/2018/02/canvas-text-break-line-letter-spacing-vertical/
2.2.1.只采用默认字体或少量定制字体(Fontmin获取特定字体的字体,写死的数据,如果换行需要计算换行问题-空格回车等奇葩问题)- ==@font-face,这样使用是涉及版权问题的,确保你们有该字体的版权==
@font-face?{
????font-family:?"DIN?Condensed";
????//?prettier-ignore
????src:?url("~@/assets/fonts/DIN-Condensed-Bold.woff")?format("woff"),?url("~@/assets/fonts/DIN-Condensed-Bold.ttf")?format("ttf");
}
@font-face?{
????font-family:?"PingFang?SC?Bold";
????//?prettier-ignore
????src:?url("~@/assets/fonts/PingFang-SC-Bold.ttf")?format("ttf"),?url("~@/assets/fonts/PingFang-SC-Bold.woff")?format("woff");
}
ctx.fillText('20px?PingFang?SC?Bold',?x,?y)
ctx.fillText('20px',?x,?y)
2.2.2(一定要完整某种字体的情况下:动态变化的数据,需要接口支持)
svg?to?img
直接domtoSvg也有安卓失败的问题
let?svg?=?接口获取svg(参考年度账单)
let?svgBase64:?string?=
????'data:image/svg+xml;base64,'?+
????window.btoa(
????????unescape(encodeURIComponent(svg))
????)
await?this.loadImg(svgBase64).then(
????img?=>?{
????????ctxC.drawImage(
????????????img,
????????????~~(20?*?this.scale),
????????????~~(186?*?this.scale)
????????)
????}
)
//?尽量使用整数
//?devicePixelRatio
protected?imgQuality?=?window.devicePixelRatio
//?和设计稿的比例?设计稿宽度是640
get?scale()?{
????return?(document.body.clientWidth?*?this.imgQuality)?/?640
}
//?画布宽高,避免生成的图片模糊情况出现
canvasBox.width?=?Math.ceil(
????document.body.clientWidth?*?this.imgQuality
)
canvasBox.height?=?Math.ceil(980?*?this.scale)
//?二维码的宽度
get?qrCodeSize()?{
????return?~~(155?*?this.scale)?//?rpx?*?scale
}
//?画布和设计稿750的比例
scale?=??(document.body.clientWidth?*?this.imgQuality)?/?750
线:
**
?*?画线
?*/
export?function?drawLine(
????ctx:?CanvasRenderingContext2D,
????data:?{
????????x:?number
????????y:?number
????????width:?number
????????height:?number
????????strokeStyle:?string
????},
????scale:?number
)?{
????let?x?=?data.x?*?scale
????let?y?=?data.y?*?scale
????let?dx?=?(data.x?+?data.width)?*?scale
????let?dy?=?data.y?*?scale
????//?开始一个新的绘制路径
????ctx.beginPath()
????//?定义直线的起点坐标为(10,10)
????ctx.moveTo(x,?y)
????//?定义直线的终点坐标为(50,10)
????ctx.lineTo(dx,?dy)
????//?颜色
????ctx.strokeStyle?=?data.strokeStyle
????//?沿着坐标点顺序的路径绘制直线
????ctx.stroke()
????//?关闭当前的绘制路径
????ctx.closePath()
}
圆:
/**
?*?画圆
?*/
export?function?drawCircle(
????ctx:?CanvasRenderingContext2D,
????data:?{
????????x:?number?//?圆心
????????y:?number
????????size:?number?//?半径
????????fillStyle:?string
????},
????scale:?number
)?{
????ctx.save()
????ctx.beginPath()
????ctx.arc(
????????~~(data.x?*?scale),
????????~~(data.y?*?scale),
????????~~(data.size?*?scale),
????????0,
????????~~(data.size?*?scale?*?Math.PI)
????)
????ctx.fillStyle?=?data.fillStyle
????ctx.fill()
????ctx.restore()
}
矩形:
/**
?*?画圆角矩形
?*/
export?function?drawRoundRect(
????ctx:?CanvasRenderingContext2D,
????data:?{
????????x:?number
????????y:?number
????????width:?number
????????height:?number
????????radius:?number
????????fillStyle:?string
????},
????scale:?number
)?{
????let?width?=?~~(data.width?*?scale)
????let?height?=?~~(data.height?*?scale)
????let?radius?=?~~(data.radius?*?scale)
????let?fillStyle?=?data.fillStyle
????ctx.save()
????ctx.translate(~~(data.x?*?scale),?~~(data.y?*?scale))
????ctx.beginPath()
????//?从右下角顺时针绘制,弧度从0到1/2PI
????ctx.arc(width?-?radius,?height?-?radius,?radius,?0,?Math.PI?/?2)
????//?矩形下边线
????ctx.lineTo(radius,?height)
????//?左下角圆弧,弧度从1/2PI到PI
????ctx.arc(radius,?height?-?radius,?radius,?Math.PI?/?2,?Math.PI)
????//?矩形左边线
????ctx.lineTo(0,?radius)
????//?左上角圆弧,弧度从PI到3/2PI
????ctx.arc(radius,?radius,?radius,?Math.PI,?(Math.PI?*?3)?/?2)
????//?上边线
????ctx.lineTo(width?-?radius,?0)
????//?右上角圆弧
????ctx.arc(width?-?radius,?radius,?radius,?(Math.PI?*?3)?/?2,?Math.PI?*?2)
????//?右边线
????ctx.lineTo(width,?height?-?radius)
????ctx.closePath()
????ctx.fillStyle?=?fillStyle
????ctx.fill()
????ctx.restore()
}
canvasBox.toBlob(async?data?=>?{
????if?(data)?{
????????//上传到7niu
????????this.url?=?await?uploadFile(data)
????}
})
import?{?saveAs?}?from?'file-saver'
saveAs(blob,?filename)
长按保存
包壳的H5是可以实现直接下载的,但为了交互了一致性,还是用了长按
接口生成,传入元素和位置等信息,接口直接生成返回图片。需要后端实现。(具体的性能和使用案例,欢迎讨论。)
不需要考虑兼容性等问题
不支持字数或字体类型过多,服务器压力较大(看具体实现方案),元素越多,接口越慢
这个方案其实也是用后端的逻辑实现了绘制元素,输出图片(过程中遇到的问题:如换行情况下需要计算字体高度(同一字体的中英文宽度不同)和后续元素的相对位置发生变化)
如果存在跨域图片无法下载,仔细阅读以下文字:
来源:https://segmentfault.com/q/10...
无特殊情况时,pc下使用dom-to-image即可。
至于h5,回顾了一下自己绘制海报的实现历程:
1.第三方库(兼容性问题,太久远以至于不记得发生了什么,只记得这个方案被驳回了)
2.接口绘制(服务器过载性能问题,速度过慢)
3.前端canvas绘制(+部分元素接口绘制好返回图片或svg,基本没什么兼容问题)(速度过慢)
4.第三方库(html2canvas,测试了一些版本环境的兼容性,但项目暂未上线,需要观察)
目前最优解看起来是html2canvas,但是有些效果无法实现,需要在还原度和性能上做取舍。
一些常用的app的海报生成是更快的,体验也更好,不知道是不是存在更好的解决方案?还是h5的限制?
【JS】节点截图的最终解决方案dom-to-image与html2canvas:https://blog.csdn.net/Mcky_Lo...
移动端H5页面截图:https://cloud.tencent.com/dev...
把DOM节点生成base64图片:https://www.jianshu.com/p/c5c...
更优雅地基于 canvas 在前端画海报:https://juejin.cn/post/684490...
canvas文本绘制自动换行、字间距、竖排等实现 https://www.zhangxinxu.com/wo...
图片跨域问题:https://segmentfault.com/q/1010000008648867
作者:JiaXinYihttps://segmentfault.com/a/1190000038910770
- EOF -
推荐阅读??点击标题可跳转
JavaScript实现网页截屏方法总结
H5基于canvas实现电子签名并生成PDF文档
小程序如何生成海报分享朋友圈
觉得本文对你有帮助?请分享给更多人
推荐关注「前端开发博客」,提升前端技能
回复「1024」领取前端进阶资料
如果觉得这篇文章还不错,来个【分享、点赞、在看】三连吧,让更多的人也看到~
cs