前言
最近遇到这样一个需求:
点击下载海报的时候,生成一张海报,并且可以下载。经过一番摸索和踩坑,终于实现了这个功能。再这里记录一下!
代码结构
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>
元素对象,实现保存图片的目标只需要将 canvas
转 image
即可。我们选择使用 Canvas2Image.js
进行转换,但它实际上也只是 canvas.toDataUrl
的一个封装。
GitHub-Canvas2Image
注意事项!!重点:
- 这个包请不要使用
npm
的方式引入,npm包没有更新,存在很多bug,包括模块未导出,下载图片没有后缀和名字的问题!
- 请直接下载压缩包,把源代码文件放入项目中,导入使用!
生成图片清晰度优化
第一步
最终图片的清晰度 取决于 上面第一步中 html
转换 Canvas
的清晰度。
这里提高清晰度的基本原理是将 canvas
的属性 width
和 height
属性放大,最后将 canvas
的 CSS样式 width
和 height
设置为原来的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 默认的 抗锯齿 设置
- 设置模糊元素的
width
和 height
为素材的原有宽高,通过 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
完整代码/流程演示
也可以直接下载 源文件 再进行导入。
在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
|
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
| import html2canvas from 'html2canvas'
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 this.util.Canvas2Image.saveAsPNG(canvas, canvas.width, canvas.height, name) }) }
downloadCode () { this.downloadImg('.code-img', '二维码') }, downloadPost () { this.downloadImg('#poster-pic', '海报') },
|