弹跳动画
在现实世界里,物体的移动往往不是匀速的。因此,给动画和过渡加上缓动效果会使界面显得更加真实。
回弹是一种常用的缓动效果,对回弹的过程进行分析会发现,回弹就是当一个过渡达到最终值时,往回倒一点,然后再次回到最终值,如此往复一次或者多次,并逐渐收敛。最终稳定在终值。
回弹往往会用在有尺寸变化的元素上,比如对于页面弹出框,先放大再缩小。下面就利用回弹模拟一个下落的弹性小球。
HTML:
<div class="ball_bg">
<div class="ball ball-a"></div>
</div>
CSS:
.ball_bg{
width: 30px;
height: 330px;
background: skyblue;
background: linear-gradient(skyblue,white 320px,yellowgreen 0);
}
.ball{
width: 20px;
height: 20px;
border-radius: 50%;
margin: 0 auto;
background: red; /*平稳回退*/
background: radial-gradient(at 30% 30%,#fdd,red);
}
@keyframes bounce-a{
60%,80%,to {
transform: translateY(300px);
}
70% {transform: translateY(200px);}
90% {transform: translateY(250px);}
}
.ball-a{
animation: bounce-a 3s forwards;
}
迫不及待地刷新页面:

但是实际的效果令人很失望,小球的移动显得很不真实。这是由于默认的调速函数时这样的:
还有四种内置的缓动曲线如下图所示:
如上图,ease-in和ease-out是相反的版本。这对组合正好满足回弹效果:小球的运动相反时,调速函数也是相反的。于是,修改上面的代码:
.ball-a{
/*animation: bounce-a 3s forwards;*/
animation: bounce-a 3s ease-in forwards;
}
@keyframes bounce-a{
60%,80%,to {
transform: translateY(300px);
animation-timing-function: ease-out;
}
70% {transform: translateY(200px);}
90% {transform: translateY(250px);}
}
效果会真是很多:

上面提到的五种曲线都是通过三次贝塞尔曲线来指定的。这种曲线由一定数量的路径片段组成,每个片段的每一端由一个锚点来控制曲率。在CSS的调速函数中,都是只有一个片段的贝塞尔曲线,因此调速函数只有两个控制曲率的锚点。
为了对预定义的曲线进行补充,CSS提供了cubic-bezier()函数,允许开发者指定自定义的调速函数。该函数接收四个参数,分别表示两个控制锚点的坐标值。
比如:cubic-bezier(x1,y1,x2,y2),则(x1,y1)表示第一个锚点坐标,(x2,y2)表示第二个锚点坐标。曲线片段的两端分别固定在(0,0)和(1,1),因此参数的取值范围为[0-1]
我们也可以利用chrome或者图形化工具来设置这四个参数使过渡更加逼真。

弹性过渡
弹性过渡通常用在表单登录的提示框上,下面模拟一个文本输入框,每当它被聚焦时,都需要展示一个提示框。这个提示框用来向用户提示格式等信息。
首先是HTML:
<label>password:</label>
<div class="pwd">
<input type="text" value="your password" class="uname-b">
<span class="callout-b callout">
Only letters, numbers, underscores (_) and hyphens (-) allowed!
</span>
</div>
然后添加样式,先用动画模拟上面提到的效果:
.name,.pwd{
position: relative;
}
.callout{
position: absolute;
left: 178px;
top:0;
max-width: 20em;
padding: .6em .8em;
border-radius: .4em;
background: #fb3;
color: white;
display: block;
font: 80%/1 sans-serif;
border: 1px solid rgba(0,0,0,.3);
box-shadow: .05em .2em .6em rgba(0,0,0,.2);
}
.callout:before{
content: "";
position: absolute;
top: .4em;
left: -.5em;
padding: .4em;
background: inherit;
border: inherit;
border-top: none;
border-right: none;
transform: rotate(45deg);
/*border: 1em solid transparent;
border-right-color: #fb3;
border-left-width: 0;*/
}
@keyframes elastic-grow{
from {transform: scale(0);}
70% {
transform: scale(1.1);
animation-timing-function: cubic-bezier(.1,.25,.1,.25);
}
}
.uname-b:not(:focus) + .callout-b{
transform: scale(0);
}
.uname-b:focus + .callout-b{
animation: elastic-grow .5s;
}
.callout-b{
transform-origin: 0% 10%;
}

虽然用动画可以模拟出刚开始的需求,但是有种大材小用的感觉,那么就换一个思路,利用上节所提到的过渡来增加弹性的效果。
这个思路主要还是利用cubic-bezier(),由上节知道x1,x2只能在区间[0-1]上取值。但是我们可以在垂直方向上突破0-1区间,从而让过渡达到低于0或者高于100%的程度。这就意味着在从scale(0)变形到scale(1)的过程中,可以在中间经历一个scale(1.1)。
在上面的代码中,要增加弹性效果,只需要把调速函数先达到110%,再过渡到100%,利用上节所提到的可视化工具,把第二个锚点向上移,跳到cubic-bezier(.25,.1,.3,1.5)的程度。
如上图,这个过渡会在50%的时间点到达100%,在70%达到110%,在最后回到100%。
我们为了便于对比,重新写一个demo
HTML:
<label>username:</label>
<div class="name">
<input type="text" value="your name" class="uname-a">
<span class="callout-a callout">
只允许字母、数字、下划线(_)和连字符(-)
</span>
</div>
CSS:
.uname-a:not(:focus) + .callout-a{
transform: scale(0);
}
.callout-a{
transition: .5s cubic-bezier(.25,.1,.3,1.5);
transform-origin: 0% 10%;
}

仔细观察会发现,提示框在弹出的时候没问题,但是当失去焦点消失时,这个过程会将原来的110%变形(scale(1.1))解析为scale(-0.1),造成视觉上是从原始大小(scale(1))到消失(scale(-0.1))然后再稍微放大些,最后再消失(scale(0))。
要解决这个问题,只需要在失去焦点的时候,把调速函数覆盖为ease。
修改代码:
.uname-a:not(:focus) + .callout-a{
transform: scale(0);
transition-timing-function: ease;
}

但是稍微有些强迫症的人可能会发现,提示框关闭的动作要比弹出时慢一些。这是因为提示框弹出时在进行到50%(即250ms)就已经达到100%,而提示框的关闭过程需要500ms。这时候只需要显示的指定transition-duration即可。
.uname-a:not(:focus) + .callout-a{
transform: scale(0);
transition-timing-function: ease;
transition-duration: .25s;
}

问题完美解决~