『CSS Secrets』は、@Lea Verou による最新の本で、CSS に関する小さな秘密を説明しています。これは CSSers にとって読む価値のある本です。一定期間読んだ後、私、@全域と @彦子は、関連する読書感想文を W3cplus で公開し、皆さんと共有します。
数年前、CSS アニメーションが新しくてとてもエキサイティングだった頃、Chris Coyier は、CSS を使用して円形のパスの周りに要素を移動させる方法はないか私に尋ねました。当時は単なる興味深いアイデアだったのですが、これの使用例をたくさん見つけました。たとえば、Google+ では、新しいメンバーを追加するためにこのようなアニメーションが使用されます。以下に示すように:
ユニークで興味深い例としては、以下に示すように、ロシアのテクノロジー Web サイトの 404 ページを参照してください:
通常、練習に適した場所は、図に示すように 404 ページです。上記のように、Web サイトの主要エリアのナビゲーション メニューが提供されます。これらのメニューは円形のパスを移動します。
しかし、各メニュー項目は地球の周りを回る惑星のようなもので、その上のいくつかのテキストには「他の星の宇宙へ飛ぶ」と書かれています。もちろん、惑星が単に円形のパスの周りを移動し、テキストが回転されなかった場合、これらのテキストはほとんど判読できなくなります。これは多数あるうちの一例にすぎません。しかし、CSS アニメーションを使用してこの効果を実現するにはどうすればよいでしょうか?
非常に単純な例を書いてみましょう。円形のパスを中心に回転する頭の動きです。これは、前述した Google+ エフェクトの簡易版に似ています。その構造は次のとおりです:
<div class="path"> <img src="lea.jpg" class="avatar" /></div>
アニメーションの作成を開始する前に、最初にいくつかの基本的なスタイル (サイズ、背景、マージンなど) を設定します。これは次の図のようになります:
これらは基本的なスタイルであるため、 , ここには書かれていませんが、問題がある場合は、例のコードを確認してください。覚えておくべき主な点は、パスの直径が 300px であるため、半径は 150px であるということです。
基本的なスタイルが完成したら、アニメーションの書き方を考え始めます。アバターを円の中に移動し、オレンジ色のパスを中心に回転させます。これを実現するには CSS アニメーションを使用できますが、そのようなアニメーションはどのように記述すればよいでしょうか?この問題に直面したとき、次のことが頭に浮かびます:
うーん、これは正しい方向への一歩ですが、以下に示すように、アバターは円形のパスの周りを回転しています:
上に示すように、アバターは円形のパスを中心に回転しますが、アバター自体も上下逆さまになっていることがわかります。テキストの場合は、文字も上下逆さまになってしまい、読むのにかなり悪影響を及ぼします。アバターが同じ方向を向いたまま円形のパスを移動するだけです。
当時、クリスも私も、この問題を解決する合理的な方法を思いつきませんでした。私たちが思いつく最善の方法は、複数のキーフレームを通るおおよその円形のパスを描くことですが、これは明らかに良いアイデアではなく、そのような円形のパスを定義する方法はありません。したがって、より良い方法を考え出す必要がありますね?
2 つの要素を使用した解決策
2 つの要素 が必要であることに注意してください。したがって、アバターをラップするには、追加の HTML 要素 代码我们需要改进一些。首先,我们动画的所有参数重复两次。如果我们要调整时间,我们就需要调整两次,这样做较为麻烦。其实可以通过 inherit 属性继承父元素的 animation : 然而,我们不需要一个全新的动画,只需要最初的一个动画。记得我们在介绍“闪烁”动画一节中,有介绍过 animation-direction 属性,其中有一个 alternate 值是非常有用的。在这里我们将使用 reverse 值,得到一个反向的原动画,因此不需要创建第二个动画: 我们继续吧!这可能不是最理想的解决方案,因为他需要添加额外的元素,但只使用了不到十行的CSS代码实现了相当复杂的动画效果。 在前一节中,我们解决了问题,但这不是最优的方案,因为它需要修改HTML。当初我给CSS工作组提了一个建议,建议可以在相同的元素指定多个转换源。这应该能够在一个元素上实现,也似乎是一个合理的要求。 在讨论过程中,Aryeh Gregor给CSS的 transform 规范中提了这样一个声明,似乎令人困惑不解: transform-origin 就是一个语法糖。你可以使用 translate() 替代。—— @Aryeh Gregor 事实证明,每个 transform-origin 可以模拟两次 translate() 。例如下面的两个代码段是等价的: 这看起来很奇怪,让我们对 transform 了解的更清楚, transform 函数不是独立的。每个 transform 属性不仅应用在元素上,而且整个坐标系统运用在同一个元素上,也将影响所有的 transform 。这也说明 为什么不同的 transfrom 顺序很重要,不同顺序的相同转换可能前生的结果会完全不同 。如果你还不了解这一点,下图可以帮助你更好的理解: 因此,我们可以利用这个方法来处理我们的动画: 这样看起来很笨拙,但不要担心,接下来我们会改善。请注意,我们现在不再有不同的 transform-origin ,但我们要记住,我们要需要两个元素和两个动画。现在所有都使用相同的 transform-origin ,可以在 .avatar 上结合这两个动画: 这代码是得到了改善,但仍然让人感到困惑。我们还能让它变得更简洁吗?我们进一步来改进它: 我们连续做了多次 translate() 特别是 translate(-50%,-150px) 和 translate(50%,50%) 。不幸的是,百分比和绝对长度不能结合在一起(除非我们使用 calc() )。然而,水平的 translate 相互取消了,但还有两个次 translateY ( translateY(-150px) 和 translateY(50%) )。因为为了取消翻转,我们可以对关键帧这样做: 代码变得更少了,重复的也变得更少,但仍然不是很好。我们可以做到更好吗?如果我们的头像在圆的中心,如下图所示: 我们可以继续减少两个 translate ,然后动画就变成: @keyframes spin { to { transform: rotate(1turn); }}.avatar { animation: spin 3s infinite linear; transform-origin: 50% 150px; /* 150px = path radius */}
<div class="path"> <div class="avatar"> <img src="lea.jpg" /> </div></div>
@keyframes spin { to { transform: rotate(1turn); }}@keyframes spin-reverse { from { transform: rotate(1turn); }}.avatar { animation: spin 3s infinite linear; transform-origin: 50% 150px; /* 150px = path radius */}.avatar > img { animation: inherit; animation-name: spin-reverse;}
@keyframes spin { to { transform: rotate(1turn); }}.avatar { animation: spin 3s infinite linear; transform-origin: 50% 150px; /* 150px = path radius */}.avatar > img { animation: inherit; animation-direction: reverse;}
使用单个元素的解决方案
有关于相关的讨论可以点击这里阅读。
transform: rotate(30deg);transform-origin: 200px 300px;transform: translate(200px, 300px) rotate(30deg) translate(-200px, -300px);transform-origin: 0 0;
@keyframes spin { from { transform: translate(50%, 150px) rotate(0turn) translate(-50%, -150px); } to { transform: translate(50%, 150px) rotate(1turn) translate(-50%, -150px); }}@keyframes spin-reverse { from { transform: translate(50%,50%) rotate(1turn) translate(-50%, -50%); } to { transform: translate(50%, 150px) rotate(0turn) translate(-50%, -50%); }}.avatar { animation: spin 3s infinite linear;}.avatar > img { animation: inherit; animation-name: spin-reverse;}
@keyframes spin { from { transform: translate(50%, 150px) rotate(0turn) translate(-50%, -150px) translate(50%,50%) rotate(1turn) translate(-50%,-50%) } to { transform: translate(50%, 150px) rotate(1turn) translate(-50%, -150px) translate(50%,50%) rotate(0turn) translate(-50%, -50%); }}.avatar { animation: spin 3s infinite linear; }
@keyframes spin{ from{ transform: translateY(150px) translateY(-50%) rotate(0turn) translateY(-150px) translateY(50%) rotate(1turn); } to { transform: translateY(150px) translateY(-150%) rotate(1turn) translateY(-150px) translateY(50%) rotate(0turn); }}.avatar { animation: spin 3s infinite linear;}
@keyframes spin{ from{ transform: rotate(0turn) translateY(-150px) translateY(50%) rotate(1turn); } to { transform: rotate(1turn) translateY(-150px) translateY(50%) rotate(0turn); }}.avatar { animation: spin 3s infinite linear;}