2023年11月8号记录:
这个功能花费了我差不多两个整天的时间才实现,非常折磨人,中途差点放弃uniapp+vue3+html2canvas+uQRcode即可实现功能
uniapp+vue3+html2canvas+canvas2image+uQRcode也可以实现,主要是实现的过程中以为需要这个插件,但最后发现不用也不影响,代码贴出来仅供参考
1、需求:
安卓系统+uniapp实现app端根据dom元素,生成图片其中包含头像,二维码等元素,
通过uni的系统分享,分享到微信,qq,朋友圈等
2、遇到的坑:
(1)uniapp不能直接操作dom元素,需要使用renderjs,在视图层操作dom元素
(2)vue3写法,callMethod定义的方法,无法在主script中触发,不能使用setup语法糖的写法
(3)base64如何转为本地路径,new plus.nativeObj.Bitmap(“test”)
(4)发包后报错,图片存在跨域问题 //不能使用静态图片,不能使用背景图
(5)uniapp中无法使用new Blob(),有些文章用了这个方法,但是我这里报错,Blob未定义
3、具体实现思路:
1、在模板区,需要生成画布的内容区域,最外面的标签上加上id=“poster”,需要通过id获取这个元素
2、在模板区,有背景图的话,不要写成静态的引入,比如: src=“@/static/image/bcg.png”,需要后端返才行。如果实在想尝试用本地的,可以试试写绝对路径,比如: src=“D:/app/my-demo/static/image/bcg.png”
3、在模板区,使用后端返的背景图,不能写background,用image标签接收,对应的样式代码,文末会贴
4、在模板区,要生成画布的内容区域里的所有image标签上可以加上crossorigin=“anonymous”,在我调试的过程中出现报错,
“Failed to execute ‘toDataURL’ on ‘HTMLCanvasElement’: Tainted canvases may not be exported”,百度搜到的最多的两种解答是图片跨域了或者画布被污染了,我还加了其他的东西,不确定具体是哪一个生效了,都会贴出来仅供参考
5、在模板区触发生成画布的点击事件上,需要注意的是要这样写,@click=“renderScript.emitData”
这个就要提到renderjs了uniapp-renderjs跳转地址
主要就是uniapp不能直接操作dom,它的视图层和逻辑层是分离的。renderjs是视图层,设置script节点的lang为renderjs,这个script中的就是视图层,而主script包裹的就是逻辑层。
lang=“renderjs”是固定的,而module是可以自定义的,相当于起个名字,比如我这里写的是renderScript,在模板区的点击事件里调用写在renderScript模块里面的方法,就是@click=“renderScript.emitData”
6、在renderjs中,引入html2canvas,调用html2canvas()返回base64图片,如果是在页面中直接展示的话,uniapp的image标签也接收base64格式的,直接展示是完全可以的,但是我需要的是生成图片,因为我要分享到微信,qq,朋友圈等
7、html2canvas插件的使用相对简单,文末代码会贴完整的,通过callMethod将数据发送到逻辑层,也就是要去主script标签中处理数据,我这里遇到一个大坑,vue2写法的可以跳过,主要是针对vue3的,那就是方法在vue3的script中无法触发,也不是完全不触发,打印传过来的数据也能打印出来,但是调用uni的api,比如uni.chooseImage/uni.saveFile等都不触发,最后才找到原因,因为vue3的setup语法糖的原因,在script标签上直接加上setup就是语法糖的写法,不需要再将所有的方法和定义的数据一样一样return出去, 不明白为什么语法糖的写法就不行,换成export default {setup(){ return{} }} 写法就可以了
7、在主script中处理base64格式转图片,const bitmap = new plus.nativeObj.Bitmap(“test”);
nativeObj这个是HTML5+的管理系统原生对象,HTML5+规范,点击这里跳转
(1)HTML5+,简称H5+,是一种基于HTML5的移动应用开发框架,它提供了许多原生功能的扩展,可以通过JavaScript进行调用
(2)plus.nativeObj.Bitmap:这是H5+中的一个原生对象,用于操作位图(图片)的相关功能。它提供了很多方法来加载、创建、保存和处理位图。
(3)new plus.nativeObj.Bitmap(“test”):这行代码创建了一个新的位图对象,并指定了名称为"test"。这里的"test"可以是图片的文件路径或一个相对路径,用于加载现有的图片,也可以是一个名称,用于创建一个新的空白位图。
这里截取的是保存示例,还有load加载图片示例,clear销毁图片示例,draw绘制图片示例等,可以直接去文档上看更详细的
8、const url = “_doc/” + new Date().getTime() + “.png”;可以直接用,当调用保存的时候,这个url就是临时路径名
9、调用uni.saveImageToPhotosAlbum,将临时路径存放到系统相册里
10、调用plus.share.sendWithSystem(),好处就是不需要集成三方SDK,缺点就是无法分享为微信小程序
这里有个注意点,参数pictures数组中的图片仅支持本地路径,所以bitmap的save方法一定要调用,先保存到本地相册,然后才能获取到。我之前想省略这一步,不想每次操作的时候都要保存一张照片,然后就会报错,报“资源获取失败”的错。
11、关于生成二维码的插件,如果没用特殊的要求,直接在Hbuilder的插件市场搜uQRcode,我找插件的时候发现这款插件下载量非常高,维护的挺好的,使用也很简单
示例代码贴的非常全,我就是直接复制拿过来用的
uniapp+vue3+html2canvas+uQRcode实现的完整代码:
npm i html2canvas
<template>
<view class="container" id="poster">
//接口获取背景图片的代码没写,就是非常简单的调接口,拿图片地址
<image :src="data.bcgPng" mode="" class="img-bcg" crossorigin="anonymous"></image>
//需要生成画布的内容区域.....
<view>
<image class="avatar" :src="data.avatar" mode="aspectFill" crossorigin="anonymous"></image>
<canvas id="qrcode" canvas-id="qrcode" style="width: 160px;height: 160px;margin:auto"></canvas>
</view>
</view>
<view class="bottom-box" @click="renderScript.emitData">
<view class="text">
点击分享
</view>
</view>
</template>
css样式,关于背景图
.img-bcg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1;
}
<script module="renderScript" lang="renderjs">
import html2canvas from 'html2canvas';
export default {
data() {
return {}
},
mounted() {},
methods: {
// 发送数据到逻辑层
emitData(e, ownerVm) {
const dom = document.getElementById('poster')
html2canvas(dom, {
width: dom.clientWidth, //dom 原始宽度
height: dom.clientHeight,
scrollY: 0, // html2canvas默认绘制视图内的页面,需要把scrollY,scrollX设置为0
scrollX: 0,
useCORS: true, //支持跨域
allowTaint: true, // 允许跨域图片
}).then((canvas) => {
//callMethod将数据发送到主script中
ownerVm.callMethod('receiveRenderData', canvas.toDataURL('image/png'))
});
}
}
};
</script>
<script>
import {
onReady
} from "@dcloudio/uni-app";
import UQRCode from '../../uni_modules/Sansnn-uQRCode/js_sdk/uqrcode/uqrcode.js';
export default {
setup() {
onReady(() => {
// 获取uQRCode实例
var qr = new UQRCode();
// 设置二维码内容
// qr.data = "https://uqrcode.cn/doc";
qr.data = "uid200";
// 设置二维码大小,必须与canvas设置的宽高一致
qr.size = 200;
// 调用制作二维码方法
qr.make();
// 获取canvas上下文
var canvasContext = uni.createCanvasContext('qrcode', this); // 如果是组件,this必须传入
// 设置uQRCode实例的canvas上下文
qr.canvasContext = canvasContext;
// 调用绘制方法将二维码图案绘制到canvas上
qr.drawCanvas();
})
function receiveRenderData(val) {
var base64 = val;
const bitmap = new plus.nativeObj.Bitmap("test");
bitmap.loadBase64Data(base64, function() {
const url = "_doc/" + new Date().getTime() + ".png"; // url为时间戳命名方式
bitmap.save(url, {
overwrite: true, // 是否覆盖
quality: 'quality' // 图片清晰度
}, (i) => {
uni.saveImageToPhotosAlbum({
filePath: url,
success: function() {
plus.share.sendWithSystem({
content: '分享内容',
type: 'image',
pictures: [url]
}, function() {
console.log('分享成功');
}, function(e) {
console.log('分享失败:' + JSON.stringify(e));
});
bitmap.clear()
}
});
}, (e) => {
uni.showToast({
title: '图片保存失败',
icon: 'none'
})
bitmap.clear()
});
}, (e) => {
uni.showToast({
title: '图片保存失败',
icon: 'none'
})
bitmap.clear()
});
}
return {
receiveRenderData,
};
}
}
</script>
uniapp+vue3+html2canvas+uQRcode+canvas2image实现的完整代码,仅供参考:
<template>
<view class="container" id="poster">
//需要生成画布的内容区域.....
<canvas id="qrcode" canvas-id="qrcode" style="width: 160px;height: 160px;margin:auto"></canvas>
</view>
//canvas2image会直接在页面中生成canvas画布绘制出来的图片
//如果需要在页面中展示,可以使用这个插件,另行优化
//uniapp提供的image标签也能接收base格式,所以这个canvas2image没在我这里发挥最大的价值
<view class="imageShow" id="Image" style="display: none;">
</view>
<view class="bottom-box" @tap="renderScript.emitData">
<view class="text">
点击分享
</view>
</view>
</template>
<script module="renderScript" lang="renderjs">
import html2canvas from 'html2canvas';
import Canvas2Image from 'canvas2image'
export default {
data() {
return {}
},
mounted() {},
methods: {
// 发送数据到逻辑层
emitData(e, ownerVm) {
let domObj = document.getElementById("poster");
//获取到DOM节点的位置
let width = domObj.offsetWidth;
let height = domObj.offsetHeight;
//DOM元素的宽高
let canvas = document.createElement("canvas")
//创建canvas
let scale = 5
//放大比例设置5倍
canvas.width = width * scale
canvas.height = height * scale
//画板的宽高
let options = {
logging: true,
//日志开关,在控制台可以查看html2canvas的内部执行流程
width: width,
height: height,
//避免下载不全
useCORS: true,
//【重要】开启跨域配置
scale: scale,
canvas: canvas,
//自定义属性
}
html2canvas(domObj, options).then((canvas) => {
let context = canvas.getContext('2d')
//关闭锯齿
context.mozImageSmoothingEnabled = false
context.webkitImageSmoothingEnabled = false
context.msImageSmoothingEnabled = false
context.imageSmoothingEnabled = false
let img = Canvas2Image.convertToJPEG(canvas, canvas.width, canvas.height)
// 这就是把canvas转化为图片
document.getElementById('Image').appendChild(img);
//展示图片的DOM节点
img.style.width = canvas.width / 5 + 'px';
img.style.height = canvas.height / 5 + 'px';
const src = img.getAttribute('src')
ownerVm.callMethod('receiveRenderData', src)
})
}
}
};
</script>
<script>
import {
onReady
} from "@dcloudio/uni-app";
import UQRCode from '../../uni_modules/Sansnn-uQRCode/js_sdk/uqrcode/uqrcode.js';
export default {
setup() {
onReady(() => {
// 获取uQRCode实例
var qr = new UQRCode();
// 设置二维码内容
// qr.data = "https://uqrcode.cn/doc";
qr.data = "uid200";
// 设置二维码大小,必须与canvas设置的宽高一致
qr.size = 200;
// 调用制作二维码方法
qr.make();
// 获取canvas上下文
var canvasContext = uni.createCanvasContext('qrcode', this); // 如果是组件,this必须传入
// 设置uQRCode实例的canvas上下文
qr.canvasContext = canvasContext;
// 调用绘制方法将二维码图案绘制到canvas上
qr.drawCanvas();
})
function receiveRenderData(val) {
var base64 = val;
const bitmap = new plus.nativeObj.Bitmap("test");
bitmap.loadBase64Data(base64, function() {
const url = "_doc/" + new Date().getTime() + ".png"; // url为时间戳命名方式
bitmap.save(url, {
overwrite: true, // 是否覆盖
quality: 'quality' // 图片清晰度
}, (i) => {
uni.saveImageToPhotosAlbum({
filePath: url,
success: function() {
plus.share.sendWithSystem({
content: '分享内容',
type: 'image',
pictures: [url]
}, function() {
console.log('分享成功');
}, function(e) {
console.log('分享失败:' + JSON.stringify(e));
});
bitmap.clear()
}
});
}, (e) => {
uni.showToast({
title: '图片保存失败',
icon: 'none'
})
bitmap.clear()
});
}, (e) => {
uni.showToast({
title: '图片保存失败',
icon: 'none'
})
bitmap.clear()
});
}
return {
receiveRenderData,
};
}
}
</script>