前端每日實戰:133# 視頻演示如何用 CSS 和 GSAP 創作有多個關鍵幀的連續動畫

圖片描述

效果預覽

按下右側的“點擊預覽”按鈕可以在當前頁面預覽,點擊鏈接可以全屏預覽。

https://codepen.io/comehope/pen/eLMKJG

可交互視頻

此視頻是可以交互的,你可以隨時暫停視頻,編輯視頻中的代碼。

請用 chrome, safari, edge 打開觀看。

https://scrimba.com/p/pEgDAM/cdDRmH9

源代碼下載

每日前端實戰系列的全部源代碼請從 github 下載:

https://github.com/comehope/front-end-daily-challenges

代碼解讀

定義 dom,容器中包含 10 個 div 子元素,每個 div 中包含 1 個 span 元素:

<figure class="container">
    <div><span></span></div>
    <div><span></span></div>
    <div><span></span></div>
    <div><span></span></div>
    <div><span></span></div>
    <div><span></span></div>
    <div><span></span></div>
    <div><span></span></div>
    <div><span></span></div>
    <div><span></span></div>
</figure>

居中顯示:

body {
    margin: 0;
    height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    background-color: lightyellow;
}

定義容器的尺寸和樣式:

.container {
    width: 400px;
    height: 400px;
    background: linear-gradient(45deg, tomato, gold);
    border-radius: 3%;
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
}

畫出容器裏的 1 個元素,它有一個外殼 div,裏面是一個白色的小方塊 span

.container {
    position: relative;
}

.container div {
    position: absolute;
    width: inherit;
    height: inherit;
    display: flex;
    align-items: center;
    justify-content: center;
}

.container div span {
    position: absolute;
    width: 40px;
    height: 40px;
    background-color: white;
}

爲容器中的元素定義下標變量,並讓元素的外殼依次旋轉,圍合成一個圓形,其中 outline 是輔助線:

.container div {
    outline: 1px dashed black;
    transform: rotate(calc((var(--n) - 1) * 36deg));
}

.container div:nth-child(1) { --n: 1; }
.container div:nth-child(2) { --n: 2; }
.container div:nth-child(3) { --n: 3; }
.container div:nth-child(4) { --n: 4; }
.container div:nth-child(5) { --n: 5; }
.container div:nth-child(6) { --n: 6; }
.container div:nth-child(7) { --n: 7; }
.container div:nth-child(8) { --n: 8; }
.container div:nth-child(9) { --n: 9; }
.container div:nth-child(10) { --n: 10; }

至此,子元素繪製完成,接下來開始寫動畫腳本。
引入 GSAP 庫:

<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/2.0.2/TweenMax.min.js"></script>

定義一個變量,代表子元素選擇器:

let elements = '.container div span';

聲明一個時間線對象:

let animation = new TimelineMax();

先設定入場方式爲由小(第1幀)變大(第2幀),其中並沒有第 2 幀的代碼,它是隱含在語義中的:

animation.from(elements, 1, {scale: 0});

讓子元素變成豎長條,向四周散開(第3幀):

animation.from(elements, 1, {scale: 0})
    .to(elements, 1, {y: '-100px', scaleX: 0.25});

讓豎長條旋轉着變成小方塊(第4幀):

animation.from(elements, 1, {scale: 0})
    .to(elements, 1, {y: '-100px', scaleX: 0.25})
    .to(elements, 1, {scaleY: 0.25, rotation: 180});

讓小方塊變成橫長條,圍成一個圓形(第5幀):

animation.from(elements, 1, {scale: 0})
    .to(elements, 1, {y: '-100px', scaleX: 0.25})
    .to(elements, 1, {scaleY: 0.25, rotation: 180})
    .to(elements, 1, {scaleX: 1});

注意,因 scrimba 在錄製過多幀時會崩潰,所以第 6 幀至第 11 幀沒有在視頻中體現。
讓圓形向內收斂,同時線條變細(第6幀):

animation.from(elements, 1, {scale: 0})
    .to(elements, 1, {y: '-100px', scaleX: 0.25})
    .to(elements, 1, {scaleY: 0.25, rotation: 180})
    .to(elements, 1, {scaleX: 1})
    .to(elements, 1, {y: '-60px', scaleY: 0.1});

讓線條向左擺動(第7幀):

animation.from(elements, 1, {scale: 0})
    .to(elements, 1, {y: '-100px', scaleX: 0.25})
    .to(elements, 1, {scaleY: 0.25, rotation: 180})
    .to(elements, 1, {scaleX: 1})
    .to(elements, 1, {y: '-60px', scaleY: 0.1})
    .to(elements, 1, {x: '-30px'});

再讓線條向右擺動(第8幀):

animation.from(elements, 1, {scale: 0})
    .to(elements, 1, {y: '-100px', scaleX: 0.25})
    .to(elements, 1, {scaleY: 0.25, rotation: 180})
    .to(elements, 1, {scaleX: 1})
    .to(elements, 1, {y: '-60px', scaleY: 0.1})
    .to(elements, 1, {x: '-30px'})
    .to(elements, 1, {x: '30px'});

再把橫線變爲豎線,造型與第 3 幀相似,只是線更細,更向內收斂(第9幀):

animation.from(elements, 1, {scale: 0})
    .to(elements, 1, {y: '-100px', scaleX: 0.25})
    .to(elements, 1, {scaleY: 0.25, rotation: 180})
    .to(elements, 1, {scaleX: 1})
    .to(elements, 1, {y: '-60px', scaleY: 0.1})
    .to(elements, 1, {x: '-30px'})
    .to(elements, 1, {x: '30px'})
    .to(elements, 1, {x: '0', scaleX: 0.1, scaleY: 1});

再把豎線變爲橫線,造型與第 5 幀相似,但線短一些(第10幀):

animation.from(elements, 1, {scale: 0})
    .to(elements, 1, {y: '-100px', scaleX: 0.25})
    .to(elements, 1, {scaleY: 0.25, rotation: 180})
    .to(elements, 1, {scaleX: 1})
    .to(elements, 1, {y: '-60px', scaleY: 0.1})
    .to(elements, 1, {x: '-30px'})
    .to(elements, 1, {x: '30px'})
    .to(elements, 1, {x: '0', scaleX: 0.1, scaleY: 1})
    .to(elements, 1, {scaleX: 0.5, scaleY: 0.1})

橫線稍向外擴散,變爲圓點(第11幀):

animation.from(elements, 1, {scale: 0})
    .to(elements, 1, {y: '-100px', scaleX: 0.25})
    .to(elements, 1, {scaleY: 0.25, rotation: 180})
    .to(elements, 1, {scaleX: 1})
    .to(elements, 1, {y: '-60px', scaleY: 0.1})
    .to(elements, 1, {x: '-30px'})
    .to(elements, 1, {x: '30px'})
    .to(elements, 1, {x: '0', scaleX: 0.1, scaleY: 1})
    .to(elements, 1, {scaleX: 0.5, scaleY: 0.1})
    .to(elements, 1, {y: '-80px', scaleY: 0.5, borderRadius: '50%'});

讓圓點變形爲豎線,並向內收縮,這個變化的距離長,所以動畫時間也要長一些(第12幀):

animation.from(elements, 1, {scale: 0})
    .to(elements, 1, {y: '-100px', scaleX: 0.25})
    .to(elements, 1, {scaleY: 0.25, rotation: 180})
    .to(elements, 1, {scaleX: 1})
    .to(elements, 1, {y: '-60px', scaleY: 0.1})
    .to(elements, 1, {x: '-30px'})
    .to(elements, 1, {x: '30px'})
    .to(elements, 1, {x: '0', scaleX: 0.1, scaleY: 1})
    .to(elements, 1, {scaleX: 0.5, scaleY: 0.1})
    .to(elements, 1, {y: '-80px', scaleY: 0.5, borderRadius: '50%'})
    .to(elements, 1, {y: '-10px', scaleX: 0.1, scaleY: 0.5, borderRadius: '0%', rotation: 0});

讓豎線從中心向外快速擴散,擴散前稍停片刻,好像線條都被髮射出一樣(第13幀):

animation.from(elements, 1, {scale: 0})
    .to(elements, 1, {y: '-100px', scaleX: 0.25})
    .to(elements, 1, {scaleY: 0.25, rotation: 180})
    .to(elements, 1, {scaleX: 1})
    .to(elements, 1, {y: '-60px', scaleY: 0.1})
    .to(elements, 1, {x: '-30px'})
    .to(elements, 1, {x: '30px'})
    .to(elements, 1, {x: '0', scaleX: 0.1, scaleY: 1})
    .to(elements, 1, {scaleX: 0.5, scaleY: 0.1})
    .to(elements, 1, {y: '-80px', scaleY: 0.5, borderRadius: '50%'})
    .to(elements, 1, {y: '-10px', scaleX: 0.1, scaleY: 0.5, borderRadius: '0%', rotation: 0})
    .to(elements, 1, {y: '-300px', delay: 0.5});

用時間尺度縮放函數讓動畫播放速度加快一倍:

animation.from(elements, 1, {scale: 0})
    .to(elements, 1, {y: '-100px', scaleX: 0.25})
    .to(elements, 1, {scaleY: 0.25, rotation: 180})
    .to(elements, 1, {scaleX: 1})
    .to(elements, 1, {y: '-60px', scaleY: 0.1})
    .to(elements, 1, {x: '-30px'})
    .to(elements, 1, {x: '30px'})
    .to(elements, 1, {x: '0', scaleX: 0.1, scaleY: 1})
    .to(elements, 1, {scaleX: 0.5, scaleY: 0.1})
    .to(elements, 1, {y: '-80px', scaleY: 0.5, borderRadius: '50%'})
    .to(elements, 1, {y: '-10px', scaleX: 0.1, scaleY: 0.5, borderRadius: '0%', rotation: 0})
    .to(elements, 1, {y: '-300px', delay: 0.5})
    .timeScale(2);

修改聲明時間線的代碼,使動畫重複播放:

let animation = new TimelineMax({repeat: -1, repeatDelay: 1});

至此,動畫完成。
隱藏容器外的內容,並刪掉輔助線;

.container {
    overflow: hidden;
}

.container div {
    /* outline: 1px dashed black; */
}

最後,裝飾一下頁面的角落:

body {
    overflow: hidden;
}

body::before,
body::after {
    content: '';
    position: absolute;
    width: 60vmin;
    height: 60vmin;
    border-radius: 50%;
    background: radial-gradient(
        transparent 25%,
        gold 25%, gold 50%,
        tomato 50%
    );
}

body::before {
    left: -30vmin;
    bottom: -30vmin;
}

body::after {
    right: -30vmin;
    top: -30vmin;
}

大功告成!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章