uni-app如何生成海报图片

项目场景:

  1. 在uni-app中,通过点击邀请分享海报的方式,可以展示不同的海报,并通过扫描海报上的二维码来实现用户之间的关系绑定,从而实现分销功能;
  2. 每次生成的海报样式都可能不同,可以根据后台配置的宽度、高度、X坐标和Y坐标的不同,需要灵活调整每个海报中展示二维码的位置。

问题描述

  1. uni-app中如何使用API生成海报。
  2. 生成的图片无法正确展示,本地可以看到,真机上看不到。
  3. 动态改变二维码的位置和大小,位置有所偏差。
  4. 怎么给头像设置成圆形。

原因分析:

  1. 当在uni-app中生成海报时,如果使用网络图片,需要使用uni.getImageInfo函数将其转换为本地临时路径。
  2. 如果后台返回的太阳码是以base64形式的数据,可能会在真机上无法展示,所以需将其转换为本地路径。
  3. 在canvas中设置的值都是以像素(px)为单位,如果根据750设计图给太阳码设置数值时,需要进行转换,以适应不同屏幕的像素密度。

解决方案:

uni-app如何生成海报图片_第1张图片

通过点击“邀请好友返现赚不停”,生成海报,有两种方案去实现以上效果:

  1. 通过uni-app提供的API,可以实现纯前端的canvas渲染海报,这种方法加载速度快且不需要占用后端资源,但无法通过长按识别二维码和转发海报,只能保存到手机相册再转发给朋友。
  2. 通过后端来渲染海报,虽然会占用后端资源,但它的优势在于后端直接生成图片。只需在image标签上添加show-menu-by-longpress属性,就可以实现长按识别二维码和直接转发海报给朋友的功能。

本文主要介绍了通过纯前端的canvas生成海报的方式:

1、逻辑梳理:

在展示海报的过程中,首先我们需要使用uView框架提供的popup组件,不过这些细节在这里并不重要,因为下面的JavaScript代码是通用的,只是样式上可能会有些不同,但是你是uni-app的项目,主要步骤如下:

  1. 首先,我们需要在canvas中展示一张海报图片。
  2. 然后,根据登录信息获取当前用户的头像和名字。
  3. 通过接口获取太阳码的数据地址。
  4. 将获取到的头像、名字和太阳码赋值到对应的位置。
   <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>

2、海报的渲染

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
}

2、保存海报到本地

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);
                                    }
                                })
                            }
                        }
                    })
                }
            })
        }

    })
}

3、工具类

// 画圆角
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)
			}
		})
	})
}

4、总结

1、使用canvas的场景需要根据具体需求而定,如果必须要使用图片的原生转发属性,那就需要后端来生成然后给我们返回图片。
2、如果加载出来的海报的页面还能滚动,需要将海报弹出的时候,整个外层样式设置为overflow:hidden,关闭的时候设置为overflow:unset
3、uni.getImageInfo() 可以把网络地址转化为本地地址。
4、uni.drawImage() 最后一定要执行ctx.draw()方法

你可能感兴趣的:(uni-app,uni-app)