特别声明,本文来源于@Nash Vail的《How you can use simple Trigonometry to create better loaders》。
最近,在研究登陆页面的时候,遇到了一个网站。对使用它的人非常有用。网站上的一些东西吸引了我的眼球,让我有点不安。
不自然的和不顺滑的动画促使我有了写这篇文章的想法。
在这篇文章中,将使用三角函数的基本概念,重新创建一个更平滑的Loading效果。我知道这听起来很奇怪,但请相信我,这里一定会很有趣。你会惊讶地发现,要编写的代码很少。当然,你可能会担心三角函数相关的知识,事实上是你不需要知道三角学或数学你能理解这篇文章。我将会解释这里的每个圆相关的事情。
这就是我们要做的!
我们开始吧
我们要做的Loading效果是由三个小的圆圈组成,他们有一个周期的上下运动,每一个都和其他的不同步。
我把它分解成几个部分,首先,我们会得到一个小圆在一个周期上下做平滑的运动。稍后我们将计算其余部分。开始撸码吧。
圆的定位
以上的代码展示的是在<svg>
元素的中心绘制了一个小圆。
来理解一下它是怎么做的。
其中width
和height
正如你想象的一样。为了简单起见,它们就是svg
元素的width
和height
,然后构成一个盒子(大家懂的,HTML中的任何一个元素都是一个盒子)。
默认情况之下,SVG
盒子具有传统的坐标系统,其原点位于左上角,以及x
,y
值分别向右和向下递增。而且,每个单元对应一个像素,这样盒子的四个角就会根据给定的width
和height
得到合适的坐标。
下一步,将需要做一个简单的小学数学的游戏。盒子的中心根据一定的公式(width/2
,height/2
)可以计算出来其坐标是(150,75)
。我们将这个值分配给cx
和cy
,并且将其设置为圆的圆心。
让圆动起来
接下来的目标是要让圆动起来。不是任何形式的动起来。我们需要圆的运动是在一个周期内做上下运动。
周期性的计算
周期是发生在有规律间隔的任何东西。周期性的最简单的例子是每天日出日落。无论现在是什么时候,比如下午6:30,24小时后,又会是下午6:30,从那时起24小时将会是下午6:30。这是正常的,这是24小时内发生的事情。
如果是中午,太阳在天空的最高点,24小时后它会再次出现。或者,如果是傍晚,太阳刚刚接触地平线,24小时后,它又会重新开始。你明白我的意思了吗?
这是一个非常简单的描述,有些人会说,甚至是不准确,不科学,但我想它仍然会让太阳重复它的之前的位置,这很好。
如果我们把太阳的垂直位置与白天的时间画在一起,我们就能更清楚地看到它的周期性。
为了绘制任何二维曲线,我们需要两个值,x
和y
。在我们的示例中就对应的是time
和positionOfTheSun
。我们收集一组值,然后把它们放在一个图上,看到的效果就如下:
纵轴y
是太阳在天空中的垂直位置(positionOfTheSun
),横轴x
代表的是时间(time
)。随着时间的推移,太阳的位置会改变,并且会在24小时后重复同样的一组值。
有了这个图形,就能算出太阳在天空中的位置。接下来看看如何做到这一点,首先,让我们来给我们的图命名,比如叫作sunsVerticalPositionAt
。
一旦我们有了这个,我们就可以形成一个等式:
verticalPositionInTheSky = sunsVerticalPositionAt( [time] )
我们只需要在图(或者从数学上说,到我们的函数)中输入时间,就可以计算出太阳的位置。
我们选择一个时间的点(比如t1
),画一条直线(与y
轴平行)将与曲线相交的地方就是我们想要知道的太阳位置。
我们继教往下吧,直接进入数学。从图中除去太阳和其他的装饰物,这就得到如下的图:
这幅图代表了周期性。一个实体(在我们的示例中是太阳的垂直位置)重复它作为另一个实体(在我们的例子中的时间)的值。
数学中不止有几个周期函数,但我们坚持最基本的和周期函数的特征,我们将用它来创建完美的Loading效果,比如我们所知道的正弦函数y = sin(x)
。
y = sin(x)
的曲线图如下:
你能看出这里的方程和我们计算出太阳在天空中位置的方程有相似之处吗?
我们可以通过x
得到y
的值,就像通过时间计算太阳在天空中的位置。整个过程都不离开整个曲线。
如果你在想什么是sin
?这只是一个函数的名字,就像我们在图(函数)中给出了sunsVerticalPositionAt
的名称。
这里的重点是y
和x
。看看,随着x
的变化,y
的值是怎样变化的(你能把这与我们的太阳在天空中改变其垂直位置的例子联系起来吗?)。
你可能也注意到了,y
的最大值是1
,其最小值是-1
。这是正弦函数的一个特征。y=sin(x)
的取值范围是-1
至1
。
但是这个范围可以通过一个简单的操作来改变。这是我们要做的。但在此之前,让我们把所学到的东西都拿出来,不管我们能做多少,都要让这个圆动起来。
代码中的数学
到目前为止,在<svg>
中的<circle>
元素有一个id
名为c
。我们可以通过JavaScript选择到这个圆,并让它动起来。
let c = document.getElementbyId('c');
animate();
function animate() {
requestAnimationFrame(animate);
}
上面的代码很简单,首先,我们把目标存储在一个变量c
中。
接下来,我们使用requestAnimationFrame
和一个名为animate
的函数。animate
使用requestAnimationFrame
递归调用自己,同时使用requestAnimationFrame
来运行任何动画,并且让动画达到60FPS
。有关于requestAnimationFrame
更多的内容可以点击这里阅读。
- 被誉为神器的
requestAnimationFrame
- 关于动画,你需要知道的
requestAnimationFrame
for Smart AnimatingrequestAnimationFrame
性能更好- 提高 HTML5 画布性能
来看代码,更有意义:
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);
}
就只增加了四行代码。如果你运行这个,你会看到圆在它的中心慢慢地移动,整个称动效果如下:
这里发生了什么?
一旦我们得到了圆心的坐标cx
和cy
,也就是50%
的地方。我们要做的就是把cx
保持不变,因为我们不想改变圆的水平位置。只需要周期性地从cy
中加减相等的数,使圆在y
轴上下移动。这就是我们在代码中所做的事情。
centreY
存储的值就是圆的中心(75
)的值,因此可以将数字加到它上面或从它上面减去。这样就改变了圆在y
轴上的位置。
currentAnimationTime
是一个初始化为0
的数字,它决定了动画的速度,我们在每次调用中增加的次数越多,动画就会越快。这里是0.15
,因为它看起来是一个足好的速度值。
currentAnimationTime
是正弦曲线(函数)中的x
。当currentAnimationTime
的值增加时,我们将它传递给Math.sin
,然后将生成的数字添加到centreY
。
然后使用setAttribute
将这个数字分配到cy
。
正如我们所知道的,sin(x)
产生的x
的值都在-1
至1
之间。因此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
)。
前面的示例中只有一个圆,所以用一个id
为c
是有效的,因为这是唯一的圆。但是我们现在总共有3
个圆,所以可以给它们一个描述性的id
。这样从左到右,就可以命名为cLeft
、cCentre
和cRight
。原来id
为c
的变成了现在的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
时的值是6
,t=12
的值是9
,依此类推。我们已经用这种方式连接了函数。换句话说,函数返回的值要比t
的值更早。
我们也可以说,已经把函数的图形平移到x
轴上。注意,下图中t=6
的旧图给出了B
的值。当图形移位后,同样的值B
在t=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);
}
这就是我们想要的。我们移动了cCentre
和cRight
动画的图形。
在这里。我们Loading动画的圆周运动绝对精确。你可以一直使用不同的值,比如增加到currentAnimationFrame
,以控制速度或幅度来控制偏移量,但Loading能按你想要的方式进行。
如需转载,烦请注明出处:https://www.w3cplus.com/animation/how-you-can-use-simple-trigonometry-to-create-better-loaders.html