CSS秘密花园: 文本动画

BerKidston 4年前
   <p>《 <a href="/misc/goto?guid=4958976660757929944" rel="nofollow,noindex">CSS Secrets</a> 》是 <a href="/misc/goto?guid=4958976660849001117" rel="nofollow,noindex">@Lea Verou</a> 最新著作,这本书讲解了有关于CSS中一些小秘密。是一本CSSer值得一读的一本书,经过一段时间的阅读,我、@南北和@彦子一起将在W3cplus发布一系列相关的读后感,与大家一起分享。</p>    <p><img src="https://simg.open-open.com/show/b873adc9c0b700ab68ae0efe9cf398be.jpg"></p>    <h3>问题</h3>    <p>有时候我们想要模拟文本输入的动画效果,就是文本一个一个的出现。这种动画效果特别在科技网站上流行。使用的正确,让你的网站效果直接提到另一个档次上。</p>    <p>通常要实现这样的效果都需要复杂的JavaScript脚本。尽管这只是纯粹的演示,使用CSS实现这样的效果那简直就是白日做梦。怎么可能呢?</p>    <p><img src="https://simg.open-open.com/show/fe95125962e5e361de52be844f53db04.png"></p>    <p><a href="/misc/goto?guid=4958868453432081143" rel="nofollow,noindex">欧洲核子研究中心官网</a> 就使用了这种动画效果。</p>    <h3>解决方案</h3>    <p>实现这个动画思路是包含文本的元素宽度从 0 慢慢过渡(一个字符一个字符)元素内容宽度。你可能意识到这种方法的局限性是什么:它不能在多行文本中运行。值得庆幸的是,大多数时候,只是单行文本上使用这种动画效果,比如说标题。</p>    <p>另外有一点需要注意的是,动画的效果随着时间的增长会有所减弱。动画持续的时间越短动画效果越好,用户体验也越好;持续时间越长,动画效果会让用户感觉很无聊。综上所述,即使技术允许使用在长文本上,但应该尽量避免使用在多行文本上。</p>    <p>让我们开始写代码吧,假设我们将这个效果用在 <h1> 的标题上,并且给文本设置等宽字体,代码如下:</p>    <pre>  <code class="language-css"><h1>CSS is awesome!</h1>  </code></pre>    <p>我们可以添加一个简单动画,宽度从 0 过渡到标题内容的宽度:</p>    <pre>  <code class="language-css">@keyframes typing {      from { width: 0 }  }  h1 {      width: 7.7em; /* Width of text */      animation: typing 8s;  }  </code></pre>    <p>这非常有意义,对吗?然而,正如下图所看到的,这是一个失败的效果,并不是我们需要的东西:</p>    <p><img src="https://simg.open-open.com/show/dd9d366b99332a04277b6e3c2f0f674a.png"></p>    <p>你可能已经猜到问题所在。你会想起使用 white-space:nowrap; 来防止文本换行,因为宽度会增加,行数变为一行。第二,使用 overflow:hidden; 截取溢出文本。解决了这些问题,我们动画才可能像是我们需要的,如下图所示:</p>    <p><img src="https://simg.open-open.com/show/424136a72823e6c6ccf9f981c7c2dee8.png"></p>    <ul>     <li>显而易见的问题是动画是平滑的,但不是一个一个字符的出现。</li>     <li>不太明显的问题是,到目前为止,使用 em 做为宽度单位,而没有使用 px ,虽然 em 比 px 更好,但效果仍然不佳。而且宽度为 7.7em 是从哪里来的?又是如何计算出来的呢?</li>    </ul>    <p>解决第一个问题可以通 steps() 处理,就像前面介绍的逐帧动画和闪烁动画一样。不幸的是,我们需要的步骤是字符串的字符数,这对于动态的文本是完全不可能的。然而,稍后我们将看到,使用一小段JavaScript代码可以实现。</p>    <p>解决第二个问题可以使用 ch 单位, ch 单位是 <a href="/misc/goto?guid=4959670925221365481" rel="nofollow,noindex">CSS3中新引入的一个单位</a> , 1ch 的大小和字母 o 的宽度相等。新单位最未知的情况之一是因为大多数情况下,并不太在乎元素宽度是相对于字符 o 宽度。然而,等宽字体是一种特殊的字体。等宽字体每个字符的宽度都和字母 o 宽度一样。因此,在我们示例中宽度就是 15 个字符数的 ch 宽度。</p>    <p>将他们放在一起:</p>    <pre>  <code class="language-css"><h1>CSS is awesome!</h1>    @keyframes typing {      from {         width: 0;       }  }  h1 {      width: 15ch; /* Width of text */      overflow: hidden;      white-space: nowrap;      animation: typing 6s steps(15);  }  </code></pre>    <p><img src="https://simg.open-open.com/show/336cd100c5be6aa9bcf94612eb178bff.png"></p>    <p>正如你看到的上图。现在我们终于看到了预期的动画效果:我们文本是一个一个字符的出现。然而,它仍然还不完美,难道是哪少了什么吗?</p>    <p>还有最后一个效果,给字符后面添加闪烁的光标。在上一节中,我们知道如何创建闪烁动画。可以通过伪元素来创建闪烁光标,并且使用 opacity 属性,当然伪元素起到的作用也有限,在这个示例中,使用右边框来替代:</p>    <pre>  <code class="language-css">@keyframes typing {      from { width: 0 }  }  @keyframes caret {      50% { border-color: transparent; }  }  h1 {      width: 15ch; /* Width of text */      overflow: hidden;      white-space: nowrap;      border-right: .05em solid;      animation: typing 6s steps(15),caret 1s steps(1) infinite;  }  </code></pre>    <p>注意,不同于文本逐个出现的动画,光标符需要不停的闪烁(甚至是所有字符都出来了)。因此给动画设置一个 infinite 关键词,让他无限次播放。同时,我们没有指定边框颜色,主要是让其自动获取文本颜色。如下图所示:</p>    <p><img src="https://simg.open-open.com/show/f34edfc16c0efd07a7925e067cfeafb7.png"></p>    <p>现在我们的动画作品比较完美了,尽管维护不是很容易:根据不同的内容的字符数要为标题设置不同的样式。不过使用JavaScript代码就可以完美的完成这个任务:</p>    <pre>  <code class="language-css">$$('h1').forEach(function(h1) {      var len = h1.textContent.length, s = h1.style;      s.width = len + 'ch';      s.animationTimingFunction = "steps("+len+"),steps(1)";  });  </code></pre>    <p>就只要这几行JS代码,不仅实现了我们要的动画效果,代码也易维护。</p>    <p>注意:也可以使用下面的一段JS代码:</p>    <pre>  <code class="language-css">var aH1 = document.getElementsByTagName('h1');  for(var i = 0, len = aH1.length; i < len; i++){      var textLen = aH1[i].textContent.length, s = aH1[i].style;      s.width = textLen + 'ch';      s.animationTimingFunction = "steps("+textLen+"),steps(1)";  }  </code></pre>    <p>这是不错的,浏览器不支持CSS动画会发生什么?从本质上说,他们将忽略所有有关于CSS的 animation 相关的东西,浏览器会能识别:</p>    <pre>  <code class="language-css">h1 {      width: 15ch; /* Width of text */      overflow: hidden;      white-space: nowrap;      border-right: .05em solid;  }  </code></pre>    <p><img src="https://simg.open-open.com/show/fb624f37cbbb6db32361169bc8698881.png"></p>    <p>浏览器不支持动画效果,会降级处理,上面的是支持 ch 效果,下面的是不支持 ch 单位的效果</p>    <p>正如上图所看到的,效果好不好主要取决于浏览器是否支持 ch 单位。如果你想避免底部那个效果,你可以通过 em 做降级处理。如果你不希望光标字符在最后,你可以改变光标字符动画中关键帧中的 border 属性,这样你只会看到一个透明的边框,如下:</p>    <pre>  <code class="language-css">@keyframes caret {      50% { border-color: currentColor; }  }  h1 {      /* ... */      border-right: .05em solid transparent;      animation: typing 6s steps(15),caret 1s steps(1) infinite;  }  </code></pre>    <p>来自: <a href="/misc/goto?guid=4959670925303652067" rel="nofollow">http://www.w3cplus.com/css3/css-secrets/typing-animation.html</a></p>    <p> </p>