小程序uniapp利用canvas生成海报并可以保存至相册

news2024/12/23 4:08:49

✨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画布,使生成的图片更加的清晰,也就是代码中的canvasMultiplecanvasMultiple变量在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在抖音小程序,微信小程序部分真机中没有动画过渡,当弹窗关闭时比较突兀,当然如果需求中没有动画过渡的要求,就不需要多这一步。

🎇感谢大家阅读,如有帮助或问题欢迎给赞👍和讨论

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

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

相关文章

Echarts 热力图的详细配置过程

文章目录 一&#xff0c;配置过程二&#xff0c;具体实例 一&#xff0c;配置过程 引入Echarts库和热力图插件 <script src"https://cdn.jsdelivr.net/npm/echarts/dist/echarts.min.js"></script> <script src"https://cdn.jsdelivr.net/npm/…

Java多线程入门到精通学习大全?了解线程池和线程常用集合的基本原理、代码示例!(第六篇:线程池和集合的学习)

设计和实现多线程应用程序需要了解线程池、线程常用集合等相关知识。下面我们将分别介绍线程池、线程常用集合的原理、使用场景、代码示例、注意事项和总结。 1. 线程池 线程池是一种线程调度机制&#xff0c;它可以管理多个线程&#xff0c;并且可以重复使用这些线程来处理多…

Transformers回顾 :从BERT到GPT4

人工智能已成为近年来最受关注的话题之一&#xff0c;由于神经网络的发展&#xff0c;曾经被认为纯粹是科幻小说中的服务现在正在成为现实。从对话代理到媒体内容生成&#xff0c;人工智能正在改变我们与技术互动的方式。特别是机器学习 (ML) 模型在自然语言处理 (NLP) 领域取得…

大众软件之变:宾利品牌制造负责人接任CARIAD CEO

作者 | 德新 编辑 | 王博 上周五&#xff0c;Business Insider爆料了大众软件组织CARIAD人事大调整&#xff0c;董事会几乎全军覆没&#xff0c;引发轩然大波。 这周一&#xff0c;大众的官宣接踵而至——大众集团老兵、原宾利品牌制造负责人Peter Bosch接替CARIAD CEO一职。 …

《基于光学传感器的心房颤动检测:综述》阅读笔记

目录 一、论文摘要 二、论文十问 Q1&#xff1a;论文试图解决什么问题&#xff1f; Q2&#xff1a;这是否是一个新的问题&#xff1f; Q3&#xff1a;这篇文章要验证一个什么科学假设&#xff1f; Q4&#xff1a;有哪些相关研究&#xff1f;如何归类&#xff1f;谁是这一课…

数智无限|东土科技科东软件5月活动预告

这个5月&#xff0c;东土科技&科东软件将携带自主研发的国产操作系统Intewell、智能控制通用工具软件MaVIEW、边缘通用控制器NewPre、基于TSN技术的智能化工业网络硬件、数字工厂智能产线一站式解决方案&#xff0c;以及面向智能工厂的离散控制、流程控制、运动控制、机器人…

赋能户外赛事,时空道宇率先实现卫星应用商业闭环

最近这两年&#xff0c;卫星通信的崛起速度惊人&#xff0c;几乎成为了一个现象级风口。 在低轨星座、高通量卫星等技术的推动下&#xff0c;卫星通信的服务能力有了极大的提升&#xff0c;不仅覆盖范围越来越广阔&#xff0c;网络连接速率和时延也有了显著进步。 进入2023年以…

【沐风老师】3DMAX艺术样条线增强螺旋线插件使用教程

3DMAX艺术样条线、增强螺旋线插件使用教程 完美漂亮的卷曲形状&#xff01; 3DMAX艺术样条线插件用于创建漂亮的螺旋线、卷曲线和环形结样条曲线&#xff0c;具有数学精度。 例如制作饰品、装饰品、铁制品设计、珠宝、布饰品、古代文化的符号、触角、动画/分发/变形的路径等等…

5万字数字乡村建设与示范项目可行性研究报告

5.1 “三平台”&#xff1a;建设支撑农业发展农村治理惠民服务的三大应用平 台 5.3.1 建设智慧农业综合服务平台 夯实数字农业基础&#xff0c;推进重要农产品全产业链大数据建设。推进农业数字化转型&#xff0c;加快推广云计算、大数据、物联网、人工智能在农业生产经营管理…

基于STL的演讲比赛管理系统

目录 一、比赛规则描述 二、比赛程序的功能 三、比赛的运行结果 四、程序的实现 1、程序实现的大致思路 2、程序的模块化 <1>选手信息的类 <2>管理比赛所有进度的接口头文件 <3>比赛进行的实现 <4>用户的交互界面<演讲比赛管理系统.cpp>…

vue项目引入pwa使网页应用可安装

最近在使用vue项目时看到一个这样的效果&#xff0c;如图&#xff1a; 现在其实有很多网站都支持把网页安装到电脑或手机&#xff08;IOS Safari支持较好&#xff09;&#xff0c;如下图安装后的效果&#xff1a; 这些都是网页应用。 接下来介绍一下如何让你的vue2项目变得可以…

动态规划刷题篇【Day01】

目录 一.问题描述 描述 二.示例 三.动态规划方法 &#xff08;一&#xff09;最优子结构 &#xff08;二&#xff09;递推关系建立 &#xff08;三&#xff09;自底向上计算 &#xff08;四&#xff09;代码实现 一.问题描述 描述 输入一个长度为n的整型数组array&#xff…

Echarts 箱线图的详细配置过程

文章目录 Echarts 简介 Echarts 简介 ECharts&#xff08;百度开源的数据可视化库&#xff09;提供了丰富的图表类型&#xff0c;其中包括箱线图。箱线图是一种用于展示数据分布情况的图表类型&#xff0c;它可以显示数据的中位数、四分位数、最大值、最小值和异常值等信息。下…

23种设计模式之工厂方法模式

前言&#xff1a;大家好&#xff0c;我是小威&#xff0c;24届毕业生&#xff0c;在一家满意的公司实习。本篇文章将23种设计模式中的工厂方法模式&#xff0c;此篇文章为一天学习一个设计模式系列文章&#xff0c;后面会分享其他模式知识。 如果文章有什么需要改进的地方还请大…

水提金银花多糖通过调节NLRP3-IL-17信号轴减轻过敏性鼻炎

文章标题&#xff1a;Water-extracted Lonicera japonica polysaccharide attenuates allergic rhinitis by regulating NLRP3-IL-17 signaling axis 发表期刊&#xff1a;Carbohydrate Polymers 影响因子&#xff1a;10.723 作者单位&#xff1a;遵义医科大学 百趣提供服务…

五、Spring Cloud Alibaba-Feign

引入 基于之前的学习&#xff0c;我们目前调用是通过restTemplate硬编码方式调用的。 restTemplate.getForObject("http://stock-service/stock/reduce", String.class);还是很不方便&#xff0c;考虑直接用service.方法进行调用&#xff0c;从而引出Feign&#xff…

vue+elementui+nodejs校园高校餐厅点餐及订餐菜品推荐评价系统6927k

传统的销售模式&#xff0c;在实体店的紧跟式的销售模式&#xff0c;会给消费者一种不自由&#xff0c;被监视的感觉。餐厅点餐及推荐系统&#xff0c;紧跟数据时代的步伐&#xff0c;使用nodejs开发语言&#xff0c;配备MySQL数据库。扎根于实际问题所开发出来的一套系统。这个…

总结845

学习目标&#xff1a; 月目标&#xff1a;5月&#xff08;张宇强化前10讲&#xff0c;背诵15篇短文&#xff0c;熟词僻义300词基础词&#xff09; 周目标&#xff1a;张宇强化前3讲并完成相应的习题并记录&#xff0c;英语背3篇文章并回诵 每日必复习&#xff08;5分钟&#…

Windows 10 安装 MySQL

确认是否已安装 MySQL &#xff08;1&#xff09;按【winr】快捷键打开运行&#xff1b; &#xff08;2&#xff09;输入 services.msc&#xff1b; &#xff08;3&#xff09;点击【确定】&#xff0c;在打开的服务列表中查找 mysql 服务&#xff0c;如果没有 mysql 服务&…

HTAP for MySQL 在腾讯云数据库的演进

摘要&#xff1a;MySQL在充分利用多核计算资源方面比较欠缺&#xff0c;无法同时满足在线业务和分析型业务的客户需求&#xff0c;而单独部署一套专用的分析型数据库意味着额外的成本和复杂的数据链路。本次主题将介绍腾讯云数据库为满足此类场景而在HTAP for MySQL产品方面进行…