3 Web Designs in 3 Weeks

动画

相较于静态内容,动态的内容对人眼更有吸引力。这可能来源于人类在漫长的进化中获得的能力,动态比静态更加危险!事实上,现实世界中我们已经不自觉的在使用这个法则,状态栏闪烁的QQ头像,界面上的弹出窗口,到处飘动的小广告,GIF动图,Flash广告等等。

GIF

页面上的动态元素会非常吸引读者的注意力,会使得页面更加生动,更清晰传神的表达出内容。我们常说,一图胜千言,而一个动画又可以胜过了多张图片(当然,我们需要把握好度,不能让动画干扰用户对内容的消费)。

页面大小

按照传统的方式,在页面上实现动画的方式有多种:

  1. 使用GIF图片
  2. 使用JavaScript来动态修改DOM的位置,加上一个定时器
  3. 使用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;
}

过渡就是将这个过程延长,我们只需要定义其实状态和结束状态的属性值,然后浏览器会根据预设的时间间隔来自动计算过程中的属性值。

transition

要定义一个过渡,我们可以指定这样一些参数:

  1. 开始时间(何时开始动画)
  2. 持续时间(动画持续时长)
  3. 属性值根据时间的变化函数(线性匀速,非线性)

其语法为:

#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已经预定义了一些命名的函数:

  1. linear 表示匀速
  2. ease 逐渐变慢
  3. ease-in 加速运动
  4. ease-out 减速运动
  5. ease-in-out 先加速后减速

Cubic Bezier

如果你精通数学的话,可以通过参数来自动以一个贝塞尔函数:

cubic-bezier(x1, y1, x2, y2)

你还可以通过这个地址Cubic Bezier进行预览,然后将参数拷贝到自己的CSS代码中。

two-circle

这样,当用户再将鼠标移动到圆上的时候,背景色会由黄绿慢慢的过渡到橙色

变形

变形是另外一个非常重要的CSS3特性。变形是指将元素的尺寸,位置,形状通过函数来进行变化。目前包括旋转(rotate),扭曲(skew),缩放(scale),移动(translate)以及矩阵变形(matrix)。

transform

旋转的语法为:

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);
}

rotate

扭曲会将元素按照x,y方向以一定的角度进行变形。这样可以将矩形变为平行四边形,菱形等形状。扭曲的语法为:

transform: skew(<x-angle>, <y-angle>);
.square {
    transform: skew(10deg);
}

skew

缩放将元素变大,涉及到缩放的时候,有一个缩放比率的问题,即元素在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的动作),会看到圆形会变大一些,而且颜色会变成橘红色,当然由于设置了过渡,这个过程式渐变式的。

但是这个动画的也有限制:

  1. 无法控制动画播放的时机
  2. 无法让动画循环播放

这就需要我们引入更高级一些的动画(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的一些参数:使用那些关键帧,动画持续多长时间,以何种时间函数,动画重播次数等。这些参数可以分别指定:

  1. animation-delay 从加载到执行动画间的延迟,默认无延迟
  2. animation-direction 动画播放完成之后,重播的起始方向
  3. animation-duration 动画播放周期时长
  4. animation-iteration-count 重复次数
  5. animation-name 关键帧名称

比如下面这个属性指定:使用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格式的图片。为了完成动画,我们首先需要设计师提供的素材集:所有动画元素的原图。

animation

以打印机这个动画为例,我们需要一个纸张,一个连杆,还有打印机的基座:

typewritter assets

有了这些基础元素,我们首先需要将各个元素按照静态的方式摆放整齐。首先需要定义合理的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;
    }
}

这个例子中的图片尺寸都是按照设计师提供的为准。通过将容器元素.typingposition属性设置为relative,所有的子元素的position属性设置为absolute,然后通过绝对定位来定位。我们只需要调整元素的top/left即可完成初步的布局:

typewritter static

动起来:

让连杆动起来看起来很容易,只需要保证他左右摆动(通过使用转换)即可:

@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);
  }
}

我们再来实现一个稍微复杂一些的动画效果:

charts

首先还是将图层通过静态方式定位:

<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;
}

移动端页面