✨uniapp实现生成海报并保存至相册组件,u-popup可以根据自己所使用的组件进行替换
这里主要讲的是JS部分,css和元素相关的就不展开赘述了,下方先给大伙看看效果图,图的下方有代码讲解,最下方有完整代码,如各位大神发现问题后请友好的交流勿喷。
⏳示例图
⏳ 图片引用
想要用cavans生成海报,首先要解决的是,将图片素材引入至canvas画布中,小程序的canvas没有办法直接使用网络图片,所以首先要把网络中的图片获取到,并已文件的格式存入内存中,利用uniapp的api简单的封装了一个获取图片的函数
// 下载图片
urlToFile(url) {
return new Promise((resolve) => {
uni.getImageInfo({
src: url,
success(res) {
resolve(res.path)
},
fail(res) {
console.log('fail -> res', res)
uni.showToast({
title: '网络异常',
duration: 2000,
icon: 'none'
})
this.$emit('close')
}
})
})
},
⏳转换rpx
拿到图片后,还有个问题要处理,那就是尺寸,在小程序中用的rpx为样式的单位。但是在canvas中却没有rpx的单位,所以我们要处理一下px转为rpx,这样就能解决不同分辨率中,样式大小不同的问题。一样的一个简单的转换函数
// rpx转px
rpxToPx(rpx) {
return (rpx / 750) * uni.getSystemInfoSync().windowWidth
},
⏳绘制函数
图片和单位的问题解决后,就要开始绘制海报了,这里需要根据ui效果图,去自行布局,本文档中只是作为一个例子。
在开发中发现canvas生成一倍图是比较模糊的,所以这里要定义一个倍数来放大canvas画布,使生成的图片更加的清晰,也就是代码中的canvasMultiple
,canvasMultiple
变量在data
中有定义,如果有些变量看着不明白,可以先看最下方的完整代码
async creatCanvas() {
if (this.posterImage) return
// 创建canvas对象
uni.showLoading({ title: '生成专属海报' })
this.canvas = uni.createCanvasContext('canvas', this)
// 这里是我自己的方法下载图片
// canvas中的插入的图片不能是网络地址只能是下载到本地的
const qrCode = await this.urlToFile(`${this.imgUrl}poster-code.png`)
const imgBg = await this.urlToFile(`${this.imgUrl}poster-bg.png`)
const logoIcon = await this.urlToFile(`${this.imgUrl}poster-logo.png`)
const fontImage = await this.urlToFile(`${this.imgUrl}poster-font.png`)
const { canvasMultiple, rpxToPx } = this
// 插入背景图 第2 3 4 5参数单位是px的所以我们要做适配 rpx转换为px 可以自定义方法 也可以使用uniapp中的方法
this.canvas.drawImage(imgBg, 0, 0, rpxToPx(590 * canvasMultiple), rpxToPx(976 * canvasMultiple))
// 将二维码插入到canvas中
this.canvas.drawImage(qrCode, rpxToPx(460 * canvasMultiple), rpxToPx(780 * canvasMultiple), rpxToPx(100 * canvasMultiple), rpxToPx(100 * canvasMultiple))
// 插入logo
this.canvas.drawImage(logoIcon, rpxToPx(74 * canvasMultiple), rpxToPx(114 * canvasMultiple), rpxToPx(84 * canvasMultiple), rpxToPx(64 * canvasMultiple))
this.canvas.drawImage(fontImage, rpxToPx(80 * canvasMultiple), rpxToPx(334 * canvasMultiple), rpxToPx(351 * canvasMultiple), rpxToPx(53 * canvasMultiple))
this.canvas.fillStyle = '#ffffff'
this.canvas.strokeStyle = '#ffffff'
// this.canvas.font = `bold ${rpxToPx(40)}px`
this.canvas.font = `normal normal 500 40px 微软雅黑`
this.canvas.setFontSize(rpxToPx(40 * canvasMultiple))
this.canvas.fillText(this.nickName, rpxToPx(80 * canvasMultiple), rpxToPx(274 * canvasMultiple))
this.canvas.font = `normal normal 600 26px 微软雅黑`
this.canvas.setFontSize(rpxToPx(26 * canvasMultiple))
this.canvas.fillText('立即扫码', rpxToPx(80 * canvasMultiple), rpxToPx(850 * canvasMultiple))
this.canvas.fillStyle = 'rgba(255, 255, 255, 0.43)'
this.canvas.strokeStyle = 'rgba(255, 255, 255, 0.43)'
this.canvas.font = `normal normal 400 26px 微软雅黑`
this.canvas.setFontSize(rpxToPx(26 * canvasMultiple))
this.canvas.fillText('生成的热乎的海报', rpxToPx(80 * canvasMultiple), rpxToPx(900 * canvasMultiple))
// 成功之后
this.canvas.draw(true, () => {
setTimeout(() => {
// 将canvas转换成图片
uni.canvasToTempFilePath({
x: 0,
y: 0,
canvasId: 'canvas',
fileType: 'png',
quality: 1,
success: (success) => {
console.log('success', success)
this.posterImage = success.tempFilePath
uni.hideLoading()
// this.canvas.draw()
},
fail: (e) => {
uni.showToast({
title: '海报生成失败',
icon: 'none'
})
this.close()
console.log('eeee', e)
}
}, this)
}, 500)
})
},
⏳保存至相册
小程序中提供了将图片保存至相册的能力,所以这里只需要把刚刚canvas绘制的海报图片,利用uniapp的saveImageToPhotosAlbum
存至相册中就可以了。
savePoster() {
console.log('savePoster', this.posterImage)
uni.saveImageToPhotosAlbum({
filePath: this.posterImage,
success: () => {
// uni.hideLoading();
uni.showToast({
title: '保存成功',
icon: 'none'
})
this.close()
},
fail: () => {
uni.hideLoading()
this.$toast({ title: '相册功能未授权,无法保存' })
},
complete: () => {
}
})
}
⏳至此生成海报并可以保存至相册的函数都已经完成,组装至一起既可以完成需求
完整代码
<template>
<u-popup :show="displayPoster" mode="center" @close="close" :overlayOpacity="0.8" :closeOnClickOverlay="true" :safeAreaInsetBottom="false">
<view :class="posterImage ? 'poster' : 'poster-hidden'">
<canvas v-if="posterImage === ''" canvas-id="canvas" class="poster-canvas" :style="{width:'1180rpx',height: '1952rpx'}"></canvas>
<template v-if="posterImage">
<image class="poster-image" :src="posterImage"></image>
<view class="save-btn">
<u-button :customStyle="saveButtonStyle" type="primary" text="保存海报" @click.stop="savePoster"></u-button>
</view>
</template>
<view class="close-icon" v-if="posterImage" @click.stop = "close" >
<image class="icon" :src="imgUrl+'close-white.png'"></image>
</view>
</view>
</u-popup>
</template>
<script>
export default {
name: 'PosterDialog',
props: {
displayPoster: {
type: Boolean,
required: true
}
},
data() {
return {
imgUrl: `${_SWF_CONFIG.TEMPLATE_URL}${_SWF_CONFIG.TEMPLATE_PATH}/images/`,
canvasMultiple: 2,
saveButtonStyle: {
background: '#0C1C2B',
border: '1px solid #0C1C2B',
width: '280rpx',
height: '88rpx',
fontSize: '32rpx',
fontWeight: '500'
},
posterImage: '',
nickName: ''
}
},
watch: {
displayPoster: {
handler(newValue) {
console.log('newValue', newValue)
const { displayName } = uni.getStorageSync('userInfo')
this.nickName = displayName
console.log('nickName', this.nickName)
if (newValue) {
this.creatCanvas()
}
},
immediate: true
}
},
methods: {
async creatCanvas() {
if (this.posterImage) return
// const that = this
// 创建canvas对象
uni.showLoading({ title: '生成专属海报' })
this.canvas = uni.createCanvasContext('canvas', this)
// 这里是我自己的方法下载图片
// canvas中的插入的图片不能是网络地址只能是下载到本地的
const qrCode = await this.urlToFile(`${this.imgUrl}poster-code.png`)
const imgBg = await this.urlToFile(`${this.imgUrl}poster-bg.png`)
const logoIcon = await this.urlToFile(`${this.imgUrl}poster-logo.png`)
const fontImage = await this.urlToFile(`${this.imgUrl}poster-font.png`)
const { canvasMultiple, rpxToPx } = this
// 插入背景图 第2 3 4 5参数单位是px的所以我们要做适配 rpx转换为px 可以自定义方法 也可以使用uniapp中的方法
this.canvas.drawImage(imgBg, 0, 0, rpxToPx(590 * canvasMultiple), rpxToPx(976 * canvasMultiple))
// 将二维码插入到canvas中
this.canvas.drawImage(qrCode, rpxToPx(460 * canvasMultiple), rpxToPx(780 * canvasMultiple), rpxToPx(100 * canvasMultiple), rpxToPx(100 * canvasMultiple))
// 插入logo
this.canvas.drawImage(logoIcon, rpxToPx(74 * canvasMultiple), rpxToPx(114 * canvasMultiple), rpxToPx(84 * canvasMultiple), rpxToPx(64 * canvasMultiple))
this.canvas.drawImage(fontImage, rpxToPx(80 * canvasMultiple), rpxToPx(334 * canvasMultiple), rpxToPx(351 * canvasMultiple), rpxToPx(53 * canvasMultiple))
this.canvas.fillStyle = '#ffffff'
this.canvas.strokeStyle = '#ffffff'
// this.canvas.font = `bold ${rpxToPx(40)}px`
this.canvas.font = `normal normal 500 40px 微软雅黑`
this.canvas.setFontSize(rpxToPx(40 * canvasMultiple))
this.canvas.fillText(this.nickName, rpxToPx(80 * canvasMultiple), rpxToPx(274 * canvasMultiple))
this.canvas.font = `normal normal 600 26px 微软雅黑`
this.canvas.setFontSize(rpxToPx(26 * canvasMultiple))
this.canvas.fillText('立即扫码', rpxToPx(80 * canvasMultiple), rpxToPx(850 * canvasMultiple))
this.canvas.fillStyle = 'rgba(255, 255, 255, 0.43)'
this.canvas.strokeStyle = 'rgba(255, 255, 255, 0.43)'
this.canvas.font = `normal normal 400 26px 微软雅黑`
this.canvas.setFontSize(rpxToPx(26 * canvasMultiple))
this.canvas.fillText('生成的热乎的海报', rpxToPx(80 * canvasMultiple), rpxToPx(900 * canvasMultiple))
// 成功之后
this.canvas.draw(true, () => {
setTimeout(() => {
// 讲canvas转换成图片
uni.canvasToTempFilePath({
x: 0,
y: 0,
canvasId: 'canvas',
fileType: 'png',
quality: 1,
success: (success) => {
console.log('success', success)
this.posterImage = success.tempFilePath
uni.hideLoading()
// this.canvas.draw()
},
fail: (e) => {
uni.showToast({
title: '海报生成失败',
icon: 'none'
})
this.close()
console.log('eeee', e)
}
}, this)
}, 500)
})
},
// rpx转px
rpxToPx(rpx) {
return (rpx / 750) * wx.getSystemInfoSync().windowWidth
},
// 下载图片
urlToFile(url) {
return new Promise((resolve) => {
uni.getImageInfo({
src: url,
success(res) {
resolve(res.path)
},
fail(res) {
console.log('fail -> res', res)
uni.showToast({
title: '网络异常',
duration: 2000,
icon: 'none'
})
this.$emit('close')
}
})
})
},
close() {
this.$emit('close')
},
savePoster() {
console.log('savePoster', this.posterImage)
uni.saveImageToPhotosAlbum({
filePath: this.posterImage,
success: () => {
// uni.hideLoading();
uni.showToast({
title: '保存成功',
icon: 'none'
})
this.close()
},
fail: () => {
uni.hideLoading()
this.$toast({ title: '相册功能未授权,无法保存' })
},
complete: () => {
}
})
}
}
}
</script>
<style scoped lang="scss">
.poster{
width: 590rpx;
height: 976rpx;
position: relative;
}
.close-icon{
width: 54rpx;
height: 54rpx;
position: absolute;
display: flex;
justify-content: center;
align-items: center;
top: 0;
right: 0;
background: rgba(43,47,54,.5);
.icon{
width: 35rpx;
height: 35rpx;
}
}
.poster-hidden{
width: 0rpx;
height: 0rpx;
overflow: hidden;
}
.save-btn{
position: absolute;
left: calc(50% - 140rpx);
bottom: -130rpx;
}
.poster-canvas{
transform: translateY(99999999999999rpx);
}
.poster-image{
width: 590rpx;
height: 976rpx;
}
</style>
⏳看到上方代码,先是利用canvas生成图片,将图片用image
标签展示出来,cavans元素移除屏幕外,这里可能有疑问为什么要这么做?直接用canvas元素来展示图片不好吗?为什么要用canvas生成的图片来显示呢?
⏳这么做的原因其实是因为canvas在抖音小程序,微信小程序部分真机中没有动画过渡,当弹窗关闭时比较突兀,当然如果需求中没有动画过渡的要求,就不需要多这一步。