天天看點

使用CSS變量簡化Apple Watch呼吸應用動畫

當我看到有關如何重新建立動畫的原始文章時,我首先想到的是,可以通過使用預處理器和特定CSS變量來簡化動畫。 是以,讓我們深入了解它,看看如何!

使用CSS變量簡化Apple Watch呼吸應用動畫
使用CSS變量簡化Apple Watch呼吸應用動畫

我們要複制的結果。

結構

我們保持完全相同的結構。

為了避免多次編寫相同的内容,我選擇使用預處理器。

我對預處理器的選擇始終取決于我要執行的操作,因為在很多情況下,諸如Pug之類的東西提供了更大的靈活性,但是有時,Haml或Slim允許我編寫最少的代碼,而不必引入代碼無論如何,我以後都不需要一個循環變量。

直到最近,在這種情況下,我可能仍會使用Haml。 但是,我目前偏愛另一種技術 ,它使我避免在HTML和CSS預處理程式代碼中同時設定項數,這意味着如果需要在某個時候使用其他值,則不必在這兩種情況下都進行修改。

為了更好地了解我的意思,請考慮以下Haml和Sass:

- 6.times do
  .item
           
$n: 6; // number of items

/* set styles depending on $n */
           

在上面的示例中,如果我更改了Haml代碼中的項目數,那麼我還需要在Sass代碼中進行更改,否則事情會中斷。 從某種程度上來說,結果不再是預期的結果。

是以,我們可以通過将圈數設定為稍後在Sass代碼中使用CSS變量的值來解決此問題。 而且,在這種情況下,使用Pug會更好:

- var nc = 6; // number of circles

.watch-face(style=`--nc: ${nc}`)
  - for(var i = 0; i < nc; i++)
    .circle(style=`--i: ${i}`)
           

我們還以類似的方式為每個

.circle

元素設定了索引。

基本風格

我們在車

body

保持完全相同的樣式,沒有變化。

就像結構一樣,我們使用預處理程式來避免多次編寫幾乎相同的東西。 我的選擇是Sass,因為這是我最滿意的,但是對于像本示範這樣的簡單事情,Sass并沒有什麼使其成為最佳選擇– LESS或Stylus也能勝任。 對我來說,編寫Sass代碼隻是更快。

但是我們使用預處理器做什麼呢?

好吧,首先,我們使用變量

$d

作為圓的直徑,這樣,如果我們想使它們變大或變小,并控制它們在動畫過程中走多遠,我們隻需要更改的值即可。這個變量。

如果有人想知道為什麼不在這裡使用CSS變量,那是因為我更喜歡隻在需要動态變量時才采用此路徑。 直徑不是這種情況,那麼為什麼還要編寫更多内容,然後甚至不得不想出可能遇到CSS變量錯誤的解決方法?

$d: 8em;

.circle {
  width: $d; height: $d;
}
           

請注意,我們沒有在包裝上設定任何尺寸(

.watch-face

)。 我們不需要。

通常,如果元素的目的隻是作為放置絕對位置的元素的容器,那麼該容器上将對其進行組轉換(是否經過動畫處理),并且該容器沒有可見的文本内容,沒有背景,沒有邊框,沒有框陰影...那麼就不需要在其上設定顯式尺寸了。

這方面的一個副作用是,為了保持我們的圈在中間,我們需要給他們一個負

margin

減去該半徑(即直徑的一半)。

$d: 8em;
$r: .5*$d;

.circle {
  margin: -$r;
  width: $d; height: $d;
}
           

我們還為他們提供了與原始文章相同的

border-radius

mix-blend-mode

background

,并且得到以下結果:

使用CSS變量簡化Apple Watch呼吸應用動畫
使用CSS變量簡化Apple Watch呼吸應用動畫

到目前為止的預期結果( 實時示範 )。

嗯,我們在WebKit浏覽器和Firefox中獲得了上述優勢,因為Edge尚不支援

mix-blend-mode

(盡管您可以投票支援實作 ,但是如果您希望看到它的支援,請這樣做,因為您的投票确實很重要),是以它向我們展示了一些醜陋的東西:

使用CSS變量簡化Apple Watch呼吸應用動畫
使用CSS變量簡化Apple Watch呼吸應用動畫

Edge的結果看起來不太好。

為了解決這個問題,我們使用

@supports

.circle {
  /* same styles as before */
  
  @supports not (mix-blend-mode: screen) {
    opacity: .75
  }
}
           

不完美,但更好:

使用CSS變量簡化Apple Watch呼吸應用動畫
使用CSS變量簡化Apple Watch呼吸應用動畫

使用

@supports

opacity

可以解決Edge( live demo )中缺少

mix-blend-mode

支援的問題。

現在讓我們看一下我們想要得到的結果:

使用CSS變量簡化Apple Watch呼吸應用動畫
使用CSS變量簡化Apple Watch呼吸應用動畫

理想的結果。

我們總共有六個圈子,其中三個在左半邊,另外三個在右半邊。 它們的

background

都是某種綠色,左半部分偏向黃色,右半部分偏向藍色。

如果我們從右半邊的最上面的一個圓圈開始編号,然後按順時針方向進行編号,則前三個圓圈在右半邊并具有藍色綠色

background

,後三個圓圈在左半邊并帶有黃色綠色

background

至此,我們将所有圓圈的

background

設定為淡藍色。 這意味着我們需要在六個圓圈的前半部分覆寫它。 由于我們無法在選擇器中使用CSS變量,是以我們可以從Pug代碼執行此操作:

- var nc = 6; // number of circles

style .circle:nth-child(-n + #{.5*nc}) { background: #529ca0 }
.watch-face(style=`--nc: ${nc}`)
  - for(var i = 0; i < nc; i++)
    .circle(style=`--i: ${i}`)
           

如果需要對此進行重新整理,

:nth-child(-n + a)

選擇

n ≥ 0

整數值得到的有效索引處的項目。 在我們的例子中,

a = .5*nc = .5*6 = 3

,是以我們的選擇器是

:nth-child(-n + 3)

如果将

n

替換為

,則得到

3

,這是一個有效的索引,是以我們的選擇器比對第三個圓。

如果将

n

替換為

1

,則得到

2

,也是一個有效的索引,是以我們的選擇器比對第二個圓。

如果将

n

替換為

2

,則得到

1

,再次有效,是以我們的選擇器比對第一個圓。

如果将

n

替換為

3

,則會得到

,這不是有效的索引,因為此處的索引不是基于

的。 在這一點上,我們停止了,因為很明顯,如果繼續下去,我們将不會獲得任何其他正值。

下面的Pen示範了它是如何工作的-一般規則是

:nth-child(-n + a)

選擇第一個

a

項目:

見筆由thebabydino( @thebabydino )上CodePen 。

回到循環分布,到目前為止的結果如下:

見筆由thebabydino( @thebabydino )上CodePen 。

定位

首先,我們使包裝器相對定位,并使其

.circle

子代絕對定位。 現在它們全部重疊在中間。

見筆由thebabydino( @thebabydino )上CodePen 。

為了了解下一步需要做什麼,讓我們看一下下圖:

使用CSS變量簡化Apple Watch呼吸應用動畫
使用CSS變量簡化Apple Watch呼吸應用動畫

最右邊的圓圈從其初始位置到最終位置( live )。

初始位置中的圓的中心點在同一水準線上,并且半徑與最右邊的圓相距。 這意味着我們可以通過沿x軸平移半徑

$r

來到達此最終位置。

但是其他圈子呢? 它們在最終位置的中心點也僅沿着其他直線偏離其初始位置的半徑。

使用CSS變量簡化Apple Watch呼吸應用動畫
使用CSS變量簡化Apple Watch呼吸應用動畫

所有圓:初始位置(中間的死點)是每個最後一個圓( 活着 )的半徑。

這意味着,如果我們首先旋轉它們的坐标系,直到它們的x軸與中心點的初始位置和最終位置之間的線重合,然後平移它們的半徑,我們就可以使它們全部位于正确的最終位置。非常相似的方式。

見筆由thebabydino( @thebabydino )上CodePen 。

好的,但是将它們每個旋轉一個角度?

好吧,我們從這樣一個事實開始:圍繞一個點在圓周上有

360°

見筆由thebabydino( @thebabydino )上CodePen 。

我們有六個均勻分布的圓,是以任何兩個連續的圓之間的旋轉差為

360°/6 = 60°

。 由于我們不需要旋轉最右邊的

.circle

(第二個

.circle

),是以該

.circle

,這會将前面的第一個

.circle

(第一個)置于

-60°

,将後面的

.circle

(第二個)置于

60°

等等。

見筆由thebabydino( @thebabydino )上CodePen 。

請注意,

-60°

300° = 360° - 60°

-60°

在圓上占據相同的位置,是以我們是通過順時針(正)旋轉

300°

還是以

60°

繞圓的另一種方式到達那裡(給我們減号)沒關系。 我們将在代碼中使用

-60°

選項,因為在我們的案例中,它可以更輕松地找到友善的圖案。

是以,我們的轉換如下所示:

.circle {
  &:nth-child(1 /* = 0 + 1 */) {
    transform: rotate(-60deg /* -1·60° = (0 - 1)·360°/6 */) translate($r);
  }
  &:nth-child(2 /* = 1 + 1 */) {
    transform: rotate(  0deg /*  0·60° = (1 - 1)·360°/6 */) translate($r);
  }
  &:nth-child(3 /* = 2 + 1 */) {
    transform: rotate( 60deg /*  1·60° = (2 - 1)·360°/6 */) translate($r);
  }
  &:nth-child(4 /* = 3 + 1 */) {
    transform: rotate(120deg /*  2·60° = (3 - 1)·360°/6 */) translate($r);
  }
  &:nth-child(5 /* = 4 + 1 */) {
    transform: rotate(180deg /*  3·60° = (4 - 1)·360°/6 */) translate($r);
  }
  &:nth-child(6 /* = 5 + 1 */) {
    transform: rotate(240deg /*  4·60° = (5 - 1)·360°/6 */) translate($r);
  }
}
           

這為我們提供了我們一直追求的分布:

見筆由thebabydino( @thebabydino )上CodePen 。

但是,它是非常重複的代碼,可以很容易地壓縮。 對于它們中的任何一個,旋轉角度都可以寫為目前索引和項總數的函數:

.circle {
  /* previous styles */
  
  transform: rotate(calc((var(--i) - 1)*360deg/var(--nc))) translate($r);
}
           

這在WebKit浏覽器和Firefox 57+中有效 ,但在Edge和較舊的Firefox浏覽器中失敗,因為缺少對在

rotate()

函數中使用

calc()

的支援。

幸運的是,在這種情況下,我們可以選擇在Pug代碼中計算和設定各個旋轉角度,然後在Sass代碼中使用它們:

- var nc = 6, ba = 360/nc;

style .circle:nth-child(-n + #{.5*nc}) { background: #529ca0 }
.watch-face
  - for(var i = 0; i < nc; i++)
    .circle(style=`--ca: ${(i - 1)*ba}deg`)
           
.circle {
  /* previous styles */
  
  transform: rotate(var(--ca)) translate($r);
}
           

在這種情況下,我們實際上并不需要其他任何以前的自定義屬性,是以我們擺脫了它們。

現在,我們擁有一個緊湊的代碼,跨浏覽器的發行版:

見筆由thebabydino( @thebabydino )上CodePen 。

好,這意味着我們已經完成了最重要的部分! 現在為絨毛...

整理起來

我們将

transform

聲明從類中取出,并将其放入一組

@keyframes

。 在該類中,我們将其替換為無翻譯用例:

.circle {
  /* same as before */
  
  transform: rotate(var(--ca))
}

@keyframes circle {
  to { transform: rotate(var(--ca)) translate($r) }
}
           

我們還将在

.watch-face

元素上添加為脈沖動畫設定的

@keyframes

集。

@keyframes pulse {
  0% { transform: scale(.15) rotate(.5turn) }
}
           

請注意,我們既不需要

0%

from

)關鍵幀,也不需要

100%

to

)關鍵幀。 每當缺少這些屬性時,它們的動畫屬性值(在我們的例子中隻是

transform

屬性)是從我們在沒有

animation

情況下對

animation

元素擁有的值生成的。

circle

動畫的情況下,這就是

rotate(var(--ca))

。 在

pulse

動畫情況下,

scale(1)

給我們的矩陣與

none

相同,這是

transform

的預設值,是以我們甚至不需要在

.watch-face

元素上進行設定。

我們将

animation-duration

設為Sass變量,這樣,如果我們想對其進行更改,則隻需要在一個位置進行更改即可。 最後,我們在

.watch-face

元素和

.circle

元素上都設定了

animation

屬性。

$t: 4s;

.watch-face {
  position: relative;
  animation: pulse $t cubic-bezier(.5, 0, .5, 1) infinite alternate
}

.circle {
  /* same as before */
  
  animation: circle $t infinite alternate
}
           

請注意,我們沒有為

circle

動畫設定計時功能。 在原始示範中這很

ease

,我們沒有明确設定它,因為它是預設值。

就是這樣–我們得到了動畫效果 !

我們還可以調整平移距離,使其不完全是

$r

,而是稍小的值(例如

.95*$r

)。 這也可以使

mix-blend-mode

效果更加有趣:

見筆由thebabydino( @thebabydino )上CodePen 。

獎金:一般情況!

以上特别是針對六個

.circle

花瓣。 現在,我們将了解如何對其進行調整,使其适用于任意數量的花瓣。 等等,我們是否需要做更多的事情,而不隻是更改Pug代碼中的圓形元素數量?

好吧,讓我們看看如果這樣做的話會發生什麼:

使用CSS變量簡化Apple Watch呼吸應用動畫
使用CSS變量簡化Apple Watch呼吸應用動畫

nc

的結果等于

6

(左),

8

(中)和

9

(右)。

結果看起來不錯,但它們并未完全遵循相同的模式-圓的上半部分(帶藍色的綠色)位于垂直對稱線的右側,下半部分(帶黃色的綠色)位于左側。

nc = 8

情況下,我們非常接近,但是對稱線不是垂直的。 但是,在

nc = 9

情況下,我們所有的圓圈都有淡黃色的綠色

background

是以,讓我們看看為什麼會發生這些事情以及如何獲得我們真正想要的結果。

使

:nth-child()

為我們工作

首先,請記住,使用以下代碼,我們使一半的圓具有藍綠色

background

.circle:nth-child(-n + #{.5*nc}) { background: #529ca0 }
           

但是在

nc = 9

情況下,我們有

.5*nc = .5*9 = 4.5

,這使我們的選擇器為

:nth-child(-n + 4.5)

。 由于

4.5

不是整數,是以選擇器無效,并且不會應用

background

。 是以,我們在這裡要做的第一件事是設定

.5*nc

值的下限:

style .circle:nth-child(-n + #{~~(.5*nc)}) { background: #529ca0 }
           

更好的是,對于

nc

值為

9

,我們得到的選擇器是

.circle:nth-child(-n + 4)

,這使我們獲得前

4

在其上應用帶藍色綠色

background

項目:

見筆由thebabydino( @thebabydino )上CodePen 。

但是,如果

nc

為奇數,我們仍然沒有相同數量的藍綠色和黃綠色綠色圓圈。 為了解決這個問題,我們使中間的圓(從第一個到最後一個)具有漸變

background

所謂“中間的圓”是指距起點和終點相等數量的圓。 以下互動式示範說明了這一點,以及以下事實:當圓圈總數為偶數時,我們沒有中間圓圈。

見筆由thebabydino( @thebabydino )上CodePen 。

好吧,我們如何得到這個圈子?

從數學上講,這是包含第一個

ceil(.5*nc)

項的集合與包含除第一

floor(.5*nc)

項之外的所有項的集合之間的交集。 如果

nc

為偶數,則

floor(.5*nc)

ceil(.5*nc)

相等,并且我們的交集為空集

。 下面的鋼筆對此進行了說明:

見筆由thebabydino( @thebabydino )上CodePen 。

我們使用

:nth-child(-n + #{Math.ceil(.5*nc)})

獲得第一個

ceil(.5*nc)

項,但是另一組呢?

通常,

:nth-child(n + a)

選擇除第

a - 1

項之外的所有項:

見筆由thebabydino( @thebabydino )上CodePen 。

是以,為了獲得除第一

floor(.5*nc)

項目,我們使用

:nth-child(n + #{~~(.5*nc) + 1})

這意味着我們為中間圓具有以下選擇器:

:nth-child(n + #{~~(.5*nc) + 1}):nth-child(-n + #{Math.ceil(.5*nc)})
           

讓我們看看這能給我們帶來什麼。

  • 如果我們有

    3

    項目,則選擇器為

    :nth-child(n + 2):nth-child(-n + 2)

    ,它使我們獲得第二個項目(

    {2, 3, 4, ...}

    {2, 1}

    集)
  • 如果我們有

    4

    項目,則選擇器為

    :nth-child(n + 3):nth-child(-n + 2)

    ,它什麼也沒捕獲(

    {3, 4, 5, ...}

    {2, 1}

    集是空集

  • 如果我們有

    5

    項目,則選擇器為

    :nth-child(n + 3):nth-child(-n + 3)

    ,它使我們獲得第三個項目(

    {3, 4, 5, ...}

    {3, 2, 1}

    集)
  • 如果我們有

    6

    項目,那麼我們的選擇器是

    :nth-child(n + 4):nth-child(-n + 3)

    ,它什麼都不會捕獲(

    {4, 5, 6, ...}

    {3, 2, 1}

    {3, 2, 1}

    集是空集

  • 如果我們有

    7

    項目,則選擇器為

    :nth-child(n + 4):nth-child(-n + 4)

    ,它使我們獲得第四個項目(

    {4, 5, 6, ...}

    {4, 3, 2, 1}

    集)
  • 如果我們有

    8

    項目,那麼我們的選擇器是

    :nth-child(n + 5):nth-child(-n + 4)

    ,它什麼都不會捕獲(

    {5, 6, 7, ...}

    {4, 3, 2, 1}

    {4, 3, 2, 1}

    集為空集

  • 如果我們有

    9

    項目,則選擇器為

    :nth-child(n + 5):nth-child(-n + 5)

    ,它使我們獲得第五個項目(

    {5, 6, 7, ...}

    {5, 4, 3, 2, 1}

    集)

現在,當我們總共有奇數個項目時,我們可以在中間選擇該項目,讓我們給它一個漸變

background

- var nc = 6, ba = 360/nc;

style .circle:nth-child(-n + #{~~(.5*nc)}) { background: var(--c0) }
  | .circle:nth-child(n + #{~~(.5*nc) + 1}):nth-child(-n + #{Math.ceil(.5*nc)}) {
  |   background: linear-gradient(var(--c0), var(--c1))
  | }
.watch-face(style=`--c0: #529ca0; --c1: #61bea2`)
  - for(var i = 0; i < nc; i++)
    .circle(style=`--ca: ${(i - 1)*ba}deg`)
           

我們使用從上到下的漸變的原因是,最終,我們希望該項目位于底部,并通過裝配體的垂直對稱線分成兩半。 這意味着我們首先需要旋轉它直到其x軸指向下,然後沿着其x軸的新方向向下平移。 在此位置,項目的頂部在元件的右半部分,而項目的底部在元件的左半部分。 是以,如果我們想要從元件的右側到元件的左側的漸變,則這是該實際

.circle

元素上的從上到下的漸變。

見筆由thebabydino( @thebabydino )上CodePen 。

使用這種技術,我們現在已經解決了一般情況的背景問題:

見筆由thebabydino( @thebabydino )上CodePen 。

現在剩下要做的就是使對稱軸垂直。

馴服角度

為了檢視我們在這裡需要做的事情,讓我們集中在頂部的期望位置。 在那裡,我們始終要有兩個圓(相對于垂直軸對稱放置)(第一個圓環在DOM順序中,第一個圓環在DOM順序中在左邊),這兩個圓将我們的裝配體分成兩個互相鏡像的兩半。

見筆由thebabydino( @thebabydino )上CodePen 。

它們是對稱的事實意味着垂直軸将它們之間的角距離

ba

360°

除以總圓

nc

)分為兩個相等的一半。

使用CSS變量簡化Apple Watch呼吸應用動畫
使用CSS變量簡化Apple Watch呼吸應用動畫

垂直對稱線和徑向線與頂角的中心點所形成的角度都等于底角( live )的一半。

是以,兩者都是遠離垂直對稱軸的底角的一半(底角

ba

360°

除以圓的總數

nc

),一個是順時針方向,另一個是相反方向。

對稱軸的上半部分為

-90°

(等于

270°

)。

使用CSS變量簡化Apple Watch呼吸應用動畫
使用CSS變量簡化Apple Watch呼吸應用動畫

圍繞圓的度數值( 實時 )。

是以,為了到達DOM順序的第一個圓(在右側頂部的圓),我們從

開始,在負方向上

90°

,然後在正方向上旋轉一個底角(順時針)。 這會将第一個圓置于

.5*ba - 90

度。

使用CSS變量簡化Apple Watch呼吸應用動畫
使用CSS變量簡化Apple Watch呼吸應用動畫

如何擷取第一個圓在( live )處的角度。

之後,每隔一個圓與前一個圓的角度加一個底角。 這樣,我們有:

  • 第一個圓(索引 ,選擇器

    :nth-child(1)

    )位于

    ca₀ = .5*ba - 90

  • 第二個圓(索引

    1

    ,選擇器

    :nth-child(2)

    )處于

    ca₁ = ca₀ + ba = ca₀ + 1*ba

  • 第三個圓(索引

    2

    ,選擇器

    :nth-child(3)

    u)處于

    ca₂ = ca₁ + ba = ca₀ + ba + ba = ca₀ + 2*ba

  • 通常,索引

    k

    的圓為

    caₖ = caₖ₋₁ + ba = ca₀ + k*ba

是以,目前在索引

i

處的

.5*ba - 90 + i*ba = (i + .5)*ba - 90

.5*ba - 90 + i*ba = (i + .5)*ba - 90

度:

- var nc = 6, ba = 360/nc;

//- same as before
.watch-face(style=`--c0: #529ca0; --c1: #61bea2`)
  - for(var i = 0; i < nc; i++)
    .circle(style=`--ca: ${(i + .5)*ba - 90}deg`)
           

這給出了最終的Pen,在這裡我們隻需要從Pu​​g代碼中更改

nc

即可更改結果:

見筆由thebabydino( @thebabydino )上CodePen 。

翻譯自: https://css-tricks.com/simplifying-apple-watch-breathe-app-animation-css-variables/