微信小程序 canvas 绘图方法及安卓手机出现绘图不准解决方案

微信小程序的canvas诟病颇多,直到调试及出库 2.2.0 版本依然如此。

1. 微信 canvas 绘制也是一个异步函数,在 ctx.draw() 后面直接获取 canvasToTempPath 是不能获取到 canvas 的具体内容的。

2. 微信小程序有很多 bug,尤其在布局上,例如 z-index 无效,定位问题,拖动问题等;因此在界面上展示 canvas 绘制的内容时,通常使用 image 作为显示主体。

3. ctx.draw() 异步操作

ctx.draw(false,function(){

      // 当 canvas 绘制完成之后,调用,可以生成临时路径,并且获取完整的图片,但是在安卓的部分设备上会出现渲染不全的效果,因此需要使用延迟加载 setTimeout 函数回避渲染过慢的问题。

})

4. 将 canvas 绘制到 page 中才能获取准确的图片元素,因此,需要设置单独的 canvas 不能为 hidden,隐藏到 page的最底部,不显示出来是一种处理方式

示例部分:

但是在安卓设备上使用这种方法进行绘制时,也会造成一些莫名错误,例如,随机的会产生绘制元素走样的情况

布局部分

wxml:: v2.2.0 官方提示

https://developers.weixin.qq.com/miniprogram/dev/component/canvas.html#canvas

# 微信的 canvas 必须使用 canvas-id 作为身份标识

# 微信的 canvas 不再 demo 中,只存在于最高层,不能通过 z-index 控制

# 请勿在 scroll-view、swiper、picker-view、movable-view 中使用 canvas 组件。

# css 动画对 canvas 无效

# bug: 避免设置过大的宽高,在安卓下会有crash的问题

因此,在使用微信的 canvas 渲染 demo 中的元素时,要回避直接使用微信 canvas 组建;使用获取 canvas 绘制的图片文件,将其保存成临时文件,然后绘制到 image 元素上。所幸,微信小程序在保存临时文件这块功能十分强大。

wx.canvasToTempFilePath({
          x: 0,
          y: 0,
          width: conf.draw_width,
          height: conf.draw_height,
          destWidth: conf.draw_width,
          destHeight: conf.draw_height,
          canvasId: target_canID,
          fail: function (res) {
            	console.log(res)
          },
          success: function (res) {
              if (cb) {
              		cb(res.tempFilePath);
              };
          }
});

很明显,在渲染字体参数时,微信的 canvas 并没有按照原设定参数绘制。这种事件产生的概率在安卓设备上很大,尤其时华为设备(华为的安卓机两台都出现了该现象),然而在苹果或者微信自己的模拟器上就没有类似 Bug 产生。

解决方法:使用惰性加载(延迟加载)方法,在调用 ctx.draw(false,function(){}) 的回调函数部分,使用 setTimeout 延迟获取 canvasToTempFilePath 可以避免 canvas 在安卓机器上绘制不全的情况。

<view class="top">
    <view class="text">历史测试结果</view>
    <view class="card">
        <view class="t">当前词汇量 :<text class="big">{{vocabulary}}</text></view>
        <image src="{{wx_his_img}}" mode="aspectFit" />
    </view>
</view>

<view class="canWrap">
    <canvas canvas-id="canHis" style="width: 1200rpx; height: 800rpx;" />
</view>

wxss::

# 微信 2.2.0 版本之前,试验过 display : none; height : 1px 等常用的隐藏元素方法,都不能准确的读取 canvas 中的元素内容

# 因此使用将显示元素移动到显示区域外的做法,将 canvas 隐藏起来

.canWrap{
    /* 该处必须使用绝对定位,并且定位到显示区域外面,如果 canvas 的大小显示不全,则保存到 image 元素中的大小也是不准确的 */
    position: fixed;
    bottom: -2000rpx;
    left: 0rpx;
    width: 100%;
    height: 100%;
    overflow: visible;
}
.canWrap canvas{
    /* 该处必须给定 canvas 大小 */
    width: 690rpx; 
    height: 450rpx;
}

测试数据:

bar_list =  [
        {
            "date": "2018\u5e747\u670824\u65e5",
            "amount": 557
        },
        {
            "date": "2018\u5e747\u670824\u65e5",
            "amount": 8801
        },
        {
            "date": "2018\u5e747\u670824\u65e5",
            "amount": 8023
        }
]

具体绘制部分参考:

function wtHistory_canvas(canID, bar_list, cb){
    // 需要对应 canvas 的比例
    const f = 2;

    let conf = {
        // 画布大小
        draw_width : 600 * f,
        draw_height: 400 * f,

        font_size: 15 * f,
        font_size_lit: 12 * f,

        bar : {

            x: 0,
            y: 0,

            interval: 60 * f,

            width: 40 * f,
            max_height: 150 * f,

            text_y: 170 * f,

            amount_max: 0

        },  

        // 图形直线
            start : {
                x: 0 * f,
                y: 170 * f
            },
            to : {
                x: 320 * f,
                y: 170 * f
            }
        }
    }

    // 初始化部分

    for (let i = 0; i < bar_list.length; i++){

        if ( conf.bar.amount_max < bar_list[i].amount ) {

            conf.bar.amount_max = bar_list[i].amount;

        }

    };

    const ctx = wx.createCanvasContext(canID);

    // 绘制列表部分
    for (let i = 0; i < bar_list.length; i++){

        let _h = bar_list[i].amount / conf.bar.amount_max * conf.bar.max_height;

        draw_lit(
            i * (conf.bar.width + conf.bar.interval) ,
            conf.bar.max_height - _h, 
            conf.bar.width,
            _h,
            bar_list[i].date,
            conf.bar.text_y + 15 * f,
            bar_list[i].amount
        );

    };

    // x 坐标直线
    coords_x();

    // 绘制直方图
    function draw_lit(x, y, width, height, text, text_y, amount_text){

        // amount_text 图形上面文字
        ctx.setFillStyle('#494949');
        ctx.setFontSize(conf.font_size);
        ctx.fillText(amount_text, x + 25*f, y + 12*f);

        // 绘制条形区域
        ctx.setFillStyle('#17b1ef');
        ctx.fillRect(x + 25*f, y + 20*f, width, height);

        // 线下文字
        ctx.setFillStyle('#cccccc');
        ctx.setFontSize(conf.font_size_lit);
        ctx.fillText(text, x, text_y);

    };

    // 绘制 x 坐标线
    function coords_x(){

        ctx.setStrokeStyle("#00ade1");
        ctx.moveTo(conf.line.start.x, conf.line.start.y);
        ctx.lineTo(conf.line.to.x, conf.line.to.y);
        ctx.stroke();

    };

    // 绘制三角形
    function triangle(opts, color, rot) {

      _ctx.beginPath();
      _ctx.setFillStyle(color);
      _ctx.moveTo(opts.x, opts.y);
      _ctx.lineTo(opts.x + 5, opts.y - 5);
      _ctx.lineTo(opts.x + 5, opts.y + 5);
      _ctx.closePath();
      _ctx.fill();

    }

    ctx.draw(true, function () {

      // 判断绘制方法,判断手机类型
      if (app_global.cache.phoneInfo.brand == 'iPhone'){

        wx.canvasToTempFilePath({
          x: 0,
          y: 0,
          width: conf.draw_width,
          height: conf.draw_height,
          destWidth: conf.draw_width,
          destHeight: conf.draw_height,
          canvasId: canID,
          fail: function (res) {
            console.log(res)
          },
          success: function (res) {

            if (cb) {
              cb(res.tempFilePath);
            };

          }
        })

      } else {

        // 延迟加载
        setTimeout(function () {

          wx.canvasToTempFilePath({
            x: 0,
            y: 0,
            width: conf.draw_width,
            height: conf.draw_height,
            destWidth: conf.draw_width,
            destHeight: conf.draw_height,
            canvasId: canID,
            fail: function (res) {
              console.log(res)
            },
            success: function (res) {

              if (cb) {
                cb(res.tempFilePath);
              };

            }
          })

        }, 80)

      }

    });

}

猜你喜欢

转载自blog.csdn.net/h357650113/article/details/81189370