likes
comments
collection
share

小程序H5实现canvas设定底图绘制功能以及闪退问题处理

作者站长头像
站长
· 阅读数 14

前言

嗨~ 友友们,最近由于业务需要开始转战小程序开发,有一个业务需求就是使用canvas进行渲染底图,并在底图上进行绘制,数据来源主要是第三方提供的数据点坐标。这里会分别对小程序和H5进行区别实现,希望可以对友友们有帮助哦~

思路分析

因为业务需要就是在底图上绘制数据点坐标,实现步骤如下:

  1. 初始化canvas对象

    小程序版:参照微信开放文档Canvas画布,进行画布初始化,注意哦,这里的ctx对象是在异步回调中返回的,需要处理异步的问题。

    小程序调试时需注意,canvas不支持真机调试,想要在真机调试模式调试画布需要开启调试模式(亲测可以,官方没有说明)。

    小程序开发工具中定位在画布上的元素会被画布遮挡,但是在预览时可以看到定位元素的效果哦~

<canvas id="myCanvas" disable-scroll="false" type="2d"></canvas>

const query = wx.createSelectorQuery();
query.select('#myCanvas')
.fields({ node: true, size: true })
.exec((res) => {
  let canvasDom = res[0].node;
  const ctx = canvasDom.getContext('2d');

  // 初始化画布大小:注意哦!此处可能引起闪退,屏幕像素比越大,canvas就会越大哦
  const dpr = wx.getSystemInfoSync().pixelRatio;
  canvasDom.width = canvasW * dpr;
  canvasDom.height = canvasH * dpr;
  ctx.scale(dpr, dpr);
});
  

H5版:参照MDN web docs 以下代码基于Vue3.0,友友们在使用时可根据自己的开发框架参照文档进行调整。

<canvas ref="canvas" class="canvas" type="2d"></canvas>

const canvas = ref(null) // canvas 的 dom节点
const ctx = canvas.value.getContext('2d')
const dpr = window.devicePixelRatio // 像素比
// 初始化画布大小
this.canvasDom.width = canvasW * dpr 
// canvasW指的是canvas需要渲染的宽度,canvasH是需要渲染的高度
this.canvasDom.height = canvasH * dpr
ctx.scale(dpr, dpr)

this.canvasContext = ctx

注意事项:因为使用动态图片作为canvas的底图,所以为了更好的适配图片,可能需要进行等比计算,计算出图片使用的最佳尺寸,如需要适配平板也需要跟产品和设计沟通具体事宜哦~

  1. 绘制底图

    小程序版:使用文档提供canvas加载图片的createImage结合drawImage进行绘制。

    文档上说明小程序绘制的网图需通过getImageInfo / downloadFile先下载得出本地临时路径

// 绘制底图
ctx.beginPath();
const img = canvasDom.createImage();
img.src = url;
img.onload = function (e) {
    ctx.drawImage(this, 0, 0, canvasW, canvasH);
};

H5版:使用Image对象进行访问图片,使用drawImage进行绘制图片。

ctx.beginPath()
const img = new Image()
img.src = image
img.onload = function (e) {
    ctx.drawImage(this, 0, 0, canvasW, canvasH)
}
  1. 遍历点坐标数据列表,绘制点坐标

    绘制坐标点,这里就是挪动到起始点,开始绘制的过程,中间的临界值条件需要友友们根据需求进行处理哦~

    这里在小程序 安卓机运行时出现了闪退的问题,并没有报错,最后经过调试,发现是频繁调用了ctx.save()导致的,小程序的Canvas组件使用的是GPU加速,但是其内存限制较为严格,而使用ctx.save()在绘制时占用了过多的内存,就可能导致闪退。

for (let i = 0; i < points.length; i++) {
  const obj = lp[i];
  ctx.moveTo(this.lastPoint.x, this.lastPoint.y); // 移动坐标
  ctx.lineTo(pointX, pointY); //连线
  ctx.stroke(); // 绘制空心
  // ctx.save(); // 保存绘制状态 导致闪退的原因
}

完整代码:

const real_Width = 500; // 这里另一个参照的实际尺寸
const real_height = 500; // 这里另一个参照的实际尺寸
const systemInfo = wx.getSystemInfoSync();
const screenW = systemInfo.windowWidth; // 屏幕可视宽
const screenH = systemInfo.windowHeight; // 屏幕可视高

let canvasW = null, canvasH = null; // 画布宽高
const wh = screenW / screenH;
if (wh < real_Width / real_height) {
  canvasW = screenW;
  canvasH = (screenW * real_height) / real_Width;
} else {
  canvasW = (screenH * real_Width) / real_height;
  canvasH = screenH;
}

export default class Draw {
  lastPoint = { x: null, y: null } // 上一个点
  canvasContext = null
  url = null // 底图本地地址
  async constructor(url) {
      this.url = url
      const { ctx } = await this.init()
      this.canvasContext = ctx
  }

  init() {
    return new Promise((resolve, reject) => {
      const query = wx.createSelectorQuery();
      query.select('#myCanvas')
        .fields({ node: true, size: true })
        .exec((res) => {
          let canvasDom = res[0].node;
          const ctx = canvasDom.getContext('2d');
  
          const dpr = wx.getSystemInfoSync().pixelRatio;
          // 初始化画布大小
          canvasDom.width = canvasW * dpr;
          canvasDom.height = canvasH * dpr;
          ctx.scale(dpr, dpr);

          // 开始绘制
          ctx.beginPath();
          ctx.strokeStyle = '#000000';
          ctx.lineCap = 'round';
          ctx.lineJoin = 'round';
          const img = canvasDom.createImage();
          img.src = this.url;
          img.onload = function (e) {
            // 底图
            ctx.drawImage(this, 0, 0, canvasW, canvasH);
          };
          resolve(ctx);
        });
    });
  }
  draw(points) {
    for (let i = 0; i < points.length; i++) {
      const obj = points[i];
      this.drawPoints(obj);
    }
  }
  drawPoints(point) {
    const ctx = this.canvasContext;
    const x = point.x;
    const y = point.y;

    const pointX = (x * canvasW) / real_Width;
    const pointY = (y * canvasH) / real_height;
   
    if (条件) {
        ctx.moveTo(this.lastPoint.x, this.lastPoint.y);
        ctx.lineTo(pointX, pointY);
    } else {
        ctx.moveTo(pointX, pointY);
    }
    this.lastPoint.x = pointX;
    this.lastPoint.y = pointY;
    ctx.stroke();
    // ctx.save();
  }
}

结语

友友们,今天也是收获满满的一天呀!以上代码纯属脑电波运行,如有bug,请自行解决或下方留言哦~ 如果你看到这里了,那就请用你发财的小手给作者点个小赞赞吧~

转载自:https://juejin.cn/post/7216213764776738873
评论
请登录