Twitter "like" 動畫實戰

原文1地址:Twitter』s Heart Animation in Full CSS

原文2地址:How Did They Do That? The Twitter 「Like」 Animation.

譯者的話:Twitter 的 "贊" 效果相信不少人已經發現了,Medium 上有兩位開發者模擬了這個效果。今天這篇文章是那兩位開發者文章的譯文綜合,文中間或夾雜譯者對實現方式的思考,請輕拍。

不知道你有沒有留意到,Twitter 已經不再使用星星動畫做「收藏」的效果,現在用的是愛心版的「贊」。不管大家對此有何評價,今天我們要來模擬的就是這個效果了。

【gif在這】

Chris Mabry 通過更改 CSS 雪碧圖 background-position 的值實現了這個動畫;Nicolas Escoffier 是用 SASS 實現,不過也參考了雪碧圖的方案。現在先讓我們看看雪碧圖的方式吧。

雪碧圖動畫的原理來自遊戲領域,通常譯者稱之為定格動畫,因為是一幀一幀變化的。讀者也可以套用走馬燈的原理。

為什麼不直接使用單張圖片呢?為了減少請求數。伺服器端和客戶端之間的傳輸成本很高,如果你的產品有320萬活躍用戶,即使只是減少幾個請求數,性能也能得到非常大的優化。「贊」動畫被我們分成了29幀,用雪碧圖的方式可以將請求數從29減少到1。

所以我們要先合成出一張長長長的雪碧圖:

現在來個三步走,就能實現這個效果了:

  1. 有一個 div放愛心,這個 div的背景圖就是上面那張雪碧圖。

  2. 構建一個 keyframe 執行 background-position 動畫,讓背景圖的位置從左移動到右。

  3. 當用戶點擊的時候,觸發這個動畫。

首先,要有一個 div:

<div class=」heart」></div>

還要有對應的樣式:

.heart { cursor: pointer; height: 50px; width: 50px; background-image:url("https://abs.twimg.com/a/1446542199/img/t1/web_heart_animation.png"); background-position: left; background-repeat:no-repeat; background-size:2900%;}

我們給一個 div 定了寬高;並將它的背景圖設置為上面那張很長的雪碧圖;然後將它的 background-position 固定到雪碧圖的最左邊(動畫的起始幀)background-size 設定為 2900%,這樣可以保證雪碧圖可以完全佔滿這個 div。不要忘了把 cursor 設置成可點擊的。

接著就可以看到一個愛心圖案:

用簡單粗暴的方法讓這顆愛心動起來的方法:

@keyframes heart-burst { from {background-position:left;} to { background-position:right;}}

這個關鍵幀的名字叫做 heart-burst,它要執行的動畫,就是將 background-positionleft變成 right

注意:動畫需要添加瀏覽器前綴,保證兼容性,兼容情況請看這裡

把這個動畫效果加到剛剛的 div上吧:

【gif在這】

我知道現在的動畫看著很奇怪,因為瀏覽器在執行序列幀動畫,還記得愛心的 div 背景圖是一張很長的雪碧圖吧,每一次我們都只能看到這個雪碧圖的一部分。就好像從一個窗口往一個世界看,只能看到世界的一部分。序列幀動畫就像窗口不動,背後的世界在很流暢地流動。所以你會看到流動的整個過程。

現在已經很接近最終效果咯。不過還需要 steps() 來調整一下,可以通過這個函數將序列幀動畫變成定格動畫:

.is_animating { animation: heart-burst .8s steps(28) 1;}

雪碧圖上有29幀,所以要切換28次,也就是 steps(28),動畫耗時800毫秒:

【gif在這】

別忘了在用戶點擊愛心的時候,才會看到動畫效果,所以我們還要綁定事件,這就交給 jQuery 了:

$(.heart).on(click, function(){ $(this).toggleClass(is_animating);});$(.heart).on(animationend, function(){ $(this).toggleClass(is_animating);});

用戶點擊愛心之後,通過切換is_animating class,來切換動畫。

我們還加上了監聽 animationed事件的代碼,當動畫執行完畢後,div上的 is_animating會被移除。當我們再次點擊愛心的時候,才能再看到動畫。

最後的最後,要保證體驗的完整,不要忘了加上 hover態時的樣式:

.heart:hover { background-position:right;}

Done!

【gif在這】

可以從這裡看到完整的代碼,或者用開發者工具直接上 Twitter 看。

------------------ 不知道怎麼轉場, 就隨便割一下------------------------

現在讓我們來看看 Nicolas Escoffier 的實現方法,這個方法很巧妙。他用 Sass 畫出了這顆愛心,Escoffier 分析出這個動畫分成了:愛心、環形和圓形,三個部分,在動畫執行的過程中,這三個部分要不斷發生變形,也就是不斷計算。Escoffier 編寫了一些 Sass 函數保證自動化的計算。

-------------------- 以下是譯文開始 ----------------------

首先,我把這個效果分成了三個層級:愛心(.heart)、環形(.ring)以及圓形(.circles),接著將它們三個都放進 .heart-wrapper 這個容器中。然後分別繪製每個層級,接著實現對應的動畫,最後把所有動畫整合到一起。

愛心

我把整個形狀分成了四塊矩形區域:

  • 左上和右上區域都是佔總高度的 25%,總寬度的 50%

  • 左下和右下區域是佔總高度的 75%,總寬度的 50%

接著在每個矩形結構中,我都使用上設置了 border-radius 值的偽元素:after,儘可能地模擬每個對應部分的形狀。

更改 color,並將 overflow設置為 hidden

環形

通過設置不同的 border-sizewidthheight就能畫出各種各樣的環形了。

圓形

將一個透明的圓形元素居中,然後給它加上陰影(shadow-box)。

圓形的陰影值,用逗號分隔。坐標值可以通過圓規和三角函數算出來:

愛心動畫

通過增減「愛心」元素的寬高比,並相應調整元素的 lefttop值,同時,不要忘了校正其他和「愛心」元素又相對位置關係的元素位置。

環形動畫

調整 border的大小以及其中的圓形的尺寸,並相應調整它們的位置和顏色。

圓形動畫

這裡用到的方法比較有技巧性,box-shadow 的值要跟著元素的坐標值、元素的大小還有顏色同時變化。

比如:

51.85185% { box-shadow: -8.48528em -8.48528em 0 -0.83333em #a068ce, -8.38671em -5.44639em 0 -0.83333em #b752e1, 1.34357em -11.92455em 0 -0.83333em #99e9c8, -0.97087em -9.95276em 0 -0.83333em #bae3d7, 10.16069em -6.38438em 0 -0.83333em #d3f491, 7.17606em -6.9645em 0 -0.83333em #dce483, 11.3266em 3.96335em 0 -0.83333em #59c392, 9.91926em 1.26817em 0 -0.83333em #67cd9f, 3.96335em 11.3266em 0 -0.83333em #caadc7, 5.19306em 8.54588em 0 -0.83333em #959ff3, -6.38438em 10.16069em 0 -0.83333em #ca5ed8, -3.44362em 9.38837em 0 -0.83333em #a975d1, -11.92455em 1.34357em 0 -0.83333em #c35dd1, -9.48718em 3.16122em 0 -0.83333em #90e0be;}

上面那段僅僅是某次變化後的值……

為了方便閱讀和更改,我寫了一個 Sass 函數來處理它:

@function setBoxShadow($distance1, $distance2, $size1, $size2, $shiftAngle, $colorRatio) { $boxS: (); @for $i from 1 through length($circles) { $circle: nth($circles, $i); $order: $i - 1; $angle1: ($order * $angleBetweenCircles) + $shiftAngleBeginning; $angle2: $angle1 + $shiftAngle; $distanceRatio1: $size * $distance1; $distanceRatio2: $size * $distance2; $firstCircle: map-get($circle, first); $firstCircleStart: map-get($firstCircle, start); $firstCircleEnd: map-get($firstCircle, end); $secondCircle: map-get($circle, second); $secondCircleStart: map-get($secondCircle, start); $secondCircleEnd: map-get($secondCircle, end); $boxS: append($boxS, cos($angle1) * $distanceRatio1 sin($angle1) * $distanceRatio1 0 $circleSize * $size1 mix($firstCircleStart, $firstCircleEnd, $colorRatio) ); $boxS: append($boxS, cos($angle2) * $distanceRatio2 sin($angle2) * $distanceRatio2 0 $circleSize * $size2 mix($secondCircleStart, $secondCircleEnd, $colorRatio) ); } @return join($boxS, (), "comma");}

這個方法循環讀取了所有的儲存在 Sass Map 中的圓形,然後根據元素間的距離、尺寸、偏移角度以及顏色的變化程度,兩個兩個地(一大小兩個圓)更新 box-shadow 的值,這些值都會以變數的形式傳入。

後面的動畫和上文中的雪碧圖動畫大同小異,就不在這裡提了,如果想要看 Sass 版的完整代碼,請看這裡。

----------------------------------- 大寫的不知道怎麼轉場的譯者 -------------------------------

在實際項目中,譯者比較多用 background-position 的雪碧圖動畫做法,但是更改 background-position 將會引起重繪:

看了一下 Twitter 的做法:將愛心這一層用 position:absolute 的方式,脫離了文檔流,保證在執行愛心動畫的時候,重繪不會影響到下文。

Sass 的寫法,會不斷更改 border-radiusbox-sizing,雖然這個屬性貌似和 background-position 一樣,只會引起重繪,但在實際項目中,譯者發現這個屬性很容易引起性能問題(特別是安卓上)。在 Escoffier 的原文評論里,也有人建議使用 transform: scale(x) 的方式。不過 lefttop也會被一直改變……總之性能上看,這個方法完全不可取T T。

但是 Sass 的寫法還是有意思的。

歡迎關注我們的新浪微博:前端外刊評論
推薦閱讀:

TAG: | 前端外刊评论 | Twitter’sHeartAnimationinFullCSS | HowDidTheyDoThat?TheTwitter“Like”Animation | gif在这 | ChrisMabry | NicolasEscoffier | 这里 | gif在这 | gif在这 | gif在这 | 这里 | NicolasEscoffier | 这里 | 前端外刊评论 |