uniapp实现canvas保存海报功能

在这里插入图片描述

父组件引入邀请海报组件:


    <QrcodePoster
      ref="poster"
      :title="title"
      :subTitle="subTitle"
      :headerImg="newIamge"
      :price="pinkT.totalPrice"
      :productPrice="productPrice"
      :quota="quota"
      :abImg="pinkT.avatar "
      :leaderNickname="newHeaderName"
      :qrcode="posterUrl"
    ></QrcodePoster>
//
  title: "", // 海报标题
  subTitle: "", // 海报副标题
  headerImg "", // 商品图片
  price "", // 商品现价
  productPrice "", // 商品原价
  quota: "", // 海报仅限x个名额
  abImg: "", // 团长头像
  leaderNickname: "", // 团长名字
  qrcode: "", // 后端返回的二维码图片

父组件打开海报方法:

openPoster() {
      this.$nextTick(() => {
        this.$refs.poster.showCanvas(
          "https:xxxx.png"
        );
      });
    },

海报组件代码

<template>
    <view class="content" v-if="isShow" @click.stop="isShow=false">
        <canvas @click.stop="" :style="{ width: canvasW + 'px', height: canvasH + 'px' }"
            canvas-id="my-canvas"></canvas>
        <view class="save-btn" @click.stop="saveImage"></view>
    </view>
</template>

<script>
    export default {
        props: {
            headerImg: {
                type: String,
                default: ''
            },
            title: {
                type: String,
                default: '商城用户'
            },
            subTitle: {
                type: String,
                default: ''
            },
            price: {
                type: String,
                default: ''
            },
            abImg: {
                type: String,
                default: ''
            },
            productPrice: {
                type: String,
                default: ''
            },
            quota: {
                type: String,
                default: ''
            },
            leaderNickname: {
                type: String,
                default: ''
            },
            qrcode: {
                type: String,
                default: ''
            }
        },
        data() {
            return {
                canvasW: 0,
                canvasH: 0,
                ctx: null,
                isShow: false,
                priceTxt: '元/人',
                posterBgc: 'xxxxx.png', // 设置背景图片
                leaderFlag: 'xxxx.png', // 团长标识logo
            }
        },
        methods: {
            //显示
            showCanvas() {
                this.isShow = true
                this.__init()
            },

            //初始化画布
            async __init() {

                uni.showLoading({
                    title: '加载中...',
                    mask: true
                })

                // 创建画布
                this.ctx = uni.createCanvasContext('my-canvas', this)
                
                this.canvasW = uni.upx2px(571);
                this.canvasH = uni.upx2px(1111);
                
                //设置画布背景颜色透明
                this.ctx.setFillStyle('#fff')

                //设置画布大小
                this.ctx.fillRect(0, 0, this.canvasW, this.canvasH)

                // 设置背景图片
                let posterImg = await this.getImageInfo(this.posterBgc)
                this.ctx.drawImage(posterImg.path, 0, 0, this.canvasW, this.canvasH)


                // 设置商品图片
                let headerImg = await this.getImageInfo(this.headerImg)
                let hW = uni.upx2px(473);
                let hH = uni.upx2px(473);
                // this.drawRoundImg(ctx, img, x, y, width, height, radius)
                this.drawRoundImg(this.ctx, headerImg.path, ((this.canvasW - hW) / 2), ((this
                    .canvasW - hW) / 2 + (uni.upx2px(60))), hW, hH, (uni.upx2px(16)))

                //绘制商品标题
                this.ctx.beginPath()
                this.ctx.setFontSize(12);
                this.ctx.setFillStyle('#000');
                this.ctx.font = 'normal bold 12px sans-serif';
                // toFormateStr(ctx, str, draw_width, lineNum, startX, startY, steps)
                this.toFormateStr(this.ctx, this.title, 170, 2, ((this.canvasW - hW) / 2), ((this.canvasW - hW) / 2 + (
                    uni.upx2px(564))), 18);
                this.ctx.fill()
                this.ctx.closePath()

                //绘制副标题
                this.ctx.beginPath()
                this.ctx.setFontSize(12);
                this.ctx.setFillStyle('#333');
                this.ctx.fillText(this.subTitle, ((this.canvasW - hW) / 2), ((this.canvasW - hW) / 2 + (
                    uni.upx2px(650))))
                this.ctx.fill()
                this.ctx.closePath()


                //绘制价格
                this.ctx.beginPath()
                this.ctx.setFontSize(29);
                this.ctx.setFillStyle('#E01E1E');
                this.ctx.fillText(this.price, ((this.canvasW - hW) / 2), ((this.canvasW - hW) / 2 + (
                    uni.upx2px(715))))
                this.ctx.closePath()


                // 获取价格长度 
                let pLength = this.computedWidth(this.price)


                // 元/人
                this.ctx.beginPath()
                this.ctx.setFontSize(12);
                this.ctx.setFillStyle('#333');
                // this.ctx.fillText(this.priceTxt, (((this.canvasW - hW) / 2) + pLength), (
                //  ((this.canvasW - hW) / 2) + hH + 124))
                this.ctx.fillText(this.priceTxt, (((this.canvasW - hW) / 2) + pLength), ((this.canvasW - hW) / 2 + (
                    uni.upx2px(715))))
                this.ctx.closePath()

                // 原价
                this.ctx.beginPath()
                let textWidth = this.ctx.measureText(this.productPrice).width - 9
                this.ctx.setFontSize(10);
                this.ctx.setFillStyle('#999');
                this.ctx.fillText(this.productPrice, (((this.canvasW - hW) / 2) + pLength + (
                    uni.upx2px(80))), ((this.canvasW - hW) / 2 + (
                    uni.upx2px(715))))
                this.ctx.rect((((this.canvasW - hW) / 2) + pLength + (
                    uni.upx2px(82))), ((this.canvasW - hW) / 2 + (
                    uni.upx2px(707))), textWidth, 1)
                this.ctx.fill()
                this.ctx.closePath()

                // 仅限x个名额
                this.ctx.beginPath()
                this.ctx.setFontSize(12);
                this.ctx.setFillStyle('#fff');
                // this.ctx.fillText(this.quota, (((this.canvasW - hW) / 2) + 170), (
                //  ((this.canvasW - hW) / 2) + hH + 88))
                this.ctx.fillText(this.quota, ((this.canvasW - hW) / 2 + (
                    uni.upx2px(330))), ((this.canvasW - hW) / 2 + (
                    uni.upx2px(648))))
                this.ctx.closePath()

                //团长图片
                let avaImg = await this.getImageInfo(this.abImg)
                // avaRound(ctx, img, x, y, radius) 
                this.avaRound(this.ctx, avaImg.path, (((this.canvasW - hW) / 2 + (
                    uni.upx2px(38)))), ((this.canvasW - hW) / 2 + (
                    uni.upx2px(820))), (
                    uni.upx2px(56)));

                // 团长标识
                this.ctx.beginPath()
                let leaderFlagImg = await this.getImageInfo(this.leaderFlag)
                this.ctx.drawImage(leaderFlagImg.path, ((this.canvasW - hW) / 2), ((this.canvasW - hW) / 2 + (
                    uni.upx2px(860))), (
                    uni.upx2px(76)), (
                    uni.upx2px(32)))
                this.ctx.closePath()

                // 团长名字
                this.ctx.beginPath()
                // this.ctx.font = 'bold 12px PingFangSC'
                this.ctx.setFontSize(12); //设置标题字体大小
                this.ctx.setFillStyle('#fff'); //设置标题文本颜色
                this.ctx.fillText(this.leaderNickname, (((this.canvasW - hW) / 2 + (
                    uni.upx2px(222)))), ((this.canvasW - hW) / 2 + (
                    uni.upx2px(808))))
                this.ctx.fill()
                this.ctx.closePath()

                //小程序码
                this.ctx.beginPath()
                let qrcodeImg = await this.getImageInfo(this.qrcode)
                this.ctx.drawImage(qrcodeImg.path, (((this.canvasW - hW) / 2 + (
                    uni.upx2px(8)))), ((this.canvasW - hW) / 2 + (
                    uni.upx2px(910))), 50, 50)
                this.ctx.closePath()

                //延迟渲染
                setTimeout(() => {
                    this.ctx.draw(true, () => {
                        uni.hideLoading()
                    })
                }, 1000)

            },
            toFormateStr(ctx, str, draw_width, lineNum, startX, startY, steps) {

                var strWidth = ctx.measureText(str).width; // 测量文本源尺寸信息(宽度)
                var startpoint = startY,
                    keyStr = '',
                    sreLN = strWidth / draw_width;
                var liner = Math.ceil(sreLN); // 计算文本源一共能生成多少行
                let strlen = parseInt(str.length / sreLN); // 等比缩放测量一行文本显示多少个字符

                // 若文本不足一行,则直接绘制,反之大于传入的最多行数(lineNum)以省略号(...)代替
                if (strWidth < draw_width) {
                    ctx.fillText(str, startX, startpoint);
                } else {
                    for (var i = 1; i < liner + 1; i++) {
                        let startPoint = strlen * (i - 1);
                        if (i < lineNum || lineNum == -1) {
                            keyStr = str.substr(startPoint, strlen);
                            ctx.fillText(keyStr, startX, startpoint);
                        } else {
                            keyStr = str.substr(startPoint, strlen - 5) + '...';
                            ctx.fillText(keyStr, startX, startpoint);
                            break;
                        }
                        startpoint = startpoint + steps;
                    }

                }
            },

            // 获取价格长度 更好控制priceTxt的位置 
            computedWidth(val) {
                let count = 0
                let arr = String(val).split('')
                arr.map(item => {
                    // 如果价格为数字类型
                    if (Number(item) || item == '0') {
                        count += 19
                    } else {
                        // 如果价格有小数点
                        count += 5
                    }
                })
                return count
            },



            //带圆角图片
            drawRoundImg(ctx, img, x, y, width, height, radius) {
                ctx.beginPath()
                ctx.save()
                // 左上角
                ctx.arc(x + radius, y + radius, radius, Math.PI, Math.PI * 1.5)
                // 右上角
                ctx.arc(x + width - radius, y + radius, radius, Math.PI * 1.5, Math.PI * 2)
                // 右下角
                ctx.arc(x + width - radius, y + height - radius, radius, 0, Math.PI * 0.5)
                // 左下角
                ctx.arc(x + radius, y + height - radius, radius, Math.PI * 0.5, Math.PI)
                // 回到原点的直线
                ctx.arc(x + radius, y + radius, radius, Math.PI, Math.PI)
                ctx.stroke()
                ctx.clip()
                ctx.drawImage(img, x, y, width, height);
                ctx.restore()
                ctx.closePath()

            },

            // 团长头像
            avaRound(ctx, img, x, y, radius) {
                ctx.save();
                let 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()
            },

            //圆角矩形
            drawRoundRect(ctx, x, y, width, height, radius, color) {
                ctx.save();
                ctx.beginPath();
                ctx.setFillStyle(color);
                ctx.setStrokeStyle(color)
                ctx.setLineJoin('round'); //交点设置成圆角
                ctx.setLineWidth(radius);
                ctx.strokeRect(x + radius / 2, y + radius / 2, width - radius, height - radius);
                ctx.fillRect(x + radius, y + radius, width - radius * 2, height - radius * 2);
                ctx.stroke();
                ctx.closePath();
            },

            //获取图片
            getImageInfo(imgSrc) {
                return new Promise((resolve, reject) => {
                    uni.getImageInfo({
                        src: imgSrc,
                        success: (image) => {
                            resolve(image);
                            console.log('获取图片成功', image)
                        },
                        fail: (err) => {
                            reject(err);
                            console.log('获取图片失败', err)
                        }
                    });
                });
            },

            //保存图片到相册
            saveImage() {
                //判断用户授权
                // uni.getSetting({
                //  success(res) {
                //      // console.log('获取用户权限', res.authSetting)
                //      if (Object.keys(res.authSetting).length > 0) {
                //          //判断是否有相册权限
                //          if (res.authSetting['scope.writePhotosAlbum'] == undefined) {
                //              //打开设置权限
                //              uni.openSetting({
                //                  success(res) {
                //                      // console.log('设置权限', res.authSetting)
                //                  }
                //              })
                //          } else {
                //              if (!res.authSetting['scope.writePhotosAlbum']) {
                //                  //打开设置权限
                //                  uni.openSetting({
                //                      success(res) {
                //                          // console.log('设置权限', res.authSetting)
                //                      }
                //                  })
                //              }
                //          }
                //      } else {
                //          return
                //      }
                //  }
                // })
                var that = this
                uni.canvasToTempFilePath({
                    canvasId: 'my-canvas',
                    quality: 1,
                    complete: (res) => {
                        // console.log('保存到相册', res);
                        uni.saveImageToPhotosAlbum({
                            filePath: res.tempFilePath,
                            success(res) {
                                uni.showToast({
                                    title: '已保存到相册',
                                    icon: 'success',
                                    duration: 2000
                                })
                                setTimeout(() => {
                                    that.isShow = false
                                }, 2000)
                            }
                        })
                    }
                }, that);
            }
        }
    }
</script>

<style scoped lang="scss">
    .content {
        position: fixed;
        width: 750rpx;
        height: 100%;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        background: rgba(0, 0, 0, .4);
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        
        // 保存按钮
        .save-btn {
            margin-top: 35rpx;
            width: 501rpx;
            height: 83rpx;
            background: url(https://xxxx.png) no-repeat;
            background-size: 100%;
        }
    }
</style>

注意

子组件的获取图片方法getImageInfo(),会验证图片获取成功否.
所以在开发过程中如果海报未能展示,请查看该方法,相关图片是否获取成功

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值