css揭秘-过渡与动画(1)
缓动效果
难题
给过渡和动画加上缓动效果(比如具有回弹效果的过渡过程)是一种流行的表现手法,可以让界面显得更加生动和真实。
弹跳动画
@keyframes bounce {
60%, 80%, to { transform: translateY(150px); }
70% { transform: translateY(50px); }
90% { transform: translateY(100px); }
.ball {
/* 尺寸样式、颜色样式等 */
margin-left: 200px;
width: 10px;
height: 10px;
border-radius: 50%;
background: red;
animation: bounce 3s;
}
不论是在 animation/transition 简写属性中,还是在 animation-timing-function /transition-timing-function 展开式属性中,都可以将默认的调速函数显示指定为 ease 关键字。除此之外,还有四种内置的缓动曲线,可以用来改变动画的推进方式。
如图所示,ease-in 与 ease-out 是反向版本,这一对组合正好是实现回弹效果所需要的:每当小球的运动方向相反时,调速函数也是相反的。可以在 animation 属性中指定一个通用的调速函数,然后在关键帧中按需覆盖它。
@keyframes bounce1 {
60%, 80%, to {
transform: translateY(200px);
animation-timing-function: ease-out;
70% { transform: translateY(150px); }
90% { transform: translateY(180px); }
.ball2 {
/* 其余样式写在这里 */
margin-left: 400px;
width: 10px;
height: 10px;
border-radius: 50%;
background: green;
animation: bounce1 3s ease-in;
}
从逻辑上来讲,只要把控制锚点的水平坐标和垂直坐标互换,就可以得到任何调速函数的反向版本,这一点对关键字也是适用的;上述五个关键字都有其对应的 cubic-bezier() 形式的值。比如:ease 等同于 cubic-bezier(.25, .1, .25, 1),因此它的反向版本就是 cubic-bezier(.1, .25, 1, .25)。因此可以将回弹动画修改为:
@keyframes bounce2 {
60%, 80%, to {
transform: translateY(200px);
animation-timing-function: ease;
70% { transform: translateY(150px); }
90% { transform: translateY(180px); }
.ball2 {
/* 外观样式 */
margin-left: 100px;
width: 30px;
height: 30px;
border-radius: 50%;
background: blue;
animation: bounce 3s cubic-bezier(.1,.25,1,.25);
}
弹性过渡
有一个文本框,当它被聚焦时,都需要展示一个提示框。这个提示框用来向用户提供帮助信息,比如字段的正确格式等。结构代码为:
<style>
input {
display: block;
padding: 0 .4em;
font: inherit;
.callout {
position: absolute;
max-width: 14em;
padding: .6em .8em;
border-radius: .3em;
margin: .3em 0 0 -.2em;
background: #fed;
border: 1px solid rgba(0,0,0,.3);
box-shadow: .05em .2em .6em rgba(0,0,0,.2);
font-size: 75%;
.callout:before {
content: "";
position: absolute;
top: -.4em;
left: 1em;
padding: .35em;
background: inherit;
border: inherit;
border-right: 0;
border-bottom: 0;
transform: rotate(45deg);
input:not(:focus) + .callout {
transform: scale(0);
.callout {
transition: .5s transform;
transform-origin: 1.4em -.4em;
</style>
<input type="text" value="niuniu">
<span class="callout">只能输入字母、数字、下划线以及中划线</span>
可以看到,目前当用户聚焦到这个文本框时有一个1.5s的过渡,但是如果在结尾时能更夸张一点就可以更生动和自然(比如:先扩大到110%的尺寸,然后再缩回到100%),可以使用上述学会的动画。
@keyframes elastic-grow {
from { transform: scale(0); }
70% {
transform: scale(1.1);
animation-timing-function:
cubic-bezier(.1,.25,1,.25); /* 反向的ease */
input:not(:focus) + .callout { transform: scale(0); }
input:focus + .callout { animation: elastic-grow .5s; }
.callout { transform-origin: 1.4em -.4em; }
跟之前的相比,确实是实现了想要的功能,但是在这个场景中,需要的仅是给过渡加入一个弹性的效果,使用动画显得有点大材小用了。
input:not(:focus) + .callout { transform: scale(0); }
.callout {
transform-origin: 1.4em -.4em;
transition: .5s cubic-bezier(.25,.1,.3,1.5);
}
但是,发现在当文本框失去焦点,提示框缩回到消失的时候并不是我们想要的效果。只想给提示框的关闭过程指定普通的 ease 调速函数,那么可以在定义关闭状态的 CSS 规则中把当前的调速函数覆盖掉:
input:not(:focus) + .callout {
transform: scale(0);
transition-timing-function: ease;
.callout {
transform-origin: 1.4em -.4em;
transition: .5s cubic-bezier(.25,.1,.3,1.5);
}
发现关闭的时候要迟钝一些。要修复这个问题既可以单独覆盖 transition-duration 这一个属性,也可以用 transition 这个简写属性来覆盖所有的值。如果选择后者的话,就不需要显式指定 ease 了,因为它本来就是初始值:
input:not(:focus) + .callout {
transform: scale(0);
transition: .25s;
.callout {
transform-origin: 1.4em -.4em;
transition: .5s cubic-bezier(.25,.1,.3,1.5);
}
为避免不小心对颜色设置了弹性过渡,可以尝试把过渡的作用范围限制在某几种特定的属性上,而不是什么都不指定。最终完美的代码为:
input:not(:focus) + .callout {
transform: scale(0);
transition: .25s transform;
.callout {
transform-origin: 1.4em -.4em;
transition: .5s cubic-bezier(.25,.1,.3,1.5) transform;
}
逐帧动画
难题
在很多时候,需要一个很难(或不可能)只通过某些 CSS 属性的过渡来实现的动画。比如一段卡通影片,或是一个复杂的进度指示框。在这种场景下,基于图片的逐帧动画才是完美的选择;你可能会产生这样一种疑问:“难道不能用 GIF 动画吗?”对大多数情况来说,答案是“能”,GIF 动画可以完美胜任。但是,GIF 动画也有一些短板,在某些场景下可能会让整体效果大打折扣。
- GIF 图片的所能使用的颜色数量被限制在 256 色。
- GIF 不具备 Alpha 透明的特性。
- 无法在 CSS 层面修改动画的某些参数,比如动画的持续时间、循环次数、是否暂停等。
解决方案
将动画中的所有帧放在一张 png 上,然后用一个元素来容纳这个加载提示,并把它的宽高设置为单帧的尺寸:
<style>
.loader {
width: 90px;
height: 90px;
background: url(loading.png) 0 0;
text-indent: 200%;
white-space: nowrap;
overflow: hidden;
</style>
<div class="loader">加载中…</div>
与贝塞尔曲线调速函数迥然不同的是, steps() 会根据你指定的步进数量,把整个动画切分为多帧,而且整个动画会在帧与帧之间硬切,不会做任何插值处理。
@keyframes loader {
to { background-position: -1248px 0; } /*1248px 为图片的宽度*/
.loader{
animation: loader 1s infinite steps(16); /* 16 为动画的帧数 */
}
steps() 还接受可选的第二个参数,其值可以是 start 或 end(默认值)。这个参数用于指定动画在每个循环周期的什么位置发生帧的切换动作但实际上这个参数用得并不多。如果我们只需要一个单步切换效果,还可以使用 step-start 和 step-end 这样的简写属性,它们分别等同于 steps(1, start) 和 steps(1, end) 。
闪烁效果
难题
有一种常见的用户体验设计手法,就是通过数次闪烁(不超过三次)来提示用户界面中有某处发生了变化,或者用来凸显出当前链接的目标(如果页面中某元素的 ID 与 URL 中的 #hash 相匹配,则它就是链接的目标)。在此类场景下使用闪烁,可以有效地把用户的注意力引导到某个特定区域。
解决方案
用 CSS 动画可以实现各种类型的闪烁效果,比如对整个元素进行闪烁(通过 opacity 属性),对文字的颜色进行闪烁(通过 color 属性),对边框进行闪烁(通过 border-color 属性)等等。只讨论文字的闪烁效果,因为这是最常见的需求。不过,同样适用于元素其他部分的闪烁效果。
一个平滑的闪烁效果。
@keyframes blink-smooth {
to {
color: transparent
.highlight {
animation: 1s blink-smooth 3;
}
这基本上成功了。这段文字可以平滑地从它原来的颜色淡化为透明色,但随后会生硬地跳回原来的颜色。这可能正是我们预先设想的效果。如果是这样的话,那就大功告成了!但如果我们希望文字颜色的变化不仅是平滑隐去的,同时还是平滑显现的。那么,可以修改关键帧,让状态切换发生在每个循环周期的中间。
@keyframes blink-smooth { 50% { color: transparent } }
.highlight {
margin: 30px;
animation: 1s blink-smooth 3;
}
还有一个问题,虽然在这个特定的动画中表现得不是很明显(因为颜色或透明度的过渡很难体现出各种调速函数的特征),但:这个动画一直是处在加速过程中的,不论是在文字显现还是隐去时——这对某些动画来说可能会显得不太自然(比如类似脉搏跳动的动画)。在那种情况下,可以从工具箱中请出另一件法宝: animation-direction 。
animation-direction 的唯一作用就是反转每一个循环周期( reverse ),或第偶数个循环周期( alternate ),或第奇数个循环周期( alternate-reverse )。它的伟大之处在于,它会同时反转调整函数,从而产生更加逼真的动画效果。我们可以把它用在需要闪烁的元素上,比如:
@keyframes blink-smooth { to { color: transparent } }
.highlight {
animation: .5s blink-smooth 6 alternate;
}
那么,普通的那种闪烁效果。
@keyframes blink {
50% {
color: transparent