微信小程序canvas画图,保存页面为海报

思路

  1. 暂时还没找的好的快速生成方案,只能通过代码一步一步画
  2. 绘制内容分类:文本、长段文本、本地图片、echarts图表
  3. 涉及功能:echarts临时图片路径获取、 长段文字处理、canvas绘制、canvas临时图片路径获取、授权判断、canvas临时图片保存至手机相册、图片全屏展示等

wxml与wxss

页面布局:fixed全屏、mask遮罩、canvas海报内容、button保存图片按钮
数据:canvasType是否显示、canvasH高度、canvasW宽度

<view class="poster" wx:if="{{canvasType}}">
    <view class="mask" catchtap="cancelPoster"></view>
    <canvas class='canvas' style='height:{{canvasH}}px;width:{{canvasW}}px;' id="myCanvas" canvas-id="myCanvas"/>
    <button size="mini" bindtap="saveImage" type="primary"> 保存图片</button>
</view>

.poster {
    position: fixed;
    left: 0;
    top: 0;
    height: 100%;
    width: 100%;
    background-color: transparent;
    display: flex;
    flex-flow: column nowrap;
    justify-content: flex-start;
    align-items: center;
    z-index: 1999;
}
.poster .mask{
    position: absolute;
    background-color: #00000080;
    height: 100%;
    width: 100%;
    z-index: 2199;
}
.poster .canvas{
    position: absolute;
    margin-top: 5%;
    background-color: #fff;
    border: 1px solid #41BDF9;
    border-radius: 20rpx;
    z-index: 2299;
}
.poster button[size=mini] {
    position: absolute;
    bottom: 10%;
    z-index: 2299;
    margin-top: 50rpx;
    background-color: #41BDF9;
}

绘制相关js

本文canvas绘制使用的const ctx = wx.createCanvasContext('myCanvas'),开发工具中会提示新版本已废弃(效果如下图),但是可忽略,不影响。(新版调了半天一直没调成功,先不研究了)
在这里插入图片描述

宽高设置

const height = wx.getSystemInfoSync().windowHeight//系统高度
const width = wx.getSystemInfoSync().windowWidth//系统宽度
const rpxtopx = width / 750 //rpx转px系数

Page({
  data: {
    canvasW: width * 0.8, //画布宽度
    canvasType: false,
  },
  //点击遮罩层推出海报显示
  cancelPoster() {
    this.setData({
      canvasType: false
    })
  }
})

点击按钮后开始绘制

 async bindShare() {
    wx.showLoading({
      title: '图片正在生成'
    })
    // echarts单个图表的宽高,本文是实际宽高转换为px并缩小至70%,可根据自身设置
    const chart = {
      w: (width - 30 * rpxtopx) * 0.7,
      h: 450 * rpxtopx * 0.7
    }
    // 画布高度根据自身绘制内容设置
    this.setData({
      canvasH: 180 + 2 * chart.h + 10,
      canvasType: true
    })
    // 绘制
    await this.drawShareImage(chart)
    wx.hideLoading();
  },

绘制函数

具体canvas绘制api参数见微信小程序开发文档

    async drawShareImage(chart) {
    // 获取echarts图表临时路径
    const chart1 = await this.getChartImage("#chart1")
    const chart2 = await this.getChartImage("#chart2")
    // console.log(chart1,chart2)
    const text1 = “短文本”
    const text2 = “长文本长文本长文本长文本长文本长文本长文本长文本长文本长文本长文本长文本长文”

    // 获取绘图上下文, 参数为canvas-id
    const ctx = wx.createCanvasContext('myCanvas')
    
    // 绘制本地目录下的图片
    ctx.drawImage("../../resource/minicode.jpg", this.data.canvasW-70, 20, 50, 50);
    
    // 绘制echarts图表1
    ctx.setFontSize(14)//设置字体大小,忽略编辑器横线提示
    ctx.fillText(text1, 20, 40)//绘制短文本
    ctx.drawImage(chart1, 20, 80, chart.w, chart.h);//绘制临时文件路径的图片
    ctx.save();
    
    // 绘制echarts图表2
    ctx.fillText('XXX', 20, 100 + chart.h)
    ctx.drawImage(chart2, 20, 120 + chart.h, chart.w, chart.h);
    ctx.save();
    
    // 长段文字自动换行,按行截取
    ctx.setFontSize(12)
    const chr = text2.split("")
    let temp = ""
    let row = [] //每行文字数据
    let rownum=0
    for (let a = 0; a < chr.length; a++) {
      //最多显示两行,多于时...代替
      if (rownum >= 2) {
        row[1]+="..."
        break;
      }
      //判断该行文字绘制长度是否超过需求宽度(本文设置为chart.w - 20)
      if (ctx.measureText(temp).width < (chart.w - 20)) {
        temp += chr[a]
      } else {
        a--;//防止字符丢失
        row.push(temp)
        temp = ""
        rownum++
      }
    }
    row.push(temp)
    // 绘制每行文字
    for (let b = 0; b < row.length; b++) {
      //注意高度每行增加,否则会重叠在一行
      ctx.fillText(row[b], 20, 140 + 2 * chart.h + b * 20, chart.w - 20);
      }
    //绘制
    ctx.draw()
  },

echarts图表临时文件路径获取

试过使用echarts.js的api生成url,总是为空,后放弃该方法

// 获取echarts临时文件路径,需要修改源码ec-canvas.js
  getChartImage(id) {
    return new Promise((resolve, reject) => {
      //id示例,"#id",ec-echarts的canvas-id
      const ec = this.selectComponent(id)
      ec.canvasToTempFilePath({
        success: res => {
          // console.log(res)
          resolve(res.tempFilePath)
        },
        fail: res => {
          console.log("fail:", res)
          reject()
        }
      })
    })
  },

如果获取到为空,则修改ec-canvas目录下ec-canvas.js


    canvasToTempFilePath(opt) {
      if (this.data.isUseNewCanvas) {
        // 新版
        const query = wx.createSelectorQuery().in(this)
        query
          .select('.ec-canvas')
          .fields({
            node: true,
            size: true
          })
          .exec(res => {
            const canvasNode = res[0].node
            opt.canvas = canvasNode
            wx.canvasToTempFilePath(opt)
          })
      } else {
      	// 注释掉原来的代码
        // 旧的
        // if (!opt.canvasId) {
        //   opt.canvasId = this.data.canvasId;
        // }
        // ctx.draw(true, () => {
        //   wx.canvasToTempFilePath(opt, this);
        // });

        // TODO,自己修改部分
        if (!opt.canvasId) {
          opt.canvasId = this.data.canvasId;
        }
        const system = wx.getSystemInfoSync().system
        if (/ios/i.test(system)) {
          ctx.draw(true, () => {
            wx.canvasToTempFilePath(opt, this);
          });
        } else { //针对安卓机型异步获取已绘制图层
          ctx.draw(true, () => {
            //断点打印依旧不会执行setTimeout(() => {wx.canvasToTempFilePath(opt, this);}, 1000);}});
            ctx.draw(true);
            setTimeout(() => { //延迟获取
              wx.canvasToTempFilePath(opt, this);
            }, 1000);
          })
        }
      }
    },

保存至相册相关js

用户点击保存图片按钮

//获取已绘制好的canvas的临时文件路径
  saveImage() {
    wx.canvasToTempFilePath({
      canvasId: 'myCanvas',
      success: (res) => {
        const tempFilePath = res.tempFilePath
        // 判断是否授权保存到相册
        this.checkAuthSetting(tempFilePath)
      },
      fail: () => {}
    })
  },

判断是否授权保存到相册

checkAuthSetting(tempFilePath){
    wx.getSetting({
      success:(res)=>{
      	//是否已授权
        if (res.authSetting['scope.writePhotosAlbum']) {
          //已授权直接保存
          this.saveImageToPhotosAlbum(tempFilePath)
        }
      	//未授权请求授权
        else if (res.authSetting['scope.writePhotosAlbum'] === undefined) {
          wx.authorize({
            scope: 'scope.writePhotosAlbum',
            success: ()=>{
               //授权后保存
              this.saveImageToPhotosAlbum(tempFilePath)
            },
            fail: ()=>{
              wx.showToast({
                title: '您没有授权,无法保存到相册',
                icon: 'none'
              })
            }
          })
        } 
        //用户拒绝授权后,无法再直接调起请求授权,需要用户自己去设置
        else {
          wx.openSetting({
            success: (res)=>{
              if (res.authSetting['scope.writePhotosAlbum']) {
                //用户设置后保存
                this.saveImageToPhotosAlbum(tempFilePath)
              } else {
                wx.showToast({
                  title: '您没有授权,无法保存到相册',
                  icon: 'none'
                })
              }
            }
          })
        }
      }
    })
  },

保存至相册,提示分享,全屏显示图片

saveImageToPhotosAlbum(tempFilePath) {
	//保存
    wx.saveImageToPhotosAlbum({
      filePath: tempFilePath,
      success: (res) => {
        //提示用户已保存
        wx.showModal({
          content: '图片已保存,分享一下吧',
          showCancel: false,
          confirmText: '好的',
          confirmColor: '#333',
          success: (res) => {
          	//用户分享后
            if (res.confirm) {
              let arr = [];
              arr.push(tempFilePath);
              //全屏显示已保存图片
              wx.previewImage({
                urls: arr,
                current: arr
              })
            }
          },
          fail: (res) => {}
        })
      },
      fail: () => {
      }
    })
  },

canvas踩坑

canvas随页面滑动问题

真机测试时,canvas父元素为弹窗fixed布局,当页面滑动时,canvas会跟随页面滑动造成显示混乱

  1. 无效方案:<canvas>设置disable-scroll="true" bindtouchmove="touchMove"只能阻止在canvas画布内滑动时页面不滑动,不能解决页面滑动时画布跟随滑动的问题
  2. 有效方案:遮罩层设置 catchtouchmove="true",这样在弹窗出现时,从根本上阻止页面滑动,从而保证canvas不滑动
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值