微信小程序canvas实现海报生成

3,770 阅读7分钟

做了个海报生成的需求,顺便整合一下问题和js,以后再做海报生成方便些。

Canvas海报生成

问题

canvas

定义一个canvas标签并定义canvas-id

<canvas canvas-id="poster" ></canvas>

canvas的css属性

//注意这里的width和height就是你最终生成图片的宽高
<style>
  canvas{
    width:100px;
    height:100px;
  }
</style>

获取的canvas实例,this就是小程序的当前页面的this指向。(注意一下异步函数里的this指向)

let context = wx.createCanvasContext('poster', this);

矩形问题

圆角矩形

矩形中问题比较麻烦的就是圆角矩形了,css中可以直接使用border-radius来设置圆角,canvas就不行了。

查了一下做法差不多都是这种,我就简单点了。

1,四角个画一个圆 2,在画线或矩形来作为四边 3,最后一个矩形来完成中间的填充

//绘制top直线
this.context.fillRect(x + r, y, w - 2 * r, r)
//开始绘制左上角半圆
this.context.arc(x + r, y + r, r, 0, 2 * Math.PI)
//绘制bottom直线
this.context.fillRect(x + r, y + h - r, w - 2 * r, r)
//开始绘制右上角半圆
this.context.arc(x + w - r, y + r, r, 0, 2 * Math.PI)
//绘制left直线
this.context.fillRect(x, y + r, r, h - 2 * r)
//开始绘制左下角半圆
this.context.arc(x + r, y + h - r, r, 0, 2 * Math.PI)
//绘制right直线
this.context.fillRect(x + w - r, y + r, r, h - 2 * r)
//开始绘制右下角半圆
this.context.arc(x + w - r, y + h - r, r, 0, 2 * Math.PI)
//绘制中间层
this.context.fillRect(x + r, y + r, w - 2 * r, h - 2 * r)

这样一个圆角矩形就创建完成了!是不是很麻烦啊

图片问题

跨域!!!

最最最麻烦的图片跨域污染canvas的问题 微信小程序中不能new Image(),所以设置不了crossOrigin:Anonymous

crossOrigin:Anonymous可以解决js中图片跨域污染canvas画布的问题。
但是没设置Access-Control-Allow-Origin头部信息的图片使用crossOrigin属性也没用。
可以去阿里官网那里看看,有些图片上是没有Access-Control-Allow-Origin的。

小程序canvas图片跨域的坑

1,在微信调试工具上你能发现,哪怕是跨域并且没有设置Access-Control-Allow-Origin:*的图片在画布上一样可以渲染出来,并且有可以生成合成图片!(如果没有,就把不检验域名打开。另外那些404域名的图片就不用试了,比如谷歌的)
2,这里就是一个坑了,微信小程序canvas上图片只支持本地图片!本地图片!本地图片!。哪怕你能看见手机测试时候也是生成不了的。
3,网络图片是需要下载到本地才能进行渲染的

wx.getImageInfo({
    src: src,//需要下载的图片网络路径
    success: function (res) {
        res.path//图片路径
    }
});

4,网络图片下载需要注意你是否将域名添加到了Down的域名白名单下,注意https和http

上面说了canvas图片跨域的坑,这里说一下图片渲染时的坑吧

渲染时遇到最麻烦的坑就是下载网络图片时是异步的,而且有延时性(来自图片的下载速度)。所以你会发现下载完成后网络图片并没有渲染到canvas上去,这是应为draw已经先于图片下载完成了。

解决网络图片下载的问题

1,小图可以直接保存在本地
2,用户进入时找时机异步下载会使用的图片保存在本地,然后地址存在storage中。
3,应为一些需求原因,有些图片需要实时生成(例:二维码)在合成图片时在下载需要的图片。等待网络图片下载完成,在与其他文字等数据一同渲染。这步就需要久的时间了。
这里可以使用Promise.all来完成异步的图片下载,等待所有回调完成时。在去执行统一的canvas渲染。

如果你想等同步渲染完成,再去一步一步的渲染异步的图片,你会发现后面的异步图片遮盖了同步时渲染的数据。而且每张图片的下载时间是无法控制的。

文字问题

文字换行

canvas这里没有文字自动换行的功能,要使用js来控制每行的字数。

/** 
   * @param {breakLinesForCanvas}进行分割文字
   * @param {String}text:文字
   * @param {Number}width:文字每行宽度
   */
  breakLinesForCanvas(text, width, Ellipsis) {
    let result = [],
      breakPoint = 0;
    //进行文字换行分割
    //breakPoint获取到分割的文字数量
    while ((breakPoint = this.findBreakPoint(text, width)) !== -1) {
      //保存分割的文字
      result.push(text.substr(0, breakPoint));
      //去除分割的文字
      text = text.substr(breakPoint);
    }
    if (text) {
      result.push(text);
    }
    if (Ellipsis && result.length >= Ellipsis) {
      result[Ellipsis - 1] += '...'
      result = this.breakLinesForCanvas(result.join(''), width, false)
      result.length = Ellipsis
    }
    return result;
  }

  /**
   * @param {findBreakPoint}进行二分法查找
   * @param {String}text:文字
   * @param {Number}width:宽度
   */
  findBreakPoint(text, width) {
    var min = 0;
    var max = text.length - 1;

    while (min <= max) {
      let middle = Math.floor((min + max) / 2);
      //使用measureText测量一半text的宽度
      let middleWidth = this.context.measureText(text.substr(0, middle)).width;
      //使用measureText测量一半 + 1text的宽度
      let oneCharWiderThanMiddleWidth = this.context.measureText(text.substr(0, middle + 1)).width;
      //获取width能获取的最大文字数量
      if (middleWidth <= width && oneCharWiderThanMiddleWidth > width) {
        return middle;
      }
      //一半文字的宽度大于文字的宽度时递减文字数量,小于时递增文字数量
      if (middleWidth < width) {
        min = middle + 1;
      } else {
        max = middle - 1;
      }
    }
    return -1;
  }

这里两个函数就解决了文字换行的问题。
主要使用findBreakPoint函数分割文字。
canvas.measureText来计算文字的width宽度是否超出限制
Ellipsis则是截断文字是否使用省略号。

文字根据上文来换行,防止文字重叠

文字就会有换行,但是下面另外的文字的x没有变化,这导致文字重叠。

就这样232323..这段文字是用了计算上文而55555没使用,就会出现文字重叠。

还有同行文字不同颜色,这个有点麻烦还有换行的问题,想了思路但还没写。先把实例放上来了,可以康我的git连接。

另外再说一个问题,微信小程序碰到一些自带方法不可用及不报错,也不报问题。 Promise.all这里因为返回的是一个数组,所以我用了flat()方法来扁平化数组,但是好像现在版本不支持WDNMD。

这里说几个注意点吧

1, 海报前端合成的时间全看用户的网络和手机性能。网络图片尽量减少体积。

2, 海报合成时真机调试和手机都试一下,免得环境问题导致有点使用不了

3, 应为网络图片需要下载到本地,所以注意自己配置的downloadFile白名单(注意http和https)

这里贴一下最终canvas图片生成后下载图片的代码吧,传入图片的url就行。

DownImage(url) {
    wx.getSetting({
      success(res) {
        // 如果没有则获取授权
        if (!res.authSetting['scope.writePhotosAlbum']) {
          wx.authorize({
            scope: 'scope.writePhotosAlbum',
            success() {
              wx.saveImageToPhotosAlbum({
                filePath: url,
                success() {
                  wx.showToast({
                    title: '保存成功'
                  });
                },
                fail() {
                  wx.showToast({
                    title: '保存失败',
                    icon: 'none'
                  });
                }
              });
            },
            fail() {
              wx.openSetting({
                success(res) {}
              });
            }
          });
        } else {
          // 有权限直接保存
          wx.saveImageToPhotosAlbum({
            filePath: url,
            success() {
              wx.showToast({
                title: '保存成功'
              });
            },
            fail() {
              wx.showToast({
                title: '保存失败',
                icon: 'none'
              });
            }
          });
        }
      }
    });

github地址:
github.com/WSQwansui/C…

    //canvas的实例
    let context = wx.createCanvasContext('poster', this)
    //渲染所使用的数据
    let List = [
    {
        type: 'background',
        color: '#26B2AF',
        x: 0, 
        y: 0,
        width: 375,
        height: 597,
        id: 'poster'
      },
      {
        x: 26,
        y: 250,
        width: 323,
        height: 300,
        type: 'view',
        color: '#fff',
        radius: true,
        radiusSize: 10
      },
      {
        src: 'https://docs.alibabagroup.com/assets2/images/cn/home/home_banner_1.png', //图片路径
        x: 0,
        y: 0,
        width: 375,
        height: 190,
        type: 'image',
        local: false,
        zIndex: 10
      },
      {
        src: '../../images/canvas_1.png',
        x: 26,
        y: 500,
        width: 100,
        height: 30,
        type: 'image',
        local: true,
        TextAlign: true,
      },
      {
        text: '232323232', //文字文本
        x: 50, //x轴起始位置
        y: 380, //y轴起始位置
        width: 300, //文本宽度
        font: 'bold 20px 微软雅黑', //字体大小与字体,默认14px 微软雅黑
        color: '#26B2AF', //颜色
        LineHeight: 0, //文字换行高度设置
        TextAlign: true,
        type: 'text',
        Relation: {
          id: 1,
          height: 30
        }
      }, {
        text: '232323232', //文字文本
        x: 50, //x轴起始位置
        y: 380, //y轴起始位置
        width: 300, //文本宽度
        font: 'bold 20px 微软雅黑', //字体大小与字体,默认14px 微软雅黑
        color: '#26B2AF', //颜色
        LineHeight: 0, //文字换行高度设置
        TextAlign: true,
        type: 'text',
        Relation: {
          id: 1,
          height: 30
        }
      }
    ];
    //初始化数据与canvas
    let SynPoster = new poster.poster(context, List)
    //最后合成,canvas合成也在这步,then中返回的就是最终的url
    SynPoster.Synthesis()
      .then(res => {
        console.log(res);
      })
      .catch(err => {
        console.log(err);
      });

给个三连吧,哐哐哐。