華麗炫酷的動畫特效總能夠讓人心曠神怡,不能自已。艷羡之餘,如果還能夠探究其華麗外表下的實現邏輯,那就是百尺竿頭,更上一步了。本次我們使用圖片、SCSS樣式以及SVG圖片動畫來實現“點贊”按鈕的動畫特效,並比較不同之處。 圖片實現 最簡單,也最容易理解的實現方式就是使用圖片。曾幾何時,幾乎所有前端特效 ...
華麗炫酷的動畫特效總能夠讓人心曠神怡,不能自已。艷羡之餘,如果還能夠探究其華麗外表下的實現邏輯,那就是百尺竿頭,更上一步了。本次我們使用圖片、SCSS樣式以及SVG圖片動畫來實現“點贊”按鈕的動畫特效,並比較不同之處。
圖片實現
最簡單,也最容易理解的實現方式就是使用圖片。曾幾何時,幾乎所有前端特效都需要藉助圖片來完成。
實現原理很簡單,通過不同的關鍵幀來“拼接”一段完整的動畫影片,每一幀即該動畫的每一個瞬間“狀態”。
首先聲明必要的盒子模型:
<div class="heart"></div>
這裡以div為例子,聲明偽類對象heart。
隨後通過樣式對heart偽類進行控制:
.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%;
}
.heart:hover {
background-position:right;
}
.is_animating {
animation: heart-burst .8s steps(28) 1;
}
@keyframes heart-burst {
from {background-position:left;}
to { background-position:right;}
}
這裡指定背景圖片位置,只顯示最左側的背景元素,滑鼠懸停時則只顯示最右側背景元素,然後當滑鼠點擊div時,觸發@keyframes heart-burst動畫,從左側移動至右側,一共進行28幀的側移動作:
這樣就完成了一段流暢的動畫特效:
See the Pen <a href="https://codepen.io/v3ucom/pen/yLjNERX"> Untitled</a> by 劉悅的技術博客 (<a href="https://codepen.io/v3ucom">@v3ucom</a>) on <a href="https://codepen.io">CodePen</a>.
需要註意的是,這裡需要藉助JavaScript綁定單擊事件,所以需要引入zepto.min.js文件
https://cdnjs.cloudflare.com/ajax/libs/zepto/1.2.0/zepto.min.js
通過zepto.min.js庫就可以很方便的操作頁面節點:
$(".heart").on('click touchstart', function(){
$(this).toggleClass('is_animating');
});
$(".heart").on('animationend', function(){
$(this).toggleClass('is_animating');
});
邏輯是單擊事件觸發動畫,動畫執行過程中再次點擊則移除動畫效果。
此種方式依賴多幀背景圖、CSS動畫以及JavaScript事件綁定,顯然成本有些高,同時多幀圖片平鋪導致體積過大,也會占用系統帶寬,得不償失。
純SCSS(樣式)實現
使用純CSS樣式也可以完成這樣的特效,但CSS3原生語法太過複雜,這裡我們使用SCSS語法,SCSS是 CSS3的超集,在保證相容性的前提下,允許使用變數、 嵌套、 混合、導入等特性, 在編寫邏輯複雜的CSS代碼時,可以簡化邏輯,提高CSS的代碼可讀性。
首先還是創建基本盒子模型:
<input id="toggle-heart" type="checkbox"/>
<label for="toggle-heart">❤</label>
這裡通過覆選框和標簽元素來控制點贊按鈕的狀態。
隨後編寫SCSS邏輯:
$bubble-d: 4.5rem; // bubble diameter
$bubble-r: .5*$bubble-d; // bubble-radius
@mixin bubble($ext) {
transform: scale(1);
border-color: #cc8ef5;
border-width: $ext;
}
body {
display: flex;
justify-content: center;
margin: 0;
height: 100vh;
}
[id='toggle-heart'] {
position: absolute;
left: -100vw;
&:checked + label {
color: #e2264d;
will-change: font-size;
animation: heart 1s cubic-bezier(.17, .89, .32, 1.49);
&:before, &:after {
animation: inherit;
animation-timing-function: cubic-bezier(.21, .61, .35, 1);
}
&:before {
will-change: transform, border-width, border-color;
animation-name: bubble;
}
}
}
[for='toggle-heart'] {
align-self: center;
position: relative;
color: #aab8c2;
font-size: 2em;
cursor: pointer;
&:before, &:after {
position: absolute;
z-index: -1;
top: 50%; left: 50%;
border-radius: 50%;
content: '';
}
&:before {
box-sizing: border-box;
margin: -$bubble-r;
border: solid $bubble-r #e2264d;
width: $bubble-d; height: $bubble-d;
transform: scale(0);
}
}
@keyframes heart {
0%, 17.5% { font-size: 0; }
}
@keyframes bubble {
15% { @include bubble($bubble-r); }
30%, 100% { @include bubble(0); }
}
這裡首先將覆選框按鈕移出界面,隨後定義bubble對象,這裡bubble指的是點擊後膨脹的紫色(#cc8ef5)泡泡,直徑是4.5rem,並且圓角修飾:$bubble-r: .5*$bubble-d
隨後通過id選擇器toggle-heart狀態判斷元素狀態,觸發heart和bubble動畫,在一秒內動態的改變heart元素以及bubble元素的位置、大小以及邊框透明度,從而完成動態特效。
接著添加煙花特效:
body {
display: flex;
justify-content: center;
margin: 0;
height: 100vh;
background: linear-gradient(135deg, #121721, #000);
font: 1em verdana, sans-serif;
}
[id='toggle-heart'] {
position: absolute;
left: -100vw;
}
[id='toggle-heart']:checked + label {
color: #e2264d;
filter: none;
will-change: font-size;
-webkit-animation: heart 1s cubic-bezier(0.17, 0.89, 0.32, 1.49);
animation: heart 1s cubic-bezier(0.17, 0.89, 0.32, 1.49);
}
[id='toggle-heart']:checked + label:before, [id='toggle-heart']:checked + label:after {
-webkit-animation: inherit;
animation: inherit;
-webkit-animation-timing-function: ease-out;
animation-timing-function: ease-out;
}
[id='toggle-heart']:checked + label:before {
will-change: transform, border-width, border-color;
-webkit-animation-name: bubble;
animation-name: bubble;
}
[id='toggle-heart']:checked + label:after {
will-change: opacity, box-shadow;
-webkit-animation-name: sparkles;
animation-name: sparkles;
}
[id='toggle-heart']:focus + label {
text-shadow: 0 0 3px white, 0 1px 1px white, 0 -1px 1px white, 1px 0 1px white, -1px 0 1px white;
}
[for='toggle-heart'] {
align-self: center;
position: relative;
color: #888;
font-size: 2em;
filter: grayscale(1);
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
cursor: pointer;
}
[for='toggle-heart']:before, [for='toggle-heart']:after {
position: absolute;
z-index: -1;
top: 50%;
left: 50%;
border-radius: 50%;
content: '';
}
[for='toggle-heart']:before {
box-sizing: border-box;
margin: -2.25rem;
border: solid 2.25rem #e2264d;
width: 4.5rem;
height: 4.5rem;
transform: scale(0);
}
[for='toggle-heart']:after {
margin: -0.1875rem;
width: 0.375rem;
height: 0.375rem;
box-shadow: 0.32476rem -3rem 0 -0.1875rem #ff8080, -0.32476rem -2.625rem 0 -0.1875rem #ffed80, 2.54798rem -1.61656rem 0 -0.1875rem #ffed80, 1.84982rem -1.89057rem 0 -0.1875rem #a4ff80, 2.85252rem 0.98418rem 0 -0.1875rem #a4ff80, 2.63145rem 0.2675rem 0 -0.1875rem #80ffc8, 1.00905rem 2.84381rem 0 -0.1875rem #80ffc8, 1.43154rem 2.22414rem 0 -0.1875rem #80c8ff, -1.59425rem 2.562rem 0 -0.1875rem #80c8ff, -0.84635rem 2.50595rem 0 -0.1875rem #a480ff, -2.99705rem 0.35095rem 0 -0.1875rem #a480ff, -2.48692rem 0.90073rem 0 -0.1875rem #ff80ed, -2.14301rem -2.12438rem 0 -0.1875rem #ff80ed, -2.25479rem -1.38275rem 0 -0.1875rem #ff8080;
}
@-webkit-keyframes heart {
0%, 17.5% {
font-size: 0;
}
}
@keyframes heart {
0%, 17.5% {
font-size: 0;
}
}
@-webkit-keyframes bubble {
15% {
transform: scale(1);
border-color: #cc8ef5;
border-width: 2.25rem;
}
30%, 100% {
transform: scale(1);
border-color: #cc8ef5;
border-width: 0;
}
}
@keyframes bubble {
15% {
transform: scale(1);
border-color: #cc8ef5;
border-width: 2.25rem;
}
30%, 100% {
transform: scale(1);
border-color: #cc8ef5;
border-width: 0;
}
}
@-webkit-keyframes sparkles {
0%, 20% {
opacity: 0;
}
25% {
opacity: 1;
box-shadow: 0.32476rem -2.4375rem 0 0rem #ff8080, -0.32476rem -2.0625rem 0 0rem #ffed80, 2.1082rem -1.26585rem 0 0rem #ffed80, 1.41004rem -1.53985rem 0 0rem #a4ff80, 2.30412rem 0.85901rem 0 0rem #a4ff80, 2.08305rem 0.14233rem 0 0rem #80ffc8, 0.76499rem 2.33702rem 0 0rem #80ffc8, 1.18748rem 1.71734rem 0 0rem #80c8ff, -1.35019rem 2.0552rem 0 0rem #80c8ff, -0.60229rem 1.99916rem 0 0rem #a480ff, -2.44865rem 0.22578rem 0 0rem #a480ff, -1.93852rem 0.77557rem 0 0rem #ff80ed, -1.70323rem -1.77366rem 0 0rem #ff80ed, -1.81501rem -1.03204rem 0 0rem #ff8080;
}
}
@keyframes sparkles {
0%, 20% {
opacity: 0;
}
25% {
opacity: 1;
box-shadow: 0.32476rem -2.4375rem 0 0rem #ff8080, -0.32476rem -2.0625rem 0 0rem #ffed80, 2.1082rem -1.26585rem 0 0rem #ffed80, 1.41004rem -1.53985rem 0 0rem #a4ff80, 2.30412rem 0.85901rem 0 0rem #a4ff80, 2.08305rem 0.14233rem 0 0rem #80ffc8, 0.76499rem 2.33702rem 0 0rem #80ffc8, 1.18748rem 1.71734rem 0 0rem #80c8ff, -1.35019rem 2.0552rem 0 0rem #80c8ff, -0.60229rem 1.99916rem 0 0rem #a480ff, -2.44865rem 0.22578rem 0 0rem #a480ff, -1.93852rem 0.77557rem 0 0rem #ff80ed, -1.70323rem -1.77366rem 0 0rem #ff80ed, -1.81501rem -1.03204rem 0 0rem #ff8080;
}
}
新增sparkles對象,通過動態的控制元素的彩色陰影來表現點擊後的煙花動態效果:
See the Pen <a href="https://codepen.io/v3ucom/pen/eYrNLWY"> Untitled</a> by 劉悅的技術博客 (<a href="https://codepen.io/v3ucom">@v3ucom</a>) on <a href="https://codepen.io">CodePen</a>.
這裡為了增加效果對比度,將背景設置為深色,同時為點贊按鈕增加亮色邊框:
[id='toggle-heart']:focus + label {
text-shadow:
0 0 3px #fff,
0 1px 1px #fff, 0 -1px 1px #fff,
1px 0 1px #fff, -1px 0 1px #fff;
}
大體上,利用transform屬性控制元素的絕對定位、顏色、邊框以及盒子模型陰影來完成此種特效,帶寬資源占用層面,明顯比圖片更具優勢。
SVG實現
SVG是矢量圖形,不受像素的影響,從而使得它在不同的平臺或者媒體下表現出的相容性更好,與此同時,SVG對動畫的支持較好,其DOM結構可以被其特定語法或者CSS控制,從而輕鬆的實現動畫效果。
首先依然聲明盒子模型:
<label class="like">
<input type="checkbox"/>
<div class="hearth"/>
</label>
隨後定義樣式:
:root {
--size: 100px;
--frames: 62;
}
html {
background-color: #15202B;
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
user-select: none;
}
input {
display: none;
}
.like {
display: block;
width: var(--size);
height: var(--size);
cursor: pointer;
border-radius: 999px;
overflow: visible;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
-webkit-tap-highlight-color: transparent;
}
.hearth {
background-image: url('https://assets.codepen.io/23500/Hashflag-AppleEvent.svg');
background-size: calc(var(--size) * var(--frames)) var(--size);
background-repeat: no-repeat;
background-position-x: calc(var(--size) * (var(--frames) * -1 + 2));
background-position-y: calc(var(--size) * 0.02);
width: var(--size);
height: var(--size);
}
input:checked + .hearth {
animation: like 1s steps(calc(var(--frames) - 3));
animation-fill-mode: forwards;
}
@keyframes like {
0% {
background-position-x: 0;
}
100% {
background-position-x: calc(var(--size) * (var(--frames) * -1 + 3));
}
}
@media (hover: hover) {
.like:hover {
background-color: #E1255E15;
.hearth {
background-position-x: calc(var(--size) * (var(--frames) * -1 + 1));
}
}
}
和普通圖片如出一轍,通過background-image來控制背景SVG圖片的順序,對背景的橫坐標進行側移動畫操作,只不過幀數提高到62幀:
See the Pen <a href="https://codepen.io/v3ucom/pen/MWGwXPv"> Untitled</a> by 劉悅的技術博客 (<a href="https://codepen.io/v3ucom">@v3ucom</a>) on <a href="https://codepen.io">CodePen</a>.
可以看到這裡通過input:checked狀態就可以觸發@keyframes like橫移動畫,並不需要單獨撰寫JavaScript邏輯。
結語
三種動畫特效實現方式各有千秋,但從可維護性以及成本控制角度上看,SCSS顯然是最優解。