cobish.github.io icon indicating copy to clipboard operation
cobish.github.io copied to clipboard

小程序 - 刻度尺组件的实现原理

Open cobish opened this issue 5 years ago • 0 comments

使用

需求里有一个需要拖动选择年龄的刻度尺,但小程序里没有自带该组件,所以找到了一个 「wx-scale」 的修改一下。

使用效果如下:

原理

刚开始自己也没有思路来实现,于是找到源码简单了解一下实现,发现其实也不难。

实现如下:

简单讲一下,通过 canvas 画出卡尺,再导出成一张图片,此时 canvas 处于隐藏的状态。然后监听 scroll-view 的滚动事件,并计算当前刻度。

其中有几个需要注意的地方:

画 canvas

官方文档里的这行代码是画不出东西的:

const ctx = wx.createCanvasContext('myCanvas');

需要将第二个参数带上才可:

const ctx = wx.createCanvasContext('myCanvas', this);

小程序里 canvas 的单位是 px,所以不同屏幕看到的刻度尺大小是一致的。

如果要考虑适配不同屏幕,需要使用 rpx,可以根据屏幕宽度来将 rpx 转换成 px。

1rpx = 屏幕宽度 / 750

具体可以参考「小程序坑-canvas」

导出图片

由于小程序基础库 2.4.0 以下 scroll-view 不支持嵌套 canvas,所以需要将 canvas 导出成图片放入 scroll-view,并将 canvas 隐藏。

所以基础库在 2.4.0 以上不用这么麻烦,直接 scroll-view 里嵌套 canvas 即可。

需要注意的是,官方文档里的 draw 完成后会有回调函数,但并不那么有效,所以保险点最好加上一个定时器。

ctx.draw(true, setTimeout(() => {
    // 导出图片
}, 300);

由于 canvas 属于原生组件,层级是最高的,源码里使用的 index: -1 在手机上还是能看得到 canvas,所以我将 canvas 的隐藏样式修改为:

.canvas {
    position: fixed;
    left: 100%;
    box-sizing: border-box;
}

滚动事件

滚动事件会有一个回传给父组件的值(当前刻度),用于父组件的数据更新,即:

this.triggerEvent('value', {
    value: resultNum
});

因为滚动事件是频繁调用的,如果父组件用于 setData 渲染的话,那会一直 setData,造成性能上的卡顿,所以最好加上函数节流。

// 函数节流
const delay = 250;
const curr = +new Date();
if (!rul.last || curr - rul.last > delay) {
    this.triggerEvent('value', {
        value: resultNum
    });
    rul.last = curr;
}

同时,在滚动事件结束后,会计算卡尺的值,从而进行整数的对齐,所以用到函数防抖,只在滚动结束的最后一次触发,而不是在滚动时频繁触发。

// 函数防抖
clearTimeout(rul.Timer);
rul.Timer = setTimeout(() => {
    this.setData({
        // ...
        centerNum: redNum,
        active: resultNum
    });

    this.triggerEvent('value', {
        value: resultNum
    });
}, 100);

cobish avatar May 30 '19 03:05 cobish