# 小程序也能做这么精致的动效？看完我给大神献上了膝盖…… \| 开发

- 作者：知晓程序
- 发布时间：2018-04-04 17:30
- 原文：https://www.ifanr.com/minapp/1000278

![](https://s3.ifanr.com/wp-content/uploads/2018/03/fm-3-1024x640.jpg)

小池是一款具有吸引力的预算应用程序，允许用户通过轻松录入，享受记账的幸福，并为用户提供大量非凡的记录输入！

在这篇文章中，会详细介绍如何做出如图的交互效果，我们将根据 canvas 画布渲染圆球所需的步骤进行讲解。

![](https://s3.ifanr.com/wp-content/uploads/2018/03/1-15.gif)

如 GIF 图中所展示的效果，黏糊糊的粘连的路径是由 metaball 函数来创建出的，然后根据返回的路径坐标值，再基于贝塞尔曲线使用 canvas 画布绘制而成。

### 实现原理

可以看到，这个交互效果主要由两个圆球组成，**它们之间存在三种情况：完全重合，完全分离和有交叠部分。**

在两圆完全重合时，小圆不会出现；当两圆之间距离超出设置的最大连接距离时，两圆会完全分离不接触；而重点在于第三种情况，两圆有接触但没有完全重合。

当第三种情况发生时，我们就需要考虑以下几点：

- 两圆有接触没有完全重叠，会出现两条弧形的轨迹，在这里称它为连接器。
- 两圆之间的切线就是连接器的最宽处（如图1-1），通过 metaball 函数计算出四个切点位置，假设切点分别为 p1，p2，p3，p4。（考虑到图形效果，切点位置还与控制因子 v 相关，当 v=0.5 时效果最佳）
- 把梯形形状的连接器转换为弧形轨迹（如图1-1），需要 metaball 函数计算出四个控制点位置，假设控制点分别为 h1，h2，h3，h4。
- 当发生移动时，这八个点的状态如图 1-3 所示。
- metaball 函数的功能就是通过计算出这八个点的坐标，并返回出来。

![](https://s3.ifanr.com/wp-content/uploads/2018/03/h-1.jpg)

图 1-1、图 1-2

![](https://s3.ifanr.com/wp-content/uploads/2018/03/3-6.gif)

图 1-3

### 实现方法

下表为 metaball 函数的参数与返回值信息：

![](https://s3.ifanr.com/wp-content/uploads/2018/03/bg.jpg)

了解 metaball 函数的实现原理后，下面讲解使用 canvas 来绘制图形的步骤：

1\. 引入metaball 函数

```
import metaball from 'utils/metaball.js'
```

2\. 设置大圆/小圆的半径和中心点坐标

```
radius1 = r1
radius2 = r2
center1 = { x: x1, y: y1 }
center2 = { x: x2, y: y2 }
```

3\. 设置两圆分离的最大距离

```
maxDist = radius1 + radius2 * 2；
```

4\. 创建 canvas绘图上下文（传入定义在 &lt;canvas/&gt; 的 canvas-id）

```
ctx = wx.createCanvasContext('canvas');
```

值得在注意的是，在手指触摸动作开始事件 touchstart 触发的事件回调函数中：

- 通过参数 e.touches\[0\].x/y 获取当前触摸点位置信息，并赋值给小圆的圆心位置变量。
- 在定时器中，每次要先清除画布，然后调用 metaball 函数，使用数组变量 a 接收 metaball 函数的返回值，并对返回结果进行如下判断：

![](https://s3.ifanr.com/wp-content/uploads/2018/03/bg2.jpg)

```
center2.x = e.touches[0].x 
center2.y = e.touches[0].y 
setInterval(() => {
      ctx.clearRect(0, 0, width, height);
      a = metaball(
        radius1,
        radius2,
        [center1.x, center1.y],
        [center2.x, center2.y],
        maxDist
      );
      if (a.length <= 1) {} if (a.length >= 2) {
        ctx.beginPath()
        ctx.setFillStyle('#ffffff')
        ctx.arc(center2.x,center2.y,radius2,0,Math.PI * 2,true)
        ctx.fill()
      }
      if (a.length > 2) {
        ctx.beginPath()
        ctx.setFillStyle('#ffffff')
        // [p1, h1, h3, p3, p4, h4, h2, p2]
        ctx.moveTo(a[7][0], a[7][1])
        ctx.lineTo(a[0][0], a[0][1])
        ctx.bezierCurveTo(
          a[1][0],
          a[1][1],
          a[2][0],
          a[2][1],
          a[3][0],
          a[3][1]
        )
        ctx.lineTo(a[4][0], a[4][1])
        ctx.bezierCurveTo(
          a[5][0],
          a[5][1],
          a[6][0],
          a[6][1],
          a[7][0],
          a[7][1]
        )
        ctx.fill()
      }
      ctx.draw()
    }, 20)
```

- 手指触摸后移动事件 touchmove 触发的事件回调函数中监听手指移动事件，将当前的触摸点位置信息赋值给小圆的圆心位置变量。

```
center2.x = e.touches[0].x
center2.y = e.touches[0].y
```

- 手指触摸动作结束事件 touchend 触发的事件回调函数中把小圆的圆心位置重置到大圆的圆心位置。

```
center2.x = center1.x
center2.y = center1.y
```

以上就是「小池记账」小程序的主页交互效果制作，当你认真去思考去完善一个小程序时，你会发现，原来小程序也可以很美。

本文作者：牛咖

![](https://s3.ifanr.com/wp-content/uploads/2018/04/zhgqb3kx860huu1eksyztxp45lmyo1m7-1.png)

「小池记账」小程序使用链接  
<https://minapp.com/miniapp/6042/>

**关注「****知晓程序****」微信公众号，回复「开发」，获取小程序开发技巧大全。**

![](https://s3.ifanr.com/wp-content/uploads/2018/01/qrcode.gif)
