相较于静态内容,动态的内容对人眼更有吸引力。这可能来源于人类在漫长的进化中获得的能力,动态比静态更加危险!事实上,现实世界中我们已经不自觉的在使用这个法则,状态栏闪烁的QQ头像,界面上的弹出窗口,到处飘动的小广告,GIF动图,Flash广告等等。
页面上的动态元素会非常吸引读者的注意力,会使得页面更加生动,更清晰传神的表达出内容。我们常说,一图胜千言,而一个动画又可以胜过了多张图片(当然,我们需要把握好度,不能让动画干扰用户对内容的消费)。
按照传统的方式,在页面上实现动画的方式有多种:
而这些方法都或多或少有些问题,比如引入额外的文件会导致页面尺寸变大,这样会导致页面加载变慢,从而影响用户体验。而CSS3的出现大大简化了动画的实现方式。通过浏览器对标准的支持,我们仅仅听过CSS就可以实现动画,而不依赖于大尺寸的外部文件。
基本上,CSS3通过过渡(transition),变形(transform)动画(animation)等方式来支持动画。我们可以分别来看。
过渡是CSS3标准的一部分,用来控制CSS属性变换的速率。比如最常见的情况,我们通过:hover
伪选择器来定义鼠标移到元素上的状态,我们为:hover
状态定义了一些不同的属性值(不同的背景颜色,不同的字体大小等等),当鼠标移动到元素上之后,我们会看到属性值会被迅速的修改。
<div class="circle"></div>
比如我们为.circle
定义了这样的样式,将它绘制成一个圆形:
.circle {
width: 20em;
height: 20em;
border: 1px solid #c0c0c0;
box-shadow: 0 0 5px #c0c0c0;
border-radius: 50%;
background-color: yellowgreen;
}
然后当:hover
发生时,将该圆的背景改成橙色:
.circle:hover {
background-color: orange;
}
过渡就是将这个过程延长,我们只需要定义其实状态和结束状态的属性值,然后浏览器会根据预设的时间间隔来自动计算过程中的属性值。
要定义一个过渡,我们可以指定这样一些参数:
其语法为:
#element {
transition: <property> <duration> <timing-function> <delay>;
}
比如我们的上例中,可以定义这样的过渡:
.circle {
width: 20em;
height: 20em;
border: 1px solid #c0c0c0;
box-shadow: 0 0 5px #c0c0c0;
border-radius: 50%;
background-color: yellowgreen;
transition: background-color 1s ease-in-out 0s;
}
这行过渡定义表示,我们要过渡background-color
属性,持续时间为1秒,时间函数为淡入淡出,开始时间为立即
。
如果我们这里有多个属性要写,可以简写为all
:
transition: all 1s ease-in-out 0s;
如果无需延时执行,可以将此处的0s
忽略。
时间函数是一个通过4个参数来定义的贝塞尔函数,简而言之,它定义了属性的值如何根据时间的变化而变化。比如匀速变化,先快后慢的减速变化,先慢后快的加速变化等等,CSS3已经预定义了一些命名的函数:
如果你精通数学的话,可以通过参数来自动以一个贝塞尔函数:
cubic-bezier(x1, y1, x2, y2)
你还可以通过这个地址Cubic Bezier进行预览,然后将参数拷贝到自己的CSS代码中。
这样,当用户再将鼠标移动到圆上的时候,背景色会由黄绿
慢慢的过渡到橙色
。
变形是另外一个非常重要的CSS3特性。变形是指将元素的尺寸,位置,形状通过函数来进行变化。目前包括旋转(rotate),扭曲(skew),缩放(scale),移动(translate)以及矩阵变形(matrix)。
旋转的语法为:
transform: rotate(<angle>);
rotate(10deg)
表示将该元素顺时针旋转10度,如果度数为负数,则表示逆时针旋转。比如:
.square {
margin: 0 5em;
width: 20em;
height: 20em;
border: 1px solid #c0c0c0;
box-shadow: 0 0 5px #c0c0c0;
background-color: yellowgreen;
transform: rotate(10deg);
}
扭曲会将元素按照x,y方向以一定的角度进行变形。这样可以将矩形变为平行四边形,菱形等形状。扭曲的语法为:
transform: skew(<x-angle>, <y-angle>);
.square {
transform: skew(10deg);
}
缩放将元素变大,涉及到缩放的时候,有一个缩放比率的问题,即元素在x,y方向的缩放比例通常需要一致。当然有些情况下,我们需要元素某个维度保持不变,而仅仅修改另一个维度。缩放的语法为:
transform: scale(<number>[, <number>]);
这个数字是缩放的倍数,大于1表示放大,小于1表示缩小。第二个参数可以忽略,当忽略时,x和y都按照同一数字进行缩放。
位移可以将元素按照x,y方向移动。当然还可以两个参数同时指定。位移的语法为:
transform: translate(<x-value>[, <y-value>]);
比如:
transform: translate(10px, 10px);
将元素向x,y方向都移动10个像素。而且此数值可能为负,负数表示反方向移动。
matrix
比较复杂,这里就不做深入讨论了。有兴趣的可以移步此处深入学习。
变形本身无法完成动画,但是当它和过渡结合起来之后,就具备了实现动画的一切条件。简而言之:变形定义了如何改变元素的形体,而过渡定义了时间关系。组合起来,我们就可以让一个元素,在某一段时间内,由一个形体慢慢的变成另外一个形体,这就是动画。
其实有了上边的过渡和变形,我们就可以完成很多动画的动作部分。比如,当鼠标:hover
到一个元素的时候,我们将这个元素水平移动300px,但是这个过程需要持续1秒:
.circle {
transition: all 1s ease 0s;
}
.circle:hover {
background-color: orange;
transform: translate(300px);
}
这时候当移动鼠标到.circle
上时,就会看到这个元素会缓慢的移动到右边。
我们还可以给:active
状态加一个不同的样式:
.circle:active {
background-color: orangered;
transform: scale(1.2);
}
当用户按下鼠标(点击button的动作),会看到圆形会变大一些,而且颜色会变成橘红色
,当然由于设置了过渡,这个过程式渐变式的。
但是这个动画的也有限制:
这就需要我们引入更高级一些的动画(animation),首先我们来看看关键帧
的概念
关键帧在很多动画系统都有定义。关键帧中包含了一些自定义的状态,每个状态都会定义一些不同的属性值,然后在这些状态之间,浏览器会自动插入一些值(通过过渡的方式)。
比如,我们可以定义这样的一个关键帧:
@keyframes breath {
0% {
opacity: 1;
}
50% {
opacity: .3;
}
100% {
opacity: 1;
}
}
这个关键帧的名字为breath
,状态0%
的时候,不透明度为1,50%
的时候为0.3,100%
的时候又恢复到1。定义好关键帧之后,我们需要通过animation
属性来使用它:
.circle {
animation: breath 2s ease-in-out infinite;
}
我们指定animation
的一些参数:使用那些关键帧,动画持续多长时间,以何种时间函数,动画重播次数等。这些参数可以分别指定:
比如下面这个属性指定:使用breath
关键帧,以2s为周期,时间函数为ease-in-out
,然后无限循环播放。
animation: breath 2s ease-in-out infinite;
我们再来定义一个跳动的效果:
@keyframes beat {
0% {
transform: scale(1);
}
50% {
transform: scale(1.2);
}
100% {
transform: scale(1);
}
}
然后在某个元素中使用这个关键帧:
.circle {
animation: beat 2s ease-in-out infinite;
}
有了这些基础知识,我们就可以来进行一些实例的开发了。这里有一组非常有趣的动画,设计师提供的是GIF格式的图片。为了完成动画,我们首先需要设计师提供的素材集:所有动画元素的原图。
以打印机这个动画为例,我们需要一个纸张,一个连杆,还有打印机的基座:
有了这些基础元素,我们首先需要将各个元素按照静态的方式摆放整齐。首先需要定义合理的DOM结构:
<section class="typing">
<div class="typewritter"></div>
<div class="handlebar"></div>
<div class="paper"></div>
</section>
然后就可以进行静态内容的摆放了:
.typing {
background-color: #CBADDA;
width: 220px;
height: 220px;
border: none;
border-radius: 50%;
position: relative;
.typewritter {
position: absolute;
background-image: url('/images/typing/typewritter.png');
background-size: cover;
width: 144px;
height: 107px;
top: 120px;
left: 38px;
z-index: 3;
}
.handlebar {
position: absolute;
background-image: url('/images/typing/handlebar.png');
background-size: cover;
width: 169px;
height: 28px;
top: 108px;
left: 26px;
z-index: 1;
}
.paper {
position: absolute;
background-image: url('/images/typing/paper.png');
background-size: cover;
width: 127px;
height: 106px;
top: 50px;
left: 40px;
z-index:2;
}
}
这个例子中的图片尺寸都是按照设计师提供的为准。通过将容器元素.typing
的position
属性设置为relative
,所有的子元素的position
属性设置为absolute
,然后通过绝对定位来定位。我们只需要调整元素的top/left即可完成初步的布局:
让连杆动起来看起来很容易,只需要保证他左右摆动(通过使用转换)即可:
@keyframes handlebar {
0% {
transform: translateX(0);
}
25% {
transform: translateX(10px);
}
50% {
transform: translateX(0);
}
75% {
transform: translateX(-10px);
}
100% {
transform: translateX(0);
}
}
然后再快速的应用这个关键帧即可:
.handlebar {
animation: handlebar .5s ease-in infinite;
}
可以看到,打印机的连杆动起来了,但是我们的帧分配的太过平均,我们可以植入一些值,使得动画更加真实:
80% {
transform: translateX(0);
}
这个帧插入后,连杆从75%到80%会突然卡一下,这样看着会更加真实一些。
做完连杆之后,我们来做纸张的动画。纸张的基本动作很简单,就是上下移动:
@keyframes paper {
0% {
transform: translateY(0);
}
30% {
transform: translateY(-10px);
}
50% {
transform: translateY(-20px);
}
60% {
transform: translateY(-30px);
}
80% {
transform: translateY(-40px);
}
90% {
transform: translateY(-45px);
}
100% {
transform: translateY(0);
}
}
然后应用这个关键帧给.paper
即可:
.paper {
animation: paper 2s ease-out infinite;
}
为了更加真实,让纸张有跳动感,我们可以插入一些帧,让纸张在完成向上的移动后,有一个短暂的(两个像素)回跳:
@keyframes paper {
0% {
transform: translateY(0);
}
30% {
transform: translateY(-10px);
}
35% {
transform: translateY(-8px);
}
50% {
transform: translateY(-20px);
}
55% {
transform: translateY(-18px);
}
60% {
transform: translateY(-30px);
}
65% {
transform: translateY(-28px);
}
80% {
transform: translateY(-40px);
}
85% {
transform: translateY(-28px);
}
90% {
transform: translateY(-45px);
}
100% {
transform: translateY(0);
}
}
我们再来实现一个稍微复杂一些的动画效果:
首先还是将图层通过静态方式定位:
<section class="mag">
<div class="magazine"></div>
<div class="picture"></div>
<div class="yellow-bar"></div>
<div class="blue-bar"></div>
</section>
我们定义了4个元素,左边的图标,右边的图标,两个小的指示器。对应的CSS为:
.mag {
background-color: #6A9ACE;
width: 220px;
height: 220px;
border: none;
border-radius: 50%;
position: relative;
overflow: hidden;
.magazine {
position: absolute;
background-image: url('/images/dissemination-assets/magazine.png');
background-size: cover;
width: 139px;
height: 157px;
top: 36px;
left: 30px;
}
.picture {
position: absolute;
background-image: url('/images/dissemination-assets/pic.png');
background-size: cover;
width: 94px;
height: 88px;
top: 94px;
left: 108px;
}
.yellow-bar {
position: absolute;
background-image: url('/images/dissemination-assets/yellow.png');
background-size: cover;
width: 27px;
height: 9px;
top: 116px;
left: 166px;
}
.blue-bar {
position: absolute;
background-image: url('/images/dissemination-assets/blue.png');
background-size: cover;
width: 25px;
height: 14px;
top: 154px;
left: 164px;
}
}
首先来看左边的图表,仔细观察GIF会发现,当图标移动到位置之后,还会向回弹一点。这个效果模拟了现实世界中的重力,显得更加真实。这个图表会略微停留一点时间,然后消失,仔细观察发现它运动的时间大约占所有时间的30%左右,因此我们需要它在30%前就完成所有的动作,然后停留剩余的70%时间。
@keyframes right-jump {
0% {
top: -50px;
left: -150px;
transform: rotate(0deg);
}
15% {
transform: rotate(-20deg);
}
18% {
transform: rotate(-10deg);
}
20% {
transform: rotate(-3deg);
}
25% {
top: 36px;
left: 30px;
transform: rotate(5deg);
}
30% {
top: 36px;
left: 30px;
transform: rotate(0deg);
}
100% {
display: none;
}
}
开始时,元素位于一个不可见的位置,到达15%的时候,它会逆时针旋转20度,由于这时候元素还不可见(容器元素的限定了overflow为hidden),因此这里相当于重新设置了元素的初始状态。18%的时候逆时针旋转18度,相对于上一个状态,它事实上是顺时针旋转了2度。一直到25%,元素旋转了25度。然后紧接着在30%的时候,我们让元素旋转到0度,这相当于向回转。
这样,该元素就会相对较慢的旋转到指定位置,然后像刹车时的惯性一样弹回到最终位置。默认的,rotate是根据元素的中心为动画基准点的,我们需要以元素的左下角为基准点,因此需要设置transform-origin
属性为bottom left
:
.magazine {
animation: right-jump 2s ease-in-out infinite;
transform-origin: bottom left;
}
同样,右边稍小一点的图表也可以应用类似的动画效果:
@keyframes left-jump {
0% {
top: 100px;
left: 260px;
transform: rotate(0);
}
40% {
top: 100px;
left: 260px;
transform: rotate(8deg);
}
44% {
top: 95px;
left: 230px;
transform: rotate(6deg);
}
48% {
top: 90px;
left: 200px;
transform: rotate(4deg);
}
52% {
top: 85px;
left: 170px;
transform: rotate(2deg);
}
56% {
top: 90px;
left: 140px;
transform: rotate(0deg);
}
60% {
top: 96px;
left: 106px;
transform: rotate(-3deg);
}
65% {
top: 94px;
left: 108px;
transform: rotate(0);
}
100% {
top: 94px;
left: 108px;
display: none;
}
}
最后,两个小图例是一个缩放的动画,但是时机需要正好在较小的图表就位之后。
@keyframes bar {
0% {
transform: scale(0);
}
80% {
transform: scale(0);
}
90% {
transform: scale(1);
}
95% {
transform: scale(1);
}
100% {
display: none;
}
}
由于这两个图例出现的时机比较晚,我们需要将实际缩放的时机拖后,即,在80%的时候,sacle
的参数还是0,相当于在它的整个动画周期中,动画启动的时机会延后,这样它就可以和其他的元素动画同步起来了。
.yellow-bar {
animation: bar 2s ease infinite;
}
.blue-bar {
animation: bar 2s ease infinite;
}