前言

最近遇到这样一个需求:
需求

点击下载海报的时候,生成一张海报,并且可以下载。经过一番摸索和踩坑,终于实现了这个功能。再这里记录一下!

代码结构

HTML

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<div
id="poster-pic"
>
<div class="poster-pic-title"></div>
<img
class="goods-img"
:src="picUrl"
alt="加载失败"
>
<div class="poster-pic-subtitle">
活动名字
</div>
<div class="poster-pic-footer">
<div class="left">
<div class="top">
<span>$999</span>
<span>$999</span>
</div>
<div class="buttom">
<div>活动时间</div>
<div>2020.11.11-2020.11.11</div>
</div>
</div>
<div class="right">
<img
class="code-img"
:src="qrcodeUrl"
alt="加载失败"
>
</div>
</div>
</div>
<span
slot="footer"
class="dialog-footer"
>
<el-button @click="download">下载二维码</el-button>
<el-button
type="primary"
@click="downloadPosters"
>下载海报</el-button>
</span>

页面大致就这样,其实页面怎么样并不重要,主要是在JS实现。

实现HTML页面保存为图片

html2canvas的用法

GitHub-Html2Canvas

实现保存为图片的第一步:HTML转换为Canvas

基于 html2canvas 可将一个元素渲染为 Canvas, 只需要简单的调用 html2canvas(element[, options]) 即可,然后返回一个含有 <canvas> 元素的 promise.

1
2
3
html2canvas(document.body.then((canvas)={
document.body.appendChild(canvas)
}))

实现保存为图片的第二步:Canvas转Image

上一步生成的 Canvas 即为包含目标元素的 <canvas> 元素对象,实现保存图片的目标只需要将 canvasimage 即可。我们选择使用 Canvas2Image.js 进行转换,但它实际上也只是 canvas.toDataUrl 的一个封装。

GitHub-Canvas2Image

注意事项!!重点:

  • 这个包请不要使用 npm 的方式引入,npm包没有更新,存在很多bug,包括模块未导出,下载图片没有后缀和名字的问题!
  • 请直接下载压缩包,把源代码文件放入项目中,导入使用!

生成图片清晰度优化

第一步

最终图片的清晰度 取决于 上面第一步中 html 转换 Canvas 的清晰度。

这里提高清晰度的基本原理是将 canvas 的属性 widthheight 属性放大,最后将 canvas 的 CSS样式 widthheight 设置为原来的1倍大小。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
downloadImg (params) {
let cntElem = document.querySelector(params)
let shareContent = cntElem
let width = shareContent.offsetWidth
let height = shareContent.offsetHeight
let canvas = document.createElement('canvas')
var scale = 2
canvas.width = width * scale
canvas.height = height * scale
canvas.getContext('2d').scale(scale, scale)
const ops = {
scale: scale,
width: width,
height: height,
}
html2canvas(shareContent, ops).then(canvas => {
let context = canvas.getContext('2d')
let img Canvas2Image.converToImage(canvas, canvas.width, canvas.height)
document.body.appendChild(imgs)
})
},

第二步

在第一步我们就可以解决通常情况下图片不清晰的问题,但是在我们的实际项目中,可能仍然存在一些十分尴尬的问题,比如大果粒一般的渲染效果。

这里我们采用一些优化策略:

  • 更改 百分比布局px 布局
  • 关闭 Canvas 默认的 抗锯齿 设置
  • 设置模糊元素的 widthheight 为素材的原有宽高,通过 scale 缩放。

基本原理:

  • 设置 px 为单位,避免样式二次计算导致的模糊
  • 默认情况下,Canvas 的抗锯齿是开启的,需要手动关闭来实现图像的锐化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
downloadImg (params) {
let cntElem = document.querySelector(params)
let shareContent = cntElem
let width = shareContent.offsetWidth
let height = shareContent.offsetHeight
let canvas = document.createElement('canvas')
var scale = 2
canvas.width = width * scale
canvas.height = height * scale
canvas.getContext('2d').scale(scale, scale)
const opts = {
scale: scale,
width: width,
height: height,
}
html2canvas(shareContent, opts).then(canvas => {
let context = canvas.getContext('2d')
// 重点:关闭抗锯齿
context.mozImageSmoothingEnabled = false
context.webkitImageSmoothingEnabled = false
context.msImageSmoothingEnabled = false
context.ImgSmoothingEnabled = false
})
},

含有跨域图片的配置(部分图片丢失的问题解决)

由于Canvas对于图片资源的同源限制,如果画布中包含跨域的图片资源会污染画布,造成生成图片样式混乱或者 html2canvas 方法不执行等问题。

我们需要在 配置中 开启跨域和禁止污染即可:

1
2
3
4
5
6
7
8
9
const opts = {
scale: scale,
width: width,
height: height,
useCORS: true, // 使用跨域
allowTaint: true, // 允许使用跨域资源
tainTest: false,
}
html2canvs(element,opts)

其他问题

文本丢失

可能使用了 html2canvas 不支持的CSS样式, 例如:display: -webkit-box

完整代码/流程演示

  • 安装 html2canvas
1
yarn add html2canvas

也可以直接下载 源文件 再进行导入。

  • 下载 canvas2image.js

在git仓库下载源文件后,进行导入,我这边是挂载到了全局方法

utils/index

1
2
3
4
5
6
import Canvas2Image from './canvas2image.js'
// ………………
export default {
// …………………………
Canvas2Image
}

mani.js

1
2
import util from '@/utils/index.js'
Vue.prototype.util = util
  • JS代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// npm 安装直接导入
import html2canvas from 'html2canvas'

/*
@params: dom节点-class/id
@name:下载之后的名字
*/
downloadImg (params, name) {
let cntElem = document.querySelector(params)
let shareContent = cntElem
let width = shareContent.offsetWidth
let height = shareContent.offsetHeight
let canvas = document.createElement('canvas')
var scale = 2
canvas.width = width * scale
canvas.height = height * scale
canvas.getContext('2d').scale(scale, scale)
const ops = {
scale: scale,
width: width,
height: height,
// 跨域配置
useCORS: true, // 使用跨域
allowTaint: true, // 允许使用跨域资源
tainTest: false,
backgroundColor: null
}
html2canvas(shareContent, ops).then(canvas => {
let context = canvas.getContext('2d')
context.mozImageSmoothingEnabled = false
context.webkitImageSmoothingEnabled = false
context.msImageSmoothingEnabled = false
context.ImgSmoothingEnabled = false
// 保存图片,可以保存PNG,JPEG等,调用对应api即可
this.util.Canvas2Image.saveAsPNG(canvas, canvas.width, canvas.height, name)
})
}

// 调用方法
downloadCode () {
this.downloadImg('.code-img', '二维码')
},
downloadPost () {
this.downloadImg('#poster-pic', '海报')
},