通过点击“邀请好友返现赚不停”,生成海报,有两种方案去实现以上效果:
本文主要介绍了通过纯前端的canvas生成海报的方式:
在展示海报的过程中,首先我们需要使用uView框架提供的popup组件,不过这些细节在这里并不重要,因为下面的JavaScript代码是通用的,只是样式上可能会有些不同,但是你是uni-app的项目,主要步骤如下:
<view class="share-btn">
<button @click="handleShareClick">邀请好友返现赚不停button>
view>
<u-popup @close="closePoster" :show="isPosterShow" mode="center" bgColor="transparent" :safeAreaInsetBottom="false"
round="20" :customStyle="{ margin: '0 auto', position: 'relative' }">
<canvas v-if="isPosterShow" :disable-scroll="true" canvas-id="mycanvas"
style="width: 604rpx;height:1080rpx;">canvas>
<view v-if="isPosterShow" class="poster-btn"><button @click="savaImgLocalClick">保存图片到本地button>view>
u-popup>
import { ref } from "vue"
import { onLoad } from "@dcloudio/uni-app"
let isPosterShow = ref<boolean>(false)
pixelRatio.value = device.pixelRatio
onLoad(async (options: any) => {
// 获取当前设备和设计图的比例
let device = uni.getSystemInfoSync()
wid.value = device.windowWidth / 750
pixelRatio.value = device.pixelRatio
})
const handleShareClick = () => {
drawPoster()
}
// 生成海报
const drawPoster = async () => {
isPosterShow.value = false
uni.showLoading({
title: '海报生成中...'
})
let ctx = uni.createCanvasContext("mycanvas")
uni.getImageInfo({
src: '获取你的网络图片地址,接口返回的地址和自己写死一个网络地址都可以',
success: async (imagePoster) => {
// 然后回返回一个本地路径画出来,这个图片的大小和canvas的宽高是一致的,所以坐标从0,0开始;
// drawImage就渲染出海报图了,604 * wid.value 因为我们要转化单位,所以在初始化的时候换算的比例就要在这里用到
ctx.drawImage(imagePoster.path, 0, 0, 604 * wid.value, 1080 * wid.value)
// 加载完后,加载头像和名字
getCanvasAvatar(ctx)
}
})
}
// 获取canvas头像
const getCanvasAvatar = (ctx: any) => {
// userInfo.value.nickname 这是我从本地取出来的数据,这个要替换成你拿的微信昵称;
// 因为微信昵称的名字会很长,在这里我们对拿到的昵称进行一个截取,然后用...取代;
let tip = userInfo.value.nickname.length >= 10 ? userInfo.value.nickname.slice(0, 11) + '...' : userInfo.value.nickname
// userInfo.value.avatar 这是你拿到的头像,也要给到getImageInfo,让它给你转成本地路径,然后进行渲染;
uni.getImageInfo({
src: userInfo.value.avatar,
success(res) {
// 给这个头像画成圆角,这个drawCircleImage方法,我放在下面tool中
drawCircleImage(ctx, res.path, 40, 40, 20)
ctx.setFillStyle("#000") // 为文字设置颜色
ctx.font = "18px PingFang SC-Medium" // 为文字设置字体大小和字体样式
ctx.fillText(tip, 70, 45) // 填充文字和给文字对应的位置
// 获取小程序码
getCanvasSunCode(ctx)
}
})
}
// 获取太阳码
const getCanvasSunCode = async (ctx: any) => {
// 因为我获取的太阳码是base64的,在这需要通过removeSave删除一下,主要是为了清理缓存
// 但是我这边是有错误的,这个并不影响海报的构建,你也可以注释掉,removeSave方法,我放在下面tool中
await removeSave()
// 将你获取的sunCode.value 传给base64Save方法,它将给你返回一个本地路径,这个sunCode.value,是服务端生成好的一个太阳码,里面可能存储了一些参数
let base64Path: any = await base64Save(sunCode.value) // base64Save方法,我放在下面tool中
// codePosition.value是后台返回的数据,里面包含了,这个小程序码的大小和位置,当然你如果你的二维码是固定的,你也可以写死如:ctx.drawImage(base64Path, 236, 236,443, 837)
ctx.drawImage(base64Path, codePosition.value[0] * wid.value, codePosition.value[1] * wid.value, codePosition.value[2] * wid.value, codePosition.value[3] * wid.value)
//通过 draw()将我们的,背景图片、头像和太阳码渲染出来,这个很关键,因为它是负责画处理你drawImage中的内容
ctx.draw()
// 将加载的弹窗隐藏
uni.hideLoading()
// 在点击按钮之前将isPosterShow.value = false,等让所有的canvas执行完成后,把popup展示出来,因为我们的popup中有保存海报的按钮,如果不这样的话,按钮会提前展示,但是这个时候海报还没展示出来
isPosterShow.value = true
}
const savaImgLocalClick = () => {
uni.showLoading({
title: '保存图片中...'
})
// 保存canvas为图片,width,height,destWidth,destHeight 一般为默认就可以,可以尝试下,看看会有什么区别,我这边并没发现什么区别
uni.canvasToTempFilePath({
canvasId: 'mycanvas',
quality: 1,
width: 604, // 画布宽度(默认为canvas宽度-x)
height: 1080, // 画布高度(默认为canvas高度-y)
destWidth: 604 * pixelRatio.value, // 输出图片宽度(默认为 width * 屏幕像素密度)
destHeight: 1080 * pixelRatio.value, // 输出图片高度(默认为 height * 屏幕像素密度)
complete(res) {
console.log(res)
downloadPoster.value = res.tempFilePath
// usrinfo.bgurl = res.tempFilePath
uni.authorize({
scope: 'scope.writePhotosAlbum',
success: () => {
//保存
uni.saveImageToPhotosAlbum({
filePath: downloadPoster.value, // 保存也是只能用本地路径
success() {
uni.hideLoading()
uni.showToast({
title: '海报已保存至本地!',
icon: 'none'
})
},
fail() {
uni.showToast({
title: '海报保存失败!',
icon: 'none'
})
}
})
},
fail: () => {
uni.showModal({
content: '由于您拒绝保存到您手机里,无法进行保存,点击确定去授权',
success: (res) => {
if (res.confirm) {
/* 这个就是打开设置的API*/
uni.openSetting({
success: () => {
// console.log(res1.authSetting);
}
})
}
}
})
}
})
}
})
}
// 画圆角
export function drawCircleImage(ctx: any, img: string, x: number, y: number, radius: number) {
ctx.save()
const size = 2 * radius
ctx.arc(x, y, radius, 0, 2 * Math.PI)
ctx.clip()
ctx.drawImage(img, x - radius, y - radius, size, size)
ctx.restore()
}
// base64转path
export function base64Save(base64File: any) { //base64File 需要加前缀
const fsm = wx.getFileSystemManager()//获取全局文件管理器
let extName = base64File.match(/data:\S+\/(\S+);/)
if (extName) {
//获取文件后缀
extName = extName[1]
}
//获取自1970到现在的毫秒 + 文件后缀 生成文件名
const fileName = Date.now() + '.' + extName
return new Promise((resolve, reject) => {
//写入文件的路径
const filePath = wx.env.USER_DATA_PATH + '/' + fileName
fsm.writeFile({
filePath,
data: base64File.replace(/^data:\S+\/\S+;base64,/, ''), //替换前缀为空
encoding: 'base64',
success: () => {
console.log(filePath, '222')
resolve(filePath)
},
fail() {
reject('写入失败')
},
})
})
}
// 删除base64
export function removeSave(FILE_BASE_NAME = 'tmp_base64src', format = 'png') {
return new Promise((resolve) => {
// 把文件删除后再写进,防止超过最大范围而无法写入
const fsm = uni.getFileSystemManager() //文件管理器
const FILE_BASE_NAME = 'tmp_base64src'
const format = 'png'
const filePath = `${wx.env.USER_DATA_PATH}/${FILE_BASE_NAME}.${format}`
fsm.unlink({
filePath,
success(res) {
console.log('文件删除成功')
resolve(true)
},
fail(e) {
console.log('readdir文件删除失败:', e)
resolve(true)
}
})
})
}
1、使用canvas的场景需要根据具体需求而定,如果必须要使用图片的原生转发属性,那就需要后端来生成然后给我们返回图片。
2、如果加载出来的海报的页面还能滚动,需要将海报弹出的时候,整个外层样式设置为overflow:hidden
,关闭的时候设置为overflow:unset
。
3、uni.getImageInfo()
可以把网络地址转化为本地地址。
4、uni.drawImage()
最后一定要执行ctx.draw()
方法