需求描述
在一个页面中底部有个保存图片的功能,点击能够保存一张生成的自定义表格图片。
第一眼见到这个需求 自己会出现了两个问题
- 如何去处理图片中的自定义内容以及样式
- 如何将自定义内容转化成图片
至于保存图片,uniapp有对应的api去实现uni.saveImageToPhotosAlbum,不做赘述
实现效果展示
下面就是生成的图片
实现步骤
大致划分成以下几个步骤
1、需要在目标页面画出图片内容的html代码,并置于于屏幕可视区域外形成隐藏的效果(不是display:none隐藏元素)
2、需要将html转成canvas的形式,再通过toDataURL方法转成base64图片形式
3、将base64转换成临时图片路径,即可展示出,并下载
已知需要用到两个插件来帮助实现
一个是html转canvas的插件 html2canvas可通过npm下载
npm install html2canvas -D
一个是将base64图片格式转本地地址的Dcloud插件image-tools
1、画需要生成图片的元素
即就是生成图片的样式 还是需要我们自己来搞出来。可以封装成一个组件形式,这里就不贴代码,因为这不是核心逻辑,况且大家需要生成图片的样式都不一样,唯一需要注意的是,这块元素是要脱离文档流,并置于屏幕外,不让用户看到
将生成图片的代码封装成一个组件,如下 需要声明一个id 为后续转canvas做准备
<KitcalculatorTable id="pagePoster"/>
并将这块区域通过固定定位置于可视区外,这样就看不到了,不能用v-if或者v-show、display:none去隐藏,这样在后续html转canvas是失败的,无法获取到该区域元素
position: fixed;
top: -999999rpx;
background-color: #fff;
padding: 30rpx;
2将html转成canvas的形式,再转成base64图片形式
<template><view class="html2Canvas" id="pagePoster"><view class="table"><view class="kitCalculatorName">{{ pageData.kitCalculatorName }}</view><KitcalculatorTable :tableData="tableData" :isSumData="true" /><view class="average-data"><view style="margin-right: 50rpx">件套平均价格:{{ pageData.averagePriceOneDesc }}</view><view v-if="visiblePrice2">平均价格2:{{ pageData.averagePriceTwoDesc }}</view></view></view><view class="bottom-logo"><img class="code" :src="codeImage" /><view><view class="">报盘计算器,长按识别二维码使用</view><img class="logo" :src="logoImage" /></view></view></view><view class="muji-button" style="margin: 0 24rpx" @click="canvasImage.generateImageSave">保存图片</view><view class="muji-button" @click="canvasImage.generateImageShare">分享</view>
</template>
<script lang="renderjs" module="canvasImage">
import html2canvas from 'html2canvas'
export default {mounted() {},methods: {generateImageSave(){this.generateImage('save')},generateImageShare(){this.generateImage('share')},// 生成图片需要调用的方法generateImage(methodType) {this.$ownerInstance.callMethod('openLoading', '正在生成图片~')setTimeout(() => {const dom = document.getElementById('pagePoster') // 需要生成图片内容的 dom 节点html2canvas(dom, {width: dom.clientWidth, //dom 原始宽度height: dom.clientHeight,scrollY: 0, // html2canvas默认绘制视图内的页面,需要把scrollY,scrollX设置为0scrollX: 0,useCORS: true, //支持跨域// scale: 2, // 设置生成图片的像素比例,默认是1,如果生成的图片模糊的话可以开启该配置项}).then((canvas) => {// 生成成功// html2canvas 生成成功的图片链接需要转成 base64位的urlthis.$ownerInstance.callMethod('receiveRenderData',canvas.toDataURL('image/png'))}).catch(err=>{this.$ownerInstance.callMethod('_errAlert',`【生成图片失败,请重试】${err}`)})}, 300)},}
}
</script>
<script>
import { pathToBase64,base64ToPath} from 'image-tools';
export default {props: {pageData: {type: Object,default: () => {return {};},},},data() {return {temUrl: '', //生成的临时路径图片codeImage: '', // 件套二维码图片logoImage: '', // logo图片};},created() {// 处理静态图片 转为base64this.actionImage();},methods: {// 处理已知静态图片 转base64格式actionImage() {this.turnBase64Image(require('@/static/logo/down-code.png'), 'codeImage');this.turnBase64Image(require('@/static/logo/muji-logo.png'), 'logoImage');},// 将图片转为base 64 位urlturnBase64Image(img, key) {uni.getImageInfo({src: img,success: (image) => {pathToBase64(image.path).then((base64) => {this[key] = base64;}).catch((error) => {console.log('转换失败:', error);});},fail: (err) => {console.log('将本地图片转为base 64报错:', err);},});},/* 将base64 位的图片路径转换为 临时路径 */loadBase64Url(imageStr) {const that = this;base64ToPath(imageStr).then((path) => {this.temUrl = path;this.closeLoading()this.saveImg()}).catch((error) => {console.error('临时路径转换出错了:', error);});},// 获取生成的base64 图片路径receiveRenderData(val) {const url = val.replace(/[\r\n]/g, ''); // 去除base64位中的空格this.loadBase64Url(url);},// 报错alert_errAlert(content) {uni.showModal({title: '提示',content: content,});},saveImg() {uni.saveImageToPhotosAlbum({filePath: this.temUrl,success: function () {uni.showToast('保存图片成功');}});},},
};
</script>
需要注意的两点是,这里要新建一个script节点,将语言改成renderjs的形式 在这之内做html转canvas的操作
再就是需要把生成图片中的已知静态图片 如下载二维码、企业logo转换成base64的形式
保存图片
最后就是点击保存后,调用canvasImage下的generateImage去生成图片,拿到base64格式图片通过image-tools的base64ToPath方法去转成临时路径再下载就ok了
题外话
这里涉及到renderjs的通讯知识,还是有点坑的
比如在renderjs模块调用script的方法是可以直接
this.$ownerInstance.callMethod('script内方法名',需要带的参数)
目前只了解可以在dom去调用renderjs方法,在script内貌似不能直接用。大家就要根据需求去灵活使用这个东西