前端开发者学堂 - fedev.cn

如何使用简单的三角函数创建Loading效果

发布于 大漠

特别声明,本文来源于@Nash Vail的《How you can use simple Trigonometry to create better loaders》。

最近,在研究登陆页面的时候,遇到了一个网站。对使用它的人非常有用。网站上的一些东西吸引了我的眼球,让我有点不安。

不自然的和不顺滑的动画促使我有了写这篇文章的想法。

在这篇文章中,将使用三角函数的基本概念,重新创建一个更平滑的Loading效果。我知道这听起来很奇怪,但请相信我,这里一定会很有趣。你会惊讶地发现,要编写的代码很少。当然,你可能会担心三角函数相关的知识,事实上是你不需要知道三角学或数学你能理解这篇文章。我将会解释这里的每个圆相关的事情。

这就是我们要做的!

我们开始吧

我们要做的Loading效果是由三个小的圆圈组成,他们有一个周期的上下运动,每一个都和其他的不同步。

我把它分解成几个部分,首先,我们会得到一个小圆在一个周期上下做平滑的运动。稍后我们将计算其余部分。开始撸码吧。

圆的定位

以上的代码展示的是在<svg>元素的中心绘制了一个小圆。

来理解一下它是怎么做的。

其中widthheight正如你想象的一样。为了简单起见,它们就是svg元素的widthheight,然后构成一个盒子(大家懂的,HTML中的任何一个元素都是一个盒子)。

默认情况之下,SVG盒子具有传统的坐标系统,其原点位于左上角,以及xy值分别向右和向下递增。而且,每个单元对应一个像素,这样盒子的四个角就会根据给定的widthheight得到合适的坐标。

下一步,将需要做一个简单的小学数学的游戏。盒子的中心根据一定的公式(width/2height/2)可以计算出来其坐标是(150,75)。我们将这个值分配给cxcy,并且将其设置为圆的圆心。

让圆动起来

接下来的目标是要让圆动起来。不是任何形式的动起来。我们需要圆的运动是在一个周期内做上下运动。

周期性的计算

周期是发生在有规律间隔的任何东西。周期性的最简单的例子是每天日出日落。无论现在是什么时候,比如下午6:30,24小时后,又会是下午6:30,从那时起24小时将会是下午6:30。这是正常的,这是24小时内发生的事情。

如果是中午,太阳在天空的最高点,24小时后它会再次出现。或者,如果是傍晚,太阳刚刚接触地平线,24小时后,它又会重新开始。你明白我的意思了吗?

这是一个非常简单的描述,有些人会说,甚至是不准确,不科学,但我想它仍然会让太阳重复它的之前的位置,这很好。

如果我们把太阳的垂直位置与白天的时间画在一起,我们就能更清楚地看到它的周期性。

为了绘制任何二维曲线,我们需要两个值,xy。在我们的示例中就对应的是timepositionOfTheSun。我们收集一组值,然后把它们放在一个图上,看到的效果就如下:

纵轴y是太阳在天空中的垂直位置(positionOfTheSun),横轴x代表的是时间(time)。随着时间的推移,太阳的位置会改变,并且会在24小时后重复同样的一组值。

有了这个图形,就能算出太阳在天空中的位置。接下来看看如何做到这一点,首先,让我们来给我们的图命名,比如叫作sunsVerticalPositionAt

一旦我们有了这个,我们就可以形成一个等式:

verticalPositionInTheSky = sunsVerticalPositionAt( [time] )

我们只需要在图(或者从数学上说,到我们的函数)中输入时间,就可以计算出太阳的位置。

我们选择一个时间的点(比如t1),画一条直线(与y轴平行)将与曲线相交的地方就是我们想要知道的太阳位置。

我们继教往下吧,直接进入数学。从图中除去太阳和其他的装饰物,这就得到如下的图:

这幅图代表了周期性。一个实体(在我们的示例中是太阳的垂直位置)重复它作为另一个实体(在我们的例子中的时间)的值。

数学中不止有几个周期函数,但我们坚持最基本的和周期函数的特征,我们将用它来创建完美的Loading效果,比如我们所知道的正弦函数y = sin(x)

y = sin(x)的曲线图如下:

你能看出这里的方程和我们计算出太阳在天空中位置的方程有相似之处吗?

我们可以通过x得到y的值,就像通过时间计算太阳在天空中的位置。整个过程都不离开整个曲线。

如果你在想什么是sin?这只是一个函数的名字,就像我们在图(函数)中给出了sunsVerticalPositionAt的名称。

这里的重点是yx。看看,随着x的变化,y的值是怎样变化的(你能把这与我们的太阳在天空中改变其垂直位置的例子联系起来吗?)。

你可能也注意到了,y的最大值是1,其最小值是-1。这是正弦函数的一个特征。y=sin(x)的取值范围是-11

但是这个范围可以通过一个简单的操作来改变。这是我们要做的。但在此之前,让我们把所学到的东西都拿出来,不管我们能做多少,都要让这个圆动起来。

代码中的数学

到目前为止,在<svg>中的<circle>元素有一个id名为c。我们可以通过JavaScript选择到这个圆,并让它动起来。

let c = document.getElementbyId('c');
animate();
function animate() {
    requestAnimationFrame(animate);
}

上面的代码很简单,首先,我们把目标存储在一个变量c中。

接下来,我们使用requestAnimationFrame和一个名为animate的函数。animate使用requestAnimationFrame递归调用自己,同时使用requestAnimationFrame来运行任何动画,并且让动画达到60FPS。有关于requestAnimationFrame更多的内容可以点击这里阅读

来看代码,更有意义:

let c = document.getElementById('c');
let currentAnimationTime = 0;
const centreY = 75;
animate();
function animate() {
    c.setAttribute('cy', centreY + (Math.sin(currentAnimationTime)));
    
    currentAnimationTime += 0.15;
    requestAnimationFrame(animate);
}

就只增加了四行代码。如果你运行这个,你会看到圆在它的中心慢慢地移动,整个称动效果如下:

这里发生了什么?

一旦我们得到了圆心的坐标cxcy,也就是50%的地方。我们要做的就是把cx保持不变,因为我们不想改变圆的水平位置。只需要周期性地从cy中加减相等的数,使圆在y轴上下移动。这就是我们在代码中所做的事情。

centreY存储的值就是圆的中心(75)的值,因此可以将数字加到它上面或从它上面减去。这样就改变了圆在y轴上的位置。

currentAnimationTime是一个初始化为0的数字,它决定了动画的速度,我们在每次调用中增加的次数越多,动画就会越快。这里是0.15,因为它看起来是一个足好的速度值。

currentAnimationTime是正弦曲线(函数)中的x。当currentAnimationTime的值增加时,我们将它传递给Math.sin,然后将生成的数字添加到centreY

然后使用setAttribute将这个数字分配到cy

正如我们所知道的,sin(x)产生的x的值都在-11之间。因此cy的值的范围是从最小的centreY - 1到最大的centreY + 1。这使得圆在其位置上以1像素的幅度垂直抖动。

我们想要增加移动的距离。这意味着需要一个大于1的数。怎么做呢?我们需要一个新函数吗?并不是。

还记得前面介绍的内容吗?这很简单,我们要做的就是反sin(x)乘以我们想要的间距数。用一个常数乘以一个函数的运算叫做缩放。注意图形是如何改变形状的,也注意乘法对正弦最大值和最小值的影响。

现在我们知道怎么修改我们的代码了。

let c = document.getElementById('c');
let currentAnimationTime = 0;
const centreY = 75;
animate();
function animate() {
    c.setAttribute('cy', 
    centreY + (20 *(Math.sin(currentAnimationTime))));
    
    currentAnimationTime += 0.15;
    requestAnimationFrame(animate);
}

这样一来就产生了一个非常平滑的圆形动画。是不是很可爱。我们所做的是通过一个数字乘以一个数字来增加正弦函数的幅度。接下来我们要做的是在这个圆的两边加上两个新的圆,让它们以同样的方式动起来。

<svg width="300" height="150">
    <circle id="cLeft" cx="120" cy="75" r="10" />
    <circle id="cCentre" cx="150" cy="75" r="10" />
    <circle id="cRight" cx="180" cy="75" r="10" />
</svg>

上面的代码让我们在svg中重新添加了两个圆circle,其中一个放置在原来圆左侧的30px处(150 - 30 = 120),另一个放置在原来圆右侧的30px处(150 + 30 = 180)。

前面的示例中只有一个圆,所以用一个idc是有效的,因为这是唯一的圆。但是我们现在总共有3个圆,所以可以给它们一个描述性的id。这样从左到右,就可以命名为cLeftcCentrecRight。原来idc的变成了现在的cCentre

运行上面的代码,你所看到的效果如下:

新添加的圆根本不动!接下来,咱们就要让他们动起来。

let cLeft= document.getElementById('cLeft'),
    cCenter = document.getElementById('cCenter'),
    cRight = document.getElementById('cRight');
let currentAnimationTime = 0;
const centreY = 75;
const amplitude = 20;
animate();
function animate() {
    cLeft.setAttribute('cy', 
    centreY + (amplitude *(Math.sin(currentAnimationTime))));
    cCenter.setAttribute('cy', 
    centreY + (amplitude * (Math.sin(currentAnimationTime))));
    cRight.setAttribute('cy', 
    centreY + (amplitude * (Math.sin(currentAnimationTime))));  
    currentAnimationTime += 0.15;
    requestAnimationFrame(animate);
}

有一些额外的代码是针对新圆的,并将相同的动画代码应用到cCentre中,得到的效果如下:

现在圆是动起来了,但这并不是我们想要的Loading动效。

虽然圆在周期性的动,但问题是所有圆都在同步运动。这不是我们想要的。我们希望每个连续的圆都有一些延迟。所以它起起来像一个圆,而不是开始的那个圆是在它之前的圆的运动。

你是否注意到,在第一个圆之后,每个圆与左边的圆稍微有点不同步。如果你用手掌来隐藏其他两个圆圈,你会注意到可见的圆仍然在执行我们最前面的动效。

现在,为了让圆的动画不同步,我们需要对代码做一些微小的调整。但要理解这种微小的变化是如何起作用的。

如果我们把每个圆的动效画成之前的图,那图就如下所示:

这里没有啥惊喜,因为我们知道每个圆都在以同样的方式运动。要知道,既然我们用正弦函数来做动画,上面所有的曲线都只是正弦函数的图。为了让这些图不同步,需要理解平移/图形的数学概念。

位移是一种硬性的转换,它不会改变函数图形的形状或大小。一个移位所要做的就是改变图形的位置。位移可以是水平的,也可以是垂直的。对于我们的意图和目的,我们感兴趣的是水平位移。

注意,下面的动图演示了如何改变y = sin(x) 曲线图水平移动。

为了理解这是如何运作的,让我们回到太阳升起和日落的比喻。

我们的函数又是什么?sunsVerticalPositionAt(t)。这是正确的!可以把任何时间都传递到这个函数,在特定的时间位置将得到太阳在天空中的垂直位置。因此,要想知道上午9点钟的太阳位置,我们就可以通过sunsVerticalPositionAt(9)来得到。

现在来考虑一下函数sunsVerticalPositionAt(t - 3)。注意这里,无论time(t)传递到这个新函数(它取t-3,而不是t),将得到太阳位置的值要比t早3小时。

也就是说,t=9时的值是6t=12的值是9,依此类推。我们已经用这种方式连接了函数。换句话说,函数返回的值要比t的值更早。

我们也可以说,已经把函数的图形平移到x轴上。注意,下图中t=6的旧图给出了B的值。当图形移位后,同样的值Bt=9时被移位的图。

同样的,如果我们加3而不是做减法。sunsVerticalPosition(t + 3)图形就会向左平移,或者换句话说,它会在3小时后给出数值。

有了这个概念的知识,我们现在可以做的是改变图形来决定最后两个圆的动画。

要做到这一点,需要对代码做一些小调整:

let cLeft= document.getElementById('cLeft'),
    cCenter = document.getElementById('cCenter'),
    cRight = document.getElementById('cRight');
let currentAnimationTime = 0;
const centreY = 75;
const amplitude = 20;
animate();
function animate() {
    cLeft.setAttribute('cy', 
    centreY + (amplitude *(Math.sin(currentAnimationTime))));
    cCenter.setAttribute('cy', 
    centreY + (amplitude * (Math.sin(currentAnimationTime - 1))));
    cRight.setAttribute('cy', 
    centreY + (amplitude * (Math.sin(currentAnimationTime - 2))));
    currentAnimationTime += 0.15;
    requestAnimationFrame(animate);
}

这就是我们想要的。我们移动了cCentrecRight动画的图形。

在这里。我们Loading动画的圆周运动绝对精确。你可以一直使用不同的值,比如增加到currentAnimationFrame,以控制速度或幅度来控制偏移量,但Loading能按你想要的方式进行。

大漠

常用昵称“大漠”,W3CPlus创始人,目前就职于手淘。对HTML5、CSS3和Sass等前端脚本语言有非常深入的认识和丰富的实践经验,尤其专注对CSS3的研究,是国内最早研究和使用CSS3技术的一批人。CSS3、Sass和Drupal中国布道者。2014年出版《图解CSS3:核心技术与案例实战》。

如需转载,烦请注明出处:https://www.fedev.cn/animation/how-you-can-use-simple-trigonometry-to-create-better-loaders.htmlsacai Nike LDWaffle + Blazer Mid Release Date