uni-app如何生成海报图片

news2024/12/23 22:45:47

项目场景:

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

问题描述

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

原因分析:

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

解决方案:

在这里插入图片描述

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

  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>
           <!-- 这是一个关闭的图标 -->
        <!-- <view v-if="isPosterShow" class="poster-close" @click="closePoster">
            <image :src="imgs.pclose"></image>
        </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()方法

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/715681.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java实现PDF转Word【收集整理】

首先感谢 Mgg9702 博主提供的转换依赖包处理&#xff0c;关于如何获得一个破解的pdf转word我这里就不追述了&#xff0c;有需要去看&#xff1a; https://blog.csdn.net/Mgg9702/article/details/124987483?spm1001.2014.3001.5506 我这里主要涉及到整理一个pdf转word的jar工…

Spring Boot原理分析 | SpringApplication、Yaml、Properties

&#x1f497;wei_shuo的个人主页 &#x1f4ab;wei_shuo的学习社区 &#x1f310;Hello World &#xff01; Spring Boot Spring开源框架&#xff0c;轻量级的Java开发框架&#xff0c;解决企业级应用开发的复杂性而创建&#xff0c;简化开发 基于POJO的轻量级和最小侵入型编程…

【计算机视觉 | 图像分割】arxiv 计算机视觉关于图像分割的学术速递(6月 30 日论文合集)

文章目录 一、分割|语义相关(8篇)1.1 MIS-FM: 3D Medical Image Segmentation using Foundation Models Pretrained on a Large-Scale Unannotated Dataset1.2 KITE: Keypoint-Conditioned Policies for Semantic Manipulation1.3 SeMLaPS: Real-time Semantic Mapping with La…

labelme的json标签和图像改变分辨率,再将json转换为YOLO的txt格式进行实例分割

最近在做一个分割数据集&#xff0c;训练数据时由于图像数据太大一直爆显存&#xff0c;然后就找了找同时resize图像和json的脚本&#xff0c;然后转换为YOLO格式一直出问题&#xff0c;标签和目标位置对不上&#xff0c;也是困扰了好久&#xff0c;终于解决&#xff0c;记录一…

惠普笔记本U盘重装Win10系统步骤

当惠普笔记本出现系统故障或需要清除所有数据时&#xff0c;通过使用U盘重新安装Win10系统是一个常见且有效的解决方法。重新安装系统可以解决许多问题&#xff0c;并为用户提供一个干净、流畅的操作环境。以下小编将为用户介绍惠普笔记本U盘重装Win10系统步骤。请注意&#xf…

手把手教学,Python 游戏编程之实现飞机大战(含源代码)

文章目录 一、游戏设定 1、游戏界面展示和设定 二、实现过程 1.我方飞机 2、敌方飞机 3、定义武器 4、武器补充库 5、主模块 总结&#xff1a; 前言 我想大家都是有玩过类似飞机大战的射击类游戏&#xff0c;也享受目标被消除通过后带来的愉悦感。 那么如果用Python来实现飞机…

Image Sensor的窗口裁剪

本文介绍Image Sensor的窗口裁剪&#xff0c;Image Sensor的实际像素通常是大于实际所支持的最大分辨率的&#xff0c;有时为了获得想要的分辨率及位置&#xff08;比如与镜头装配相匹配&#xff09;&#xff0c;需要设置Image Sensor的像素输出位置及大小&#xff0c;本文以OS…

为什么向导式对话框中的取消按钮始终可用

PropSheet_SetWizButtons 是一个宏&#xff0c;其定义位于 PRSHT.H 头文件中&#xff0c;实际上&#xff0c;它只是调用了 PostMessage 函数来向目标窗口发送 PSM_SETWIZBUTTONS 这个消息&#xff0c;仅此而已。 如果你亲自上阵体验一番&#xff0c;就会发现有这么一个问题(特…

c++ stl 之vector使用

参考&#xff1a;https://www.runoob.com/cplusplus/cpp-stl-tutorial.html “C STL&#xff08;标准模板库&#xff09;是一套功能强大的 C 模板类&#xff0c;提供了通用的模板类和函数&#xff0c;这些模板类和函数可以实现多种流行和常用的算法和数据结构&#xff0c;如向…

特征向量可视化01_tsne_pca

在学习机器学习或深度学习基础知识的同时训练模型是一个非常有指导性的过程。该数据集易于理解且格式适当&#xff0c;可供您使用。然而&#xff0c;当您走进现实世界并尝试解决行业或现实生活中的挑战时&#xff0c;数据集如果一开始就不存在&#xff0c;通常会很混乱。理解为…

uniapp怎么把px转换成对应手机型号的rpx

首先获取系统手机屏幕的宽度系统信息的概念 | uni-app官网&#xff0c;然后根据公式转换 rpx 750*元素 B 在设计稿上的宽度为 多少px/手机屏幕的宽度 详见&#xff1a;CSS 支持 | uni-app官网 如下为把宽度为1px的转成对应手机型号的rpx uni.getSystemInfo({success(res) {co…

网络营销VS传统营销有什么区别?

随着互联网的普及和发展&#xff0c;网络营销已经成为企业营销的重要手段之一。相比传统营销&#xff0c;网络营销具有更多的优势和特点。本文将从市场环境、营销手段、成本效益等方面&#xff0c;分析网络营销与传统营销的区别。#网络营销# 一、市场环境不同 传统营销主要是通…

华为OD机试真题 Python 实现【查找单入口空闲区域】【2022 Q4 100分】,附详细解题思路

目录 一、题目描述二、输入描述三、输出描述四、解题思路五、Python算法源码六、效果展示1、输入2、输出3、说明 一、题目描述 给定一个 m x n 的矩阵&#xff0c;由若干字符 ‘X’ 和 ‘O’构成&#xff0c;’X’表示该处已被占据&#xff0c;’O’表示该处空闲&#xff0c;请…

[pyqt5]动态加载ui文件并给菜单的一个子菜单添加触发事件

场景&#xff1a;大家都知道如果直接将ui文件转成py文件后&#xff0c;如果产品经理要你加一些界面控件&#xff0c;你就得改转换后代码这样很麻烦&#xff0c;我们可以直接加载ui文件&#xff0c;然后编写触发事件&#xff0c;因此写了一个简单案例&#xff0c;证明切实可行&a…

微服务:Springboot集成Hystrix实现熔断、降级、隔离

文章目录 前言知识积累Springboot集成Hystrix1、maven依赖引入2、application开启feign的hystrix支持&#xff08;客户端配置限流降级熔断&#xff09;3、入口类增加EnableFeignClients EnableHystrix 开启feign与hystrix4、feign调用增加降级方法服务端配置限流降级熔断(选择使…

stm32 使用keil无实物(软件)仿真,虚拟串口通讯

准备 1.keil 2.vspd虚拟串口 3.sscom串口助手 4.CubeMX //哪里报错no ‘read‘ permission&#xff0c;把哪里map一下 map 0x40000000, 0x400077FF read write // APB1 map 0x40010000, 0x40014BFF read write // APB2 map 0x40020000, 0x4007FFFF read write …

​​国风写实虚拟人频“营业”,塑造国潮文化元宇宙入口

近几年&#xff0c;随着时代话语权逐渐递交给Z世代的年轻人&#xff0c;文化自信成为了主流审美&#xff0c;国风虚拟人激发了年轻人心中的民族文化自豪感。 国风虚拟人谷小雨频营业&#xff0c;发布了“中文之美”虚拟人动画&#xff0c;穿越古今四时感受“雨”字流转之美&am…

MMdetection框架速成系列 第04部分:配置文件详细解析+文件结构剖析+Config类核心实现

&#x1f697;&#x1f697;&#x1f697;&#x1f697;&#x1f697;&#x1f697;&#x1f697;&#x1f697;&#x1f697;&#x1f697;&#x1f697;&#x1f697;&#x1f697;&#x1f697;&#x1f697;&#x1f697;&#x1f697; MMdetection框架速成系列 MMdetect…

简单回顾一下kafka的学习

简单回顾一下kafka的学习 WhatBrokerControllerPartitionReplicationTopicProducerConsumer Why为什么有多个分区为什么有副本 How搭建集群Java简单使用ProducerConsumeroffset提交方式自动提交 - 默认手动提交 消费者poll消息的过程指定分区消费消息回溯消费指定offset消费新消…

Firefly

Firefly(流萤): 中文对话式大语言模型在本文中&#xff0c;笔者将介绍关于Firefly&#xff08;流萤&#xff09;模型的工作&#xff0c;一个中文对话式大语言模型。https://mp.weixin.qq.com/s/TX7wj8IzD_EaMTvk0bjRtA一个支持中文的176B开源基础模型BLOOM&#xff1a;从数据源…