前端视觉交互——DigitalClock3D

少女dtysky

世界Skill

时刻2018.04.22

一个基于THREE和GSAP(Tween)实现的3D数字时钟。 这个也是以前见过的一个效果,觉得很不错就尝试做出来了。
code,demo

Cover

原理

数字时钟想必大家基本都实现过,其逻辑模型很简单,就是用JS的API获取时间,然后将时分秒分别拆分计算出来,最后渲染到页面上。而本效果的重点是在于这个渲染。

效果拆解

首先让我们拆开这个时钟,不难发现其主要由六个数字和两对点构成,数字表明时间,由一个类似于魔方的立方体构成,点则用于分隔,由两个反色的cube构成,这个是单纯静态渲染方面的问题。而另一方面,如何让数字和点动起来也是个问题。也就是说,整个效果被规约为了以下几个小问题:

  1. 如何在平面渲染一个数字
  2. 如何在同一个立方体渲染多个数字
  3. 如何渲染点
  4. 如何让数字和点动起来

下面就这几点来做一下分析:

在平面上渲染一个数字

渲染一个数字并不困难,有过硬件背景或者做过数电实验的同学,应该都接触过数码管这东西。而在本效果中渲染一个数字和数码管其实没什么区别,本质上都是设置几个格子,通过将格子染上不同的颜色来表现出一个数字。在本例中,采用标准的3x5的格子阵列来表示一个数字,其中前景为黑色的格子,背景为白色的格子,倘若用1代表黑色格子,0代表白色格子,则数字4将有如下表现:

1 0 1
1 0 1
1 1 1
0 0 1
0 0 1

这就是一个数字在平面上的模型,换言之,只要立方体面对用户的这一面的格子颜色分配如上,用户就会看到一个4

在同一个立方体渲染多个数字

以上的模型可以在平面渲染0-9的任意一个数字,让我们将思想扩展到三维,会发现每个格子都是组成数字的那个3x5x3的立方体中的每一个小cube在x-y平面上的投影。

那么如何让一个立方体能表现出多个数字呢?你的第一想法一定是:“每次更新这些格子,让它的数字变化不就可以了”。这当然是一种思路,但这种思路过于粗暴,无法实现本例中的效果,让我们仔细观察本例中数字的变换方式,它并非瞬变,而是通过不同行绕Y轴的渲染实现的。

这就产生了一个难题:我们需要合理得设置立方体中每一个cube的颜色,来使其可以通过绕Y轴的旋转来达到在x-y平面上投影出不同数字的目的。

这段话看起来拗口,其实说的问题很简单,就是说我们要在一定的约束下,构造一个立方体,这个立方体在Y轴方向上有5行,每一行都是一个有3x3个cube构成的阵列,每一行的旋转是独立的,每次旋转都会选择其一面朝向用户(即投影到 x-y平面),所有行投影的组合就构成了一个数字。这样一来,如何构造这个立方体就成了问题的症结所在,让我们仔细思考一下有哪些约束:

  1. 立方体由五行构成,每一行都是3x3的cube阵列
  2. 每行通过旋转构造一个x-y平面投影,投影可以组合出任意数字
  3. 每行的每个面之间有强关联,边角上的cube是重合的,投影后每一面的边角的那个格子会和相邻面耦合
  4. 需要构造每个数字的格子组合固定,总共十种

进行分析后,不难发现这其实可以通过枚举(也没别的办法)得出每一个数字需要的格子组合,然后反推出每一行需要的投影,最后再通过这些投影反推出每一行的cube颜色构成,最后再推算需要渲染一个数字时,每一行需要以那一面出现(即旋转到什么角度)。

根据这个思想,我写了个脚本num_helper.py来计算每一行可能出现的投影的组合,并将反算结果存在了NumbersLUT内,不难发现,这过程中我其实最终将问题规约成了一个对九宫格的染色问题:

0 0 1
0 0 0
1 1 1
(第一行的顶视图,旋转0度对应111,旋转90度对应001,旋转270度对应101)

如此一来,后续便可以通过每一行的转角来控制显示哪一个数字了。

渲染点

点的渲染就十分简单了,其实就是两个一半单位深度的、不同色的立方体拼起来,加个定位,并在每次时间更新的时候旋转180度罢了,不再赘述。

动起来

即便解决了渲染问题,倘若只是在每次时间更新的时候切换一下转角,也太过生硬。为了不那么生硬,我们需要对这种转角的变换做一个动画,这本质上其实就是在一定时间内对这个变换进行插值。这里我引入了通用的解决方案Tween来进行插值,使用起来只要设定好目标角度和duration即可:

js Tween.to(num.rotation, .4, {y: degree});

不过这里有一点需要注意,为了在时间变换时达到一个“前进”的观感,数字的每一行和点每次旋转的方向应该都是顺时针的。对于点而言这很好办,但对于数字就不同了,比如上面我们计算出的第一行,其三个角度是0、90和270,那么如果上一次的角度是270,这一次是90,直接旋转就会变成逆时针。这个解决起来也很简单,分两步实现:

  1. 使转角始终保持在0~360度内,在每次变换计算前先判断rotation.y的值,若大于360则减去360
  2. 判断当前角度和上一次角度的大小,若大于,则走正常逻辑,若小于,则给当前的角度加上360度

如此便可以保证顺时针的旋转。

如果不是自己的创作,少女是会标识出来的,所以要告诉别人是少女写的哦。