[譯] 50 行 CSS 代碼擼一個陰陽八卦的 Loading 動效
原文地址: Creating Yin and Yang Loaders On the Web | CSS-Tricks
背景
網上做 Loading 動效的教程和工具比比皆是,用八卦圖的還是頭一次聽說 =,=
因為一直都比較喜歡這種小而精、並且能讓人眼前一亮的玩法。萬萬沒想到居然還是個外國程序媛(推特請戳)的作品。
果斷翻譯過來供大家參考。
正文
不久前我見到了這樣的動畫。這讓我有了一個想法即:我要在不使用外部的庫的情況下,用儘可能少的代碼、使用多樣化的方法(其中包括一些如今可以使用的新功能,如 CSS 變數)創建我自己的版本。
本文將引導你完成構建這些 Demo 的過程。
在介紹其他任何步驟之前,先給出我最終要實現的動畫效果,如下:
期望的效果: 一個旋轉的八卦圖,伴隨著兩個 「葉片」 大小循環地增加和縮小
從何處開始?
無論我們選擇使用哪種方式重新創作上述動畫,我們總是會先從靜態陰陽形狀開始,如下所示:
靜態的陰陽八卦圖
該起始靜態形狀的結構如下圖所示:
靜態八卦圖的結構
首先,我們有一個直徑為 d 的大圓。在這個圓內,我們緊緊地嵌入兩個較小的圓圈,每個圓圈的直徑都是我們初始大圓直徑的一半。 這意味著這兩個較小圓的每一個的直徑等於大圓的半徑 r(或 0.5 * d)。在直徑為 r 的這兩個較小圓的內部,我們有一個更小的同心圓。
如果我們畫出通過所有這些圓的所有中心點的大圓的直徑 - 上圖中的線段 AB,它與內圓之間的交點將其分成 6 個相等的較小的段。 這意味著其中一個最小圓的直徑為 r/3(或 d/6),其半徑為 r/6。
純 HTML + CSS 版
在這種情況下,我們可以用一個元素和它的兩個偽元素來實現。 以下動畫說明了創建兩個「葉片」的方式(因為整個面板將會旋轉,所以切換對稱軸無關緊要):
實際的元素是外層的大圓,它有一個從上到下的漸變,中間有一個尖銳的過渡。 偽元素是我們放置的較小圓。一個較小圓的直徑是大圓的直徑的一半。 兩個較小的圓都與大圓垂直中心對齊。
開始編寫代碼!
首先,我們決定大圓圈的直徑 $d。 我們使用 viewport 單位,以便在調整大小時可以很好地擴展。 我們將這個直徑值設置為其 width 和 height,使用 border-radius使元素圓形化,並給出一個從上到下的漸變背景,中間從到黑色到白色的過渡。
animated illustration: how to ? out of 3 components$d: 80vmin;nn.? {n width: $d; height: $d;n border-radius: 50%;n background: linear-gradient(black 50%, white 0);n}n
So far, so good:
? with HTML + CSS, step #1
現在我們來看看我們用偽元素創建的較小的圓。 我們給我們的元素顯示:通過設置align-items:center,使它的孩子(或我們的例子中的偽元素)中間垂直對齊。 我們使這些偽元素具有其父元素的一半高度(50%),並且確保它們水平地覆蓋大圓圈的一半。 最後,我們將它們與border-radius進行整合,給它們一個虛擬背景,並設置內容屬性,以便我們可以看到它們:
.? {n display: flex;n align-items: center;n /* same styles as before */nn &:before, &:after {n flex: 1;n height: 50%;n border-radius: 50%;n background: #f90;n content: ;n }n}n
? with HTML + CSS, step #2
接下來,我們需要給他們不同的背景:
? with HTML + CSS, step #3.? {n /* same styles as before */nn &:before, &:after {n /* same styles as before */n background: black;n }nn &:after { background: white }n}n
有點像那麼回事兒了對吧!
在我們得到靜態形狀之前,要做的就是給這兩個偽元素添加邊框。 黑色應該是一個白色的邊框,而白色的黑色邊框應該是黑色的。 這些邊界應該是偽元素直徑的三分之一,這是大圓圈直徑的三分之一,即 $d/6。
.? {n /* same styles as before */nn &:before, &:after {n /* same styles as before */n border: solid $d/6 white;n }nn &:after {n /* same styles as before */n border-color: black;n }n}n
但是,結果看起來並不正確:
? with HTML + CSS, step #4這是因為在垂直方向上,邊框被加到了height。 水平地,我們沒有設置寬度,所以邊框可以從 content 空間獲得。 有兩個修復方法:一個是在偽元素上設置border-size:border-box;第二個是將偽元素的高度更改為$d/6 - 我們將使用後者:
? with HTML + CSS, step #5
我們現在有了基本的八卦形狀,所以讓我們繼續做動畫!
這個動畫從第一個偽元素縮小的狀態開始,讓我們設定原始大小為一半(這意味著縮放因子$f為 0.5),而第二個偽元素已經擴展到佔用所有可用的空間即:大圓圈的直徑(原始尺寸的兩倍)減去第一個圓圈的直徑(即其原始尺寸的$f); 然後狀態改變到第二個偽元件的縮小到$f的狀態,第一個偽元素已擴展到其原始大小的2 - $f。
第一個偽元素圓相對於其最左點(因此我們需要設置0 50%的變換原點),而第二個相對於其最右點(100%50%)進行縮放。
$f: .5;n$t: 1s;nn.? {n /* same styles as before */nn &:before, &:after {n /* same styles as before */n transform-origin: 0 50%;n transform: scale($f);n animation: s $t ease-in-out infinite alternate;n }nn &:after {n /* same styles as before */n transform-origin: 100% 50%;n animation-delay: -$t;n }n}n@keyframes s { to { transform: scale(2 - $f) } }n
我們現在有了形狀變化的動畫:
? with HTML + CSS, step #6最後一步是使整個形狀旋轉起來:
$t: 1s;nn.? {n /* same styles as before */n animation: r 2*$t linear infinite;n}n@keyframes r { to { transform: rotate(1turn) } }n
我們得到了最後的結果!
然而,還有一件事情我們可以做,使編譯的 CSS 更有效率:消除冗餘與 CSS 變數!
白色可以以 hsl(0,0%,100%)的形式寫入 HSL 格式。 色調和飽和度無關緊要,任何 100% 亮度的值都是白色的,所以我們將它們設置為 0,使我們的代碼執行效率更高。 類似地,黑色可以寫成 hsl(0,0%,0%)。 再次,色調和飽和度無關緊要,任何亮度為 0% 的值都是黑色的。 鑒於此,我們的代碼變成:
.? {n /* same styles as before */nn &:before, &:after {n /* same styles as before */n border: solid $d/6 hsl(0, 0%, 100% /* = 1*100% = (1 - 0)*100% */);n transform-origin: 0 /* = 0*100% */ 50%;n background: hsl(0, 0%, 0% /* 0*100% */);n animation: s $t ease-in-out infinite alternate;n animation-delay: 0 /* = 0*-$t */;n }nn &:after {n /* same styles as before */n border-color: hsl(0, 0%, 0% /* = 0*100% = (1 - 1)*100% */);n transform-origin: 100% /* = 1*100% */ 50%;n background: hsl(0, 0%, 100% /* = 1*100% */);n animation-delay: -$t /* = 1*-$t */;n }n}n
從上述結果可以看出:
- 我們的轉換起點的 x 分量是第一個偽元素的calc(0 * 100%),第二個是calc(1 * 100%)
- 我們的邊框顏色是第一個偽元素的 hsl(0,0%,calc((1 - 0)* 100%))和第二個 hsl(0,0%,calc((1 - 1)* 100%))
- 我們的背景是第一個偽元素的hsl(0,0%,calc(0 * 100%))和第二個偽元素的hsl(0,0%,calc(1 * 100%))
- 我們的動畫延遲是第一個偽元素的calc(0 *#{ - $ t}),第二個的calc(1 *#{ - $ t})
這意味著我們可以使用一個用作開關的自定義屬性 --i,第一個偽元素為 0,第二個為偽元素為 1:
.? {n /* same styles as before */nn &:before, &:after {n /* same styles as before */n --i: 0;n border: solid $d/6 hsl(0, 0%, calc((1 - var(--i))*100%));n transform-origin: calc(var(--i)*100%) 50%;n background: hsl(0, 0%, calc(var(--i)*100%));n animation: s $t ease-in-out calc(var(--i)*#{-$t}) infinite alternate;n }nn &:after { --i: 1 }n}n
這樣就消除了所有這些規則兩次的編寫:我們現在需要做的就是翻轉開關!
可悲的是,現在只能在 WebKit 瀏覽器中使用,因為 Firefox 和 Edge 不支持使用calc()作為動畫延遲值,Firefox 不支持在 hsl()當中中使用它。 =^=
Canvas + JavaScript
還有興趣?請戳原文吧SVG + JavaScript
還有興趣?請戳原文吧結語
代碼中的奇淫技巧固然重要,但歸根結底,好的 idea 才是技術進步的源泉。
如果覺得文章對你有幫助的話,去 Github Repo 給個 star 吧親~
推薦閱讀:
※吉卜力動畫檢查——館野仁美專訪
※為什麼比較老的動畫片里要動起來的部分顏色特殊?
※如何評價演出家,動畫監督山田尚子?
※國內 3D 動畫的製作成本構成是怎樣的?總成本構成呢?國外和國內有什麼不同?
※製片人野口和紀談《THE REFLECTION》的製作歷程:日式「斯坦李·宇宙」的誕生