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

✨uniapp实现生成海报并保存至相册组件,u-popup可以根据自己所使用的组件进行替换

这里主要讲的是JS部分,css和元素相关的就不展开赘述了,下方先给大伙看看效果图,图的下方有代码讲解,最下方有完整代码,如各位大神发现问题后请友好的交流勿喷。

⏳示例图

小程序uniapp利用canvas生成海报并可以保存至相册_第1张图片

⏳ 图片引用

想要用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在抖音小程序,微信小程序部分真机中没有动画过渡,当弹窗关闭时比较突兀,当然如果需求中没有动画过渡的要求,就不需要多这一步。

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

你可能感兴趣的:(小程序,微信小程序,uni-app,javascript,前端,微信小程序,vue.js)