这篇文章主要讲解了“怎么在小程序中实现保存图片组件功能”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“怎么在小程序中实现保存图片组件功能”吧!
思路
首先声明下组件采用的是uniapp,具体实现了可以绘制图片、绘制文字以及保存海报至相册的基本功能,在开发中这些也完全够用了。
通过canvas绘制海报。通过uni.canvasToTempFilePath
将绘制好的 canvas转为图片。通过uni.saveImageToPhotosAlbum
将本地临时路径的图片保存至手机相册中。而我的想法是将所有采用的方法全部封装到组件中,只通过父组件去调用需要使用的方法和调整相关的参数即可。 具体使用可以查看示例代码
通过canvas绘制海报内容的顺序先后问题
通过使用promise对象决定绘制海报内容的顺序先后。promise.all()
方法进行canvas最后一步的绘画操作 context.draw()
注意uni.getImageInfo()
在绘制图片 和 头像时,组件通过
uni.getImageInfo()
去获取图片的相关信息,调用该方法成功的前提是需要在微信小程序后台配置download域名和request域名当然最好把uploadFile域名也一起配置,防止出差错。但是官方给出的提示是配置download域名白名单即可,但是获取不到图片信息,这算是一个大坑了。如果没有进行相关配置,在调试时 或者 体验版 正式版等 打开了vconsole调试工具。uni.getImageInfo() 是可以获取到图片信息的,一旦关闭了vconsole uni.getImageInfo() 将会fail, 也是个坑。
本组件方法,变量介绍
props
canvasInfo Object (必需)
canvasWidth 画布宽度
canvasHeight 画布高度
canvasId 画布标识
isFullScreen Boolean
为ture时表示画布为手机屏幕全屏,canvasInfo设置的宽高将失效。
默认为 false
methods
canvasInit(callback) canvas初始化,所有有关画布canvas操作需在其回调函数操作。
drawCanvasImage(context, src, _imageWidth, _imageHeight, dx, dy) 在canvas绘制一张图片
drawCircularAvatar(context, url, _circularX, _circularY, _circularR) 在canvas绘制一张圆形图片
drawText(options) 在canvas绘制单行、多行文本
startDrawToImage(context, promiseArr, callback) 将canvas操作draw()进行绘制
posterToPhotosAlbum(filePath) 保存至手机相册
示例代码
<template><view><view class="savePosterItem"><image v-show="tempFilePath" :src="tempFilePath"></image><save-poster-com v-show="!tempFilePath" ref="savePoster" :canvasInfo="canvasInfo"></save-poster-com></view><button class="savePosterBtn" type="primary" @click="saveBtnFun">保存海报</button></view></template><script>import SavePosterCom from '@/components/SavePosterCom/SavePosterCom.vue'export default {components: {SavePosterCom},data() {return {canvasInfo: {canvasWidth: 620,canvasHeight: 950,canvasId: 'save-poster'},tempFilePath: '',canvasBgUrl: 'https://file.lsjlt.com/upload/202306/25/ktp0qrbhjfh.jpg?auto=compress&cs=tinysrgb&dpr=1&w=500',avatarUrl: 'https://p9-passport.byteacctimg.com/img/user-avatar/4dbf31fa6dec9c65b78a70d28d843c04~300x300.image'}},onLoad() {let {drawCanvasImage,drawCircularAvatar,drawText} = this.$refs.savePoster.$options.methodsthis.$refs.savePoster.canvasInit(({context,comThis}) => {// 获取画布宽高let canvasWH = comThis.canvasWH// 绘制海报背景图let promise_1 = drawCanvasImage(context, this.canvasBgUrl, canvasWH.canvasWidth, canvasWH.canvasHeight)// 必须先绘制玩海报背景图 再去操作其他绘制内容promise_1.then(res => {let promise_2 = drawCircularAvatar(context, this.avatarUrl, canvasWH.canvasWidth / 2, canvasWH.canvasHeight /7, 70)let promise_3 = drawText({context: context,text: '皮皮虾仁',dx: (canvasWH.canvasWidth / 2) + 60,dy: canvasWH.canvasHeight / 4,fontSize: 30,fontColor: '#5D4037'})let promise_4 = drawCanvasImage(context, this.avatarUrl, 150, 150, (canvasWH.canvasWidth / 2) + 85, (canvasWH.canvasHeight -165)) this.$refs.savePoster.startDrawToImage(context, [promise_1,promise_2,promise_4], (tempFilePath) => {this.tempFilePath = tempFilePath})})})},methods: {saveBtnFun() {uni.showModal({title: '保存海报',content: '海报将被保存至相册中',confirmText: '保存',success: (res) => {if(res.confirm) {this.$refs.savePoster.posterToPhotosAlbum(this.tempFilePath)}}})}}}</script><style>.savePosterItem {text-align: center;}.savePosterItem > image {width: 620rpx;height: 950rpx;}.savePosterBtn {margin-top: 40rpx;width: 80%;}</style>
组件源码
<template><view><canvas :canvas-id="canvasInfo.canvasId" :style="{width: canvasWH.canvasWidth + 'px', height: canvasWH.canvasHeight + 'px'}"></canvas></view></template><script>export default {name: 'savePosterCom',data() {return {userPhoneWHInfo: {},canvasWH: {canvasWidth: 0,canvasHeight: 0}}},props: {// 决定保存下来的图片的宽高canvasInfo: {type: Object,default: () => {return {canvasWidth: 0,canvasHeight: 0,canvasId: 'canvasId'}}},// canvas画布是不是全屏,默认是false。 false时使用必须传 canvasInfoisFullScreen: Boolean},created() {this.userPhoneWHInfo = this.getPhoneSystemInfo()if (this.isFullScreen) { // 画布全屏this.canvasWH.canvasWidth = this.userPhoneWHInfo.windowWidththis.canvasWH.canvasHeight = this.userPhoneWHInfo.windowHeight} else { // 指定宽高this.canvasWH.canvasWidth = this.canvasInfo.canvasWidththis.canvasWH.canvasHeight = this.canvasInfo.canvasHeight}},mounted() {},methods: {getPhoneSystemInfo() {const res = uni.getSystemInfoSync();return {windowWidth: res.windowWidth,windowHeight: res.windowHeight}},getCanvasContextInit(canvasId) {return uni.createCanvasContext(canvasId, this)},canvasInit(callback) {let context = this.getCanvasContextInit(this.canvasInfo.canvasId)if (context) {callback({context: context,comThis: this})}},startDrawToImage(context, promiseArr, callback) {// 将之前在绘图上下文中的描述(路径、变形、样式)画到 canvas 中let canvasId = this.canvasInfo.canvasIdlet tempFilePath = ''Promise.all(promiseArr).then(res => {context.draw(false, async () => {callback(await this.canvasToImage(canvasId))})})},drawCanvasImage(context, src, _imageWidth, _imageHeight, dx, dy) {return new Promise((resolve, reject) => {uni.getImageInfo({src: src,success: res => {context.drawImage(res.path, (dx - _imageWidth), (dy - _imageHeight), _imageWidth, _imageHeight)resolve(context)},})})},drawCircularAvatar(context, url, _circularX, _circularY, _circularR) {let dx = _circularX - _circularR;let dy = _circularY - _circularR;let dwidth = _circularR * 2;let dheight = _circularR * 2return new Promise((resolve, reject) => {uni.downloadFile({url: url,success: res => {context.save()context.beginPath()// _circularX圆的x坐标 _circularY圆的y坐标 _circularR圆的半径context.arc(_circularX, _circularY, _circularR, 0, 2 * Math.PI)context.clip()// dx: 图像的左上角在目标canvas上 X 轴的位置// dy: 图像的左上角在目标canvas上 Y 轴的位置// dwidth: 在目标画布上绘制图像的宽度,允许对绘制的图像进行缩放// dheight: 在目标画布上绘制图像的高度,允许对绘制的图像进行缩放context.drawImage(res.tempFilePath, dx, dy, dwidth, dheight)context.restore()// context.draw()resolve(context)}})})},drawText(options) {let {context,text,dx,dy,rowStrnum = text.length,lineHeight = 0,fontSize = 16,fontColor = 'black'} = optionsreturn new Promise((resolve, reject) => {context.setFontSize(fontSize)context.setFillStyle(fontColor)context.setTextBaseline('middle')// 获取需要绘制的文本宽度let textWidth = Number(context.measureText(text).width)// console.log('textWidth',textWidth)// 获取文本的字数 let textNum = text.length// 获取行数 向上取整let lineNum = Math.ceil(textNum / rowStrnum)// console.log('textNum',textNum)// console.log('lineNum',lineNum)for (let i = 0; i < lineNum; i++) {let sliceText = text.slice(i * rowStrnum, (i + 1) * rowStrnum)// fillText 的 dx = 文字最左边的距离到屏幕政策的距离context.fillText(sliceText, dx - textWidth, dy + i * lineHeight);}resolve(context)})},canvasToImage(canvasId) {return new Promise((resolve, reject) => {uni.canvasToTempFilePath({canvasId: canvasId, // 画布标识success: res => {// 在H5平台下,tempFilePath 为 base64resolve(res.tempFilePath)},fail: err => {console.log('err', err)reject(err)}}, this)})},posterToPhotosAlbum(filePath) {console.log('filePath',filePath)uni.showLoading({title: '保存中...'})uni.saveImageToPhotosAlbum({filePath: filePath,success: (res) => {uni.showToast({title: '保存成功,请前往手机相册中查看',mask: true,icon: 'none',duration: 2000})},fail: (err) => {console.log('err',err)if (err.errMsg.includes('deny')||err.errMsg.includes('denied')) { // 用户选择拒绝 this.openSetting()} else if (err.errMsg.includes('fail cancel')) { // 用户在保存图片时 取消了uni.showToast({title: '已取消保存,无法保存至相册',mask: true,icon: 'none',duration: 2000})return}},complete: () => {uni.hideLoading()}})},openSetting() {uni.showModal({title: '温馨提示',content: '保存图片至相册中,需要您同意添加访问相册权限',cancelText: '拒绝',confirmText: '同意',success: res => {if (res.confirm) {uni.openSetting({success: settingdata => {if (settingdata.authSetting['scope.writePhotosAlbum']) {console.log('获取权限成功,给出再次点击图片保存到相册的提示。')uni.showToast({title: '授权成功,请再次点击保存',icon: 'none',duration: 2000,})} else {console.log('获取权限失败,给出不给权限就无法正常使用的提示')uni.showToast({title: '需要访问相册权限',icon: 'none',duration: 2000,})}},fail: (res) => {console.log('err', err)}})} else {uni.showToast({title: '已拒绝授权,无法保存至相册',mask: true,icon: 'none',duration: 2000})return}}})}}}</script><style></style>
感谢各位的阅读,以上就是“怎么在小程序中实现保存图片组件功能”的内容了,经过本文的学习后,相信大家对怎么在小程序中实现保存图片组件功能这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是编程网,小编将为大家推送更多相关知识点的文章,欢迎关注!