Transitions & Animations

我之前也接触过动画,但是平时没怎么用,一段时间不看就又忘了,transition、translate、transform记得头皮发麻。若本篇文章从概念的角度去介绍transition和animation的用法,会显得枯燥冗余,而且网上资料很多,解释比较单一,比较难理解。今天换一种方式,主要从应用的角度去认识transition和animation的用法。

关于transition和animation属性

transition和animation思维导图

Elastic transition (弹性过渡)

小球下落动画

根据常识(自由落体)我们都知道,球下降速度会不断变化,接触到地面会反弹,因为还有速度,会继续上升一段时间,后再次下落,如此往复,直到静止。
小球下落过程图
重力势能->动能->重力势能->动能->静止(阻力的存在)

@keyframes

@keyframes创建动画的原理是,定义关键帧的CSS样式,将一套 CSS 样式逐渐变化为另一套 CSS 样式。
以百分比来规定改变发生的时间,或者通过关键词 “from” 和 “to”,等价于 0% 和 100%。

  1. 语法

    1
    @keyframes animationname {keyframes-selector{css-style;}}
  2. 示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @keyframes bounce{
    60%,80%, to{//60%、80%、100%时的位置
    transform: translateY(400px);
    animation-timing-function: ease;/*特别规定其的timing函数*/
    }
    70%{//70%时的位置
    transform: translateY(300px);
    }
    90%{//90%时的位置
    transform: translateY(360px);
    }
    }

贝塞尔曲线

timing-function有五个可选值:ease(默认),linear(匀速),ease-in(逐渐加速),ease-out(逐渐减速),ease-in-out(先加速后减速)。想要定义自己想要的效果,就要用到贝塞尔函数cubic-bezier(x1,y1,x2,y2)
4个贝塞尔曲线图

  1. 语法
    对应图中的坐标(x1,y1)、(x2,y2),其中x轴是时间轴,y轴是进度轴,导数就是这个时刻点的速率。

    1
    cubic-bezier(x1,y1,x2,y2)
  2. 贝塞尔曲线网站
    cubic-bezier

弹性过渡

  1. 背景
    有这样一个需求:一个输入框,当focus的时候有一个tip弹出,提示用户输入框该输入啥,tip显隐的时候就会有过渡的效果。

  2. 代码

    • HTML

      1
      2
      3
      4
      5
      <label>
      Your username: <input name="elastic" id="username" />
      <span class="callout">Only letters, numbers,
      underscores (_) and hyphens (-) allowed!</span>
      </label>
    • CSS

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      body {
      padding: 1.5em;
      font: 200%/1.6 Baskerville;
      }
      input {
      display: block;
      padding: 0 .4em;
      font: inherit;
      }
      .callout{
      position: absolute;//@@inline元素scale()无效
      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{/*完成倒三角*/
      position: absolute;
      top: -.4em;
      left: 1em;
      content: '';
      padding: .35em;
      background: #fed;
      border: inherit;
      border-right: 0;
      border-bottom: 0;
      transform: rotate(45deg);
      }
      /*用transition来实现*/
      input[name="elastic"]:not(:focus) + .callout{
      transform: scale(0);
      /*属性对应解释
      *@@ 消失的时间会比出现的时间长,因为出现只用了500ms的一半,加个transition-duration控制时间
      *@@ 加上transition-property,指定要过渡的属性为transform
      *@@ 规定了不聚焦时的动画函数为ease,不加这个,消失时会突然变大一下,默认用了和出现时相同的timing-function
      */
      transition: .25s transform ease;
      }
      .callout{
      transition: .5s transform cubic-bezier(.25,.1,.3,1.5);/*贝塞尔函数横轴(Time)不能超过1,但纵轴(Progress)可以,有Back效果*/
      transform-origin: 1.4em -.4em;/*动画起步点*/
      }

Frame by frame animation (逐帧动画)

  1. 背景
    在我们拥有一个动画每一帧的情况下,我们来完成一个loading效果,雪碧图如下
    loading雪碧图

  2. animation-timing-function之steps()
    相较于贝塞尔曲线,steps可以被认为是分段函数
    steps曲线图

  3. 代码

    • HTML

      1
      <div class="loader">Loading...</div>
    • CSS

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      @keyframes loader{
      to{
      background-position: -800px 0;
      }
      }
      .loader{
      width: 100px;
      height: 100px;
      background: url(img/loader.png) 0 0;
      /*steps()
      *@@ steps(8)表示loader的每个阶段分8帧
      *@@ 这里整个动画时间为1s,动画只有一个阶段,有八帧,说明每帧的停留时间就是0.125s(125ms)
      *@@ steps的设置都是针对两个关键帧之间的,而非是整个keyframes
      */
      animation: loader 1s infinite steps(8);
      line-height: 100px;
      text-align: center;
      }

Blinking (闪烁)

之前是有标签的,用于定义闪烁的字体,已被废除
揭秘史上最遭恨的 Blink 标签的起源
现在css还保留的text-decoration:blink,然而我发现大部分浏览器也是不支持的
但有时候仍然需要blink的效果,我们可以用动画来实现

思路

闪烁其实就是文字的显示隐藏,当频率达到一定值时,就有闪烁的感觉:

  • 可以通过对dom结构的opacity设置为0来隐藏
  • 可以通过对文本处理,color为transparent来隐藏
  • 可以通过border-color来达到闪烁的效果

我们接下来以普遍的文字闪烁进行讲解。

代码

  • HTML结构

    1
    <p class="highlight">blink blink blink</p>
  • CSS

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //这样达到了闪烁的预期,但是,还是有需要优化的地方:我们能明显感觉到,字体消失的下一个又突然的变亮,不够的自然。
    @keyframes blink-smooth{
    50%{/*有1/2帧在停留状态*/
    color: transparent;
    }
    }
    .highlight{
    animation: 1s blink-smooth infinite;
    font-size: 50px;
    }

以上代码产生的效果会不够自然,颜色变化可以参照下图
normal对应效果
想要有渐变的效果,就要对动画的方向进行改变

animation-direction

四个值

  • Normal 规定的正向效果
  • Reverse 规定的反向
  • Alternate 奇数次时是正向,偶数次时是反向
  • Alternate-revere 偶数次时是正向,奇数次时是反向
    四个值分别对应的效果为
    alternate对应效果

    改进后的CSS

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @keyframes blink-smooth{
    to{//100%时字体是透明的
    color: transparent;
    }
    }
    .highlight{
    animation: 1s blink-smooth infinite alternate;/*随着奇偶反转*/
    font-size: 50px;
    }

Typing animation (打字效果)

有时候啊,我们希望文字一个个出现,模拟一下打字的场景,感觉那样非常的酷
饿了么

思路

我们可以用动画的效果逐渐增加可见区域dom的长度,从而实现打字的效果
局限性,只能打一行的字,不支持多行,但是一行也能满足我们的要求

初体验

代码

  • HTML

    1
    <h1>CSS is awesome!</h1>
  • CSS

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @keyframes typing{
    from{
    width: 0;
    }
    }
    h1{
    animation: typing 8s;
    font-size: 2em;
    width: 30em;/*15*2=30em*/

    white-space: nowrap;
    overflow: hidden;
    }

新的问题

新的问题出现:

  1. 动画太平滑了,没有那种一个文字一个文字打出来的感觉
  2. 还有个不明显的问题就是:我们这边写死了宽度,这个宽度是哪里来的,瞎蒙的么?字数变化了,我们又该怎么计算这个宽度?
  3. 缺少光标

解决方案

第一个问题用steps来解决,因为steps可以给我们提供一帧一帧的感觉
第二个问题:
引入ch单位的概念,表示0这个字符的长度,monospace(单间隔)字体中,0和任意字符的长度是一样的,所以长度是15(包括空格),但是这个还是要手动去设置dom的长度,结合js我们可以自动设置dom的长度,便于拓展和可维护性
第三个问题发挥想象力,最右边的字永远是靠近dom结构的border的,所以我们可以把border作为我们的光标

再次尝试

  • HTML

    1
    2
    <h1>CSS is awesome!</h1>
    <h1>CSS is awesome!hahahah</h1>
  • CSS

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    @keyframes typing{
    from{
    width: 0;
    }
    }
    @keyframes caret{
    50%{
    border-color: transparent;
    }
    }
    h1{
    font: bold 200% Consolas, Monaco, monospace;/*一定要是单间隔字体哦!*/
    white-space: nowrap;
    overflow: hidden;
    border-right: .05em solid;/*不需要去指定border的颜色,默认为text的颜色*/
    animation: typing 7s steps(15),caret 1s steps(1) infinite;
    }
  • JS

    1
    2
    3
    4
    5
    [].forEach.call(document.getElementsByTagName('h1'),function(h1) {
    var len = h1.textContent.length, s = h1.style;
    s.width = len + 'ch';
    s.animationTimingFunction = "steps("+len+"),steps(1)";//一个是typing,一个是caret
    });

Smooth state animations (平滑动画)

当我们有一个很长的图片,比如说风景图,
这张风景图(1295x616)呢,要显示在一个相对小很多的容器里面,比如说150x300,我们要怎么处理?
首先一种思路我们可能压缩图片嘛,但是必须是等比例的压缩,不然会变形
然后想到了css属性background-size可以调整图片尺寸大小,比如说contain(照片会被包含在容器内,但往往会放大缩小),cover(损失掉多出的部分)
强行设置background-sizing:100% 100%;图片就会变形
其实我们可以用动画的效果让图片不断的滚动,这样用户可以看到图片上的所有内容,也好似身临其境,用户体验是非常棒的。

思路

通过动画让图片滚动

初体验

  • HTML

    1
    <div class="panoramic"></div>
  • CSS

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @keyframes panoramic{
    to {
    background-position: 100% 0;
    }
    }
    .panoramic{
    margin: 0 auto;
    width: 150px;
    height: 300px;
    background: url('./img/lanscape.jpg');
    background-size: auto 100%;
    animation: panoramic 10s linear infinite alternate;
    }

新的问题

新的问题出现:

  1. 有时候我们需要的动画不是自动播放的,我们需要根据用户的一些操作去响应我们的动画,比如:hover伪类的时候实现动画效果
    那么我们需要考虑的就hover的时候动画开始,鼠标移除的时候,动画停止。

解决方案

我们要是能设置鼠标移入和移出时的动作就好了,控制动画的状态那就完美,这就要用到下面的属性
Animation-play-state(用来控制动画的状态)有两个状态:paused(动画暂停)和running(动画开始)

再次尝试

  • HTML

    1
    <div class="panoramic"></div>
  • CSS

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    @keyframes panoramic{
    to {
    background-position: 100% 0;
    }
    }
    .panoramic{
    margin: 0 auto;
    width: 150px;
    height: 300px;
    background: url('./img/lanscape.jpg');
    background-size: auto 100%;
    animation: panoramic 10s linear infinite alternate;
    animation-play-state: paused;
    }
    .panoramic:hover,.panoramic:focus{
    animation-play-state: running;
    }