
本文翻自【互動網頁程式教學】用 GSAP 製作直播互動動態效果,若是對文章內容有疑問,或是想要老闆手把手帶你飛,都可以觀看影片跟著動手做,也附上這次成品。
這次要帶大家使用 vue.js, vue-cli, gsap 來模擬 facebook 手機版的直播畫面,首先將視訊鏡頭中使用者的畫面做為直播畫面,下半部留言區塊點擊表情符號後,表情符號加上彈幕的動畫效果。輸入訊息後,訊息會經由轉場動畫出現在畫面中,使用者也能點選刪除留言來看到訊息的離場動畫。
製作表情符號進出場的動畫會使用到 gsap ,gsap 是由 greenSock所開發,常被用來取代以前的 flash,提供許多製作動畫的套件,包含這次會使用到的 tweenMax 及 timelineMax,由於是透過 js 所撰寫,動畫呈現有更大的自由度。但要注意個人和商用部分功能是免費的,若需引入專案時要多留意。範例中,也會帶大家使用 vue transition 提供的兩種模式,來觸發表情符號與留言的進出場效果。
這次直播筆記會帶大家學會以下內容:
- 認識 vue 中的 ref, $refs
- 使用原生 js 載入視訊影像
- 使用 gsap 製作表情符號過場動畫
- 使用 vue 中的 transition 製作過場動畫
事前準備
開發環境
老闆在這次專案改使用 CodeSandbox進行開發,關於環境和其他套件的設定,同學可以參考老闆的成品。
CodeSandbox 比起之前老闆示範時常用的Codepen來說,功能較完整一些,除了提供大家建立 project、安裝需要使用的 library 之外,也能在上面跑 npm 的 package 設定。製作大型專案需要測試時,老闆習慣會使用 CodeSandbox ,但小缺點就是不支援 emmet(註:輸入簡化碼後會自動產生完整HTML & CSS程式碼,加快程式碼輸入,也降低手誤機率),撰寫程式碼較不方便一些。
透過 new sandbox 創建新的專案,選擇 Vue( vue2 的 cli)後,可以看到左邊有 files 欄,包含專案所有資料夾及檔案,這次專案只會在 App.vue 這支檔案中開發,同學們不用被資料夾結構嚇到。
打開 App.vue 檔案之後可以發現有三個區塊分別為:
- <template>:撰寫 html,改使用 pug
- <script>:撰寫 vue 及 js
- <style>:撰寫 css,改使用 scss
首先,將使用不到的元件 HelloWorld 相關的敘述全部拔除,準備好基本的結構後,可以看到右邊的畫面只剩下一個 vue 的 logo。
//將HTML撰寫語言改成pug
<template lang="pug">
#app
img(alt="Vue logo", src="./assets/logo.png", width="25%")
</template>
<script>
export default {
name: "App"
};
</script>
//將撰寫語言改成scss
<style lang="scss">
#app {
font-family: "Avenir", Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
接著,讓我們來安裝這次會使用的套件。畫面的左欄有 Dependencies,因為我們在創建專案時選擇 vue,可以看到 codeSandbox 已經幫我們裝了兩個套件。我們只需要再將 gsap 載入即可,在放大鏡區塊(Add Dependency)打入 gsap 並選擇安裝,就大功告成。


接下來會使用到的 API:
這次專案會使用以下的內容,這邊先重點整理給大家,不清楚的地方,可以透過後面跟著老闆操作,或是觀看相關文件,了解每個 api 使用時機。
Vue
- script
- el:資料要綁定的區塊
- data:vue 要綁定的資料放置處
- mounted:vue 的生命週期, el 被掛載之後會執行裡面的程式碼
- methods:使用到的 function 放置處
- computed:計算屬性,會因為 data 內的值改變,而跟著變動
- $refs:可以搭配 ref 屬性來取得 DOM 元件 (延伸參考資料)
// javascript
var vm = new Vue({
el: '#app',
data: {
text: 'Hello World',
texts: ['H', 'i']
},
mounted () {...},
methods: {
changeText () {
this.$refs.input.focus()
}
},
computed: {
showText () { return ...}
}
})
- template:畫面部分會使用到以下內容
- {{text}}:將資料綁定到畫面中顯示
- v-model:將資料綁定到畫面中顯示或修改
- v-for:讓陣列資料重複產生 dom,可以搭配索引值綁定
- :key:可以搭配 v-for 使用,提供 vue 識別每個 dom 是不同的,在傳入 v-for 的陣列中,key 要是獨特的值,避免識別上出錯。
- @click=””:當點擊目標物會觸發傳入 click 的內容
- ref:可以在程式碼中搭配 $refs 取得 DOM 元件(延伸參考資料)
// html
#app
p {{text}}
input (v-model="text ref="input")
p(v-model="showText")
div(:class="")
div(v-for="(item, idx) in texts", :key="idx") {{item}}
button(@click="changeText()")
- vue – transition-group:vue 提供給在 dom 要被加入、移除或更新時的動態效果,使用方法會在後面實做中解說。若想要參閱官方說明文件可點此閱讀。
- js:Math.random():會產出一個大於等於 0、小於 1 之間的隨機小數。若想要參閱更詳細的說明文件可點此閱讀。
gsap
- tweenMax:針對指定的 DOM 在動畫時間內執行動畫
TweenMax.to(執行動畫的DOM, 動畫時間, {
y: 200, // 位移 200 px
rotate: 360, // 旋轉 360 度
delay: 3, // 3 秒後才執行動畫
repeat: 2, // 會重複執行兩次
yoyo: true // 會倒帶後再執行一次
});
- timeLineMax:可讓動畫多段依序進行 ,使用 to 去接後續要播放的動畫
let tl = new TimelineMax() // 新增 tl 變數
tl.to(this.$refs.logo, 1, { // 使用 this.$refs 去取得 dom
y: 200,
rotate: 360
}).to(this.$refs.logo, 1, {
scale: 2
})
js – getUserMedia
提供瀏覽器獲得使用者影像,navigator 會詢問瀏覽器有沒有影片可以使用,找到之後將其放到 video tag 中,要記得提供瀏覽器取用麥克風或錄影機的權限。(延伸參考資料)
var constraints = { audio: true, video: { width: 1280, height: 720 } };
navigator.mediaDevices
.getUserMedia(constraints)
.then((mediaStream) => {
var video = this.$refs.myVideo;
video.srcObject = mediaStream; // 將 video 指定到指定的 DOM 中
video.onloadedmetadata = function (e) {
video.play();
};
})
.catch(function (err) { // 出錯時的處裡
console.log(err.name + ": " + err.message);
});
跟著老闆開始動手做
操作一段與多段動畫
我們在環境準備階段已經把 gsap 裝到專案中,首先我們使用 vue 的 logo 來練習 gsap 製作動畫方式。gsap 內有很多個製作動畫的方式,老闆帶大家操作兩種型式的動畫,分別為 tweenMax, timelineMax 兩種。
讓 logo 動起來之前,先介紹兩種方式讓 gsap 抓到 logo 這個 dom 元件。
- html 賦予 id,使用 #logo 讓 gsap 取得 dom
- html 賦予 ref,使用 $refs 讓 gsap 取得 dom
我們想要讓 logo 一秒內下滑,並旋轉,這邊會使用到 gsap 的 TweenMax,所以我們將它 import 到專案中,並在 vue 的 mounted 階段操作動畫,mounted 是 vue 的生命週期,會在 vue app 載入後執行裡面的動畫,寫法及參數如下。gsap 有許多的動畫值可以操作,建議同學們不用死背,需要時去查文件即可。
- TweenMax.to(dom, 秒數, 動畫物件) (延伸參考資料https://greensock.com/docs/v2/TweenMax)
<template lang="pug">
#app
img#logo(alt="Vue logo",
src="./assets/logo.png",
width="25%")
</template>
<script>
import { TweenMax } from "gsap";
export default {
name: "App",
mounted() {
TweenMax.to("#logo", 1, {
y: 200, // 位移 200 px
rotate: 360, // 旋轉 360 度
delay: 3, // 3 秒後才執行動畫
repeat: 2, // 會重複執行兩次
yoyo: true // 會倒帶後再執行一次
});
},
};
</script>

完成一段式的動畫後,會發現 TweenMax.to() 無法滿足多段式的動畫需求。如果我們希望動畫是多段小動畫依序進行,那要一直寫許多 TweenMax.to 並加上 delay 嗎?
其實,gsap 有另一個功能 TimelineMax 就可以達成我們的需求,使用 TimeLineMax 時,要注意需要先新增 new TimelineMax 的變數,使用的方式是第一段動畫完成後,使用 to 去接後續要播放的動畫,傳入的參數與 TweenMax 一樣。使用方法如下:
我們前面提到有兩種方式可以取得 dom 元件,這邊改使用 vue 所提供的 ref 及 $refs 去取得要執行動畫的 dom 元件。
<template lang="pug">
#app
img(ref="logo", alt="Vue logo", src="./assets/logo.png", width="25%")
//- img 多加 ref 屬性
</template>
<script>
import { TweenMax, TimelineMax } from "gsap";
export default {
name: "App",
mounted() {
let tl = new TimelineMax() // 新增 tl 變數
tl.to(this.$refs.logo, 1, { // 使用 this.$refs 去取得 dom
y: 200,
rotate: 360
}).to(this.$refs.logo, 1, {
scale: 2
})
},
};
</script>

直播畫面與 live 標籤
接著來處理畫面,會處理的內容分別為:模擬手機直播畫面的樣式切版、使用 video 視訊畫面做為直播影片、利用 ref 來取得 video 的位置、 live 動畫效果與時間計數器。
- 模擬手機畫面:只需要針對畫面樣式進行調整,在幫 live 區塊做定位時,記得在 #app 多加上 position: relative,否則預設會以 body 做為參考。
- 使用 video 作為直播影片:在畫面上準備待會要放置 video 的 dom,並在裡面放上一個 video tag ,這邊可以對 video tag 使用 muted 屬性,待會的影像就會是靜音的狀態。接著在 mounted 中使用 getUserMedia 來獲得影像。vue 初始化時,會先建立一個空的 video DOM,到了 mounted (vue app 載入之後)階段,navigator會詢問瀏覽器有沒有影片可以使用,找到之後將其放到 video tag 中,要記得提供瀏覽器取用麥克風或錄影機的權限。
- 這邊我們也練習前面提到的 ref ,來取得 video tag,MDN 上面提供的範例是使用 function,因為 function 有自己的 scope,無法在函式內部使用 this 取得 vue 本身。有兩種解法,在外面宣告 _this 變數,或是用 es6 的 arrow function。
- live 動畫效果:要幫 live 字樣加上呼吸燈的亮暗亮暗效果,除了前面有練習過的 repeat, yoyo 屬性外,也會使用到 gsap 中的 easing api,可以選擇自己喜歡的時間曲線後,在專案中引入。
- 時間計數器:在 mounted 中使用 setInterval 來進行每秒都會增加時間的值,利用這個值換算成需要的格式。透過 computed 來回傳需要的字串,computed 的使用時機為「已經知道資料是什麼,基於原本的值去加工後回傳,不會影響到原本的資料」。也利用 padStart 將時間中的時分秒三個資料都能是2位數,在最後回傳結果字串時,利用 es6 的頓號`來組裝字串。
<template lang="pug">
#app
.liveLabel
.red(ref="liveTag") LIVE //LIVE小標
.counter {{timeLabel}} //時間計數器
.videoContainer
video(ref="myVideo", autoplay="true", muted)
</template>
<script>
import { TweenMax, Power0 } from "gsap";
export default {
name: "App",
data() {
return {
time: 0,
};
},
computed: {
timeLabel() {
let sec = this.time % 60;
let min = Math.floor(this.time / 60) % 60;
let hour = Math.floor(this.time / 3600) % 24;
let pd = (num) => (num + "").padStart(2, "0"); // padStart api, 不足長度字串在前面補上0
return `${pd(hour)}:${pd(min)}:${pd(sec)}`;
},
},
mounted() {
// 每秒執行一次增加 time 的值
setInterval(() => {
this.time++;
}, 1000);
// Live 呼吸燈
TweenMax.to(this.$refs.liveTag, 1, {
css: {
backgroundColor: "rgba(255, 0, 0, 0.3)",
},
ease: Power0.easeNone,
repeat: -1,
yoyo: true,
});
// 影片串流
var constraints = { audio: true, video: { width: 1280, height: 720 } };
navigator.mediaDevices
.getUserMedia(constraints)
.then((mediaStream) => { // 改成 arrow function, this就不會抓到內部而是外層的元件
var video = this.$refs.myVideo;
video.srcObject = mediaStream;
video.onloadedmetadata = function (e) {
video.play();
};
})
.catch(function (err) {
console.log(err.name + ": " + err.message);
});
},
};
</script>
<style lang="scss">
html,
body {
background-color: #333;
display: flex;
justify-content: center;
align-items: center;
}
#app {
position: relative;
font-family: "Avenir", Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
width: 390px;
height: 744px;
background-color: white;
}
.videoContainer {
display: flex;
justify-content: center;
align-items: center;
height: 450px;
overflow: hidden;
video {
height: 100%;
}
}
.liveLabel {
position: absolute;
color: #fff;
display: flex;
left: 50%;
top: 30px;
transform: translateX(-50%);
.red {
padding: 5px 10px;
background-color: red;
font-weight: 900;
}
.counter {
padding: 5px 10px;
background-color: rgba(black, 0.6);
}
}
</style>

表情符號功能
接下來我們要來做表情符號清單與點擊表情符號後的效果。
- 表情符號清單:新增一個變數記錄所有表情符號,結合 split 就能將表情字串轉換成陣列,調整樣式後,將選單 #emojiToolBar 放置在畫面的右下方。
- 表情符號被點擊後動畫效果:清單中的表情符號被點擊後,使用 css 的類別選擇器 :active 來改變 transition 做為被點擊的動態回饋。我們也在清單中的每顆表情符號使用 vue 的語法 @click,當表情符號有點擊事件時,會觸發 addEmoji 函式,同時將被點擊的表情做為參數傳入函式中。
- 記錄有哪些表情符號被點擊:當使用者點擊表情符號後,我們需要記錄有什麼表情符號被觸發,才有辦法去跑對應的動畫,所以在 data 中我們新增一個變數 currentEmojiList ,當清單中的符號被按壓後,會將新的表情符號 push 到陣列裡。
- nextTick 確保資料已更新(延伸參考資料):因為 vue 不是即時更新,資料更新和畫面更新有時間差,所以在更新資料後,馬上去抓新的 dom 會失敗,改使用 nextTick 確定資料更新完畢才跑後續的程式碼。
- tweenMax 初始化設定:使用了 gsap.set() 這個 api,可以針對準備進場的動畫做初始設定,老闆希望表情剛進場時能從小變到大,所以我們在 set 中新增一個 scale: 0.2。
- 表情符號進出場動畫:期望的動畫流程為,按壓表情符號後,先往上飄並慢慢放大到定點,往左飄變小並離場,因為每個表情符號都要兩段式的動畫,這時就可以使用前面提到的 TimelineMax 來達成效果。若是有超出畫面則被隱藏,只要透過 css 去對 #app 做 overflow: hidden 即可。
- 加上隨機數值:完成前面幾點,目前的動畫會有點死板,為了讓動畫更自然,我們讓每個表情起始點不同,上移的距離也不同,製造出交錯的表情符號動畫。分別在 set 內新增一個 x 的值,隨機從0~-100 中挑一個數並加上20;也讓每個表情符號上移的 y 位置不同 ,所以在第一段動畫的終點,讓 y 的值組合不同的 random數。
- 時間函數:大致功能都完成後,希望兩段動畫能再自然一點,所以為兩段動畫都加上速度曲線的值 ease,大家也可以參考相關文件,動手試試不同種的速度效果。
<template lang="pug">
#app
...
.contentArea
ul.floatingEmojiList
li.floatingEmoji(
v-for="(emoji, emojiId) in currentEmojiList",
:class="`emoji_${emojiId}`"
) {{ emoji }}
ul#emojiToolBar
li.emojiBtn(v-for="emoji in emojis", @click="addEmoji(emoji)") {{ emoji }}
</template>
<script>
import { TweenMax, TimelineMax, Power0, Power1, Power4 } from "gsap";
const emojiList = "👍,🎉,😂,😯,😢,😡";
export default {
name: "App",
data() {
return {
time: 0,
emojis: emojiList.split(","),
currentEmojiList: [],
};
},
computed: {
...
},
mounted() {
...
},
methods: {
addEmoji(emoji) {
this.currentEmojiList.push(emoji);
let tl = new TimelineMax();
this.$nextTick(() => {
let _id = `.emoji_${this.currentEmojiList.length - 1}`;
tl.set(_id, {
scale: 0.2,
x: Math.random() * -100 + 20,
})
.to(_id, 1, {
y: -200 + Math.random() * -100,
scale: 1,
ease: Power4.easeOut,
})
.to(_id, 3, {
x: -500,
scale: 0.6,
ease: Power1.easeIn,
});
});
},
},
};
</script>
<style>
...
#app {
...
overflow: hidden;
}
...
#emojiToolBar {
position: absolute;
right: 0;
bottom: 0;
margin: 0;
display: flex;
list-style: none;
.emojiBtn {
font-size: 40px;
width: 50px;
cursor: pointer;
transition: 0.5s;
&:active {
transition: 0s;
transform: scale(0.8);
}
}
}
.contentArea {
position: relative;
}
.floatingEmojiList {
list-style: none;
.floatingEmoji {
position: absolute;
right: 50px;
top: 50px;
font-size: 50px;
}
}
</style>
改使用 transition 元件製作表情符號動畫
接下來我們來使用 vue 中 transition-group 元件改寫表情符號進場的過程。vue 提供了 transition 與 transition-group 兩種元件,讓元件在特定的時間點觸發指定的 function 或加上特定的 class 名稱(詳細請參考延伸資料)。transition 與 transition-group 的差別在於,如果只有一個元件會改變使用前者,這個專案是用在由 for 產出的 li 元件們上,所以使用後者。接著就可以把原本在 addEmoji 裡的程式碼搬到 enter 中。
此時,也可以拔掉 nextTick ,因為在 transition-group 上的屬性 v-on:enter 會在確定資料更新才觸發進場,就不用再使用 nextTick 去監聽元件是否生成。要注意的是,如果有使用 v-for ,記得要補上 key 值。
<template lang="pug">
#app
...
.contentArea
ul.floatingEmojiList
transition-group(v-on:enter="enter") //子元件進場時會觸發 enter 函式
li.floatingEmoji(
v-for="(emoji, emojiId) in currentEmojiList",
:key="emojiId", // 補上 key
:class="`emoji_${emojiId}`"
) {{ emoji }}
ul#emojiToolBar
li.emojiBtn(v-for="emoji in emojis", @click="addEmoji(emoji)") {{ emoji }}
</template>
<script>
import { TweenMax, TimelineMax, Power0, Power1, Power4 } from "gsap";
const emojiList = "👍,🎉,😂,😯,😢,😡";
export default {
...
mounted() {
...
},
methods: {
enter(el) { // 動畫進場時觸發的動畫
let tl = new TimelineMax();
tl.set(el, {
scale: 0.2,
x: Math.random() * -100 + 20,
})
.to(el, 1, {
y: -200 + Math.random() * -100,
scale: 1,
ease: Power4.easeOut,
})
.to(el, 3, {
x: -500,
scale: 0.8,
ease: Power1.easeIn,
});
},
addEmoji(emoji) { // 將動畫內容搬到 enter 函式中
this.currentEmojiList.push(emoji);
},
},
};
</script>
留言區塊
製作送出留言的功能,分別有以下項目需要完成:
- 準備假資料:先準備單筆資料的格式,分別有頭像顏色、發言人、內容。
- 輸入框及送出按鈕:這次只是模擬訊息送出的狀態,機制會是使用者輸入留言,成功送出訊息時,將這個訊息加到 comments 中,並將輸入框清空。若是輸入框為空的,則使用預設的內容送出。
- 預設訊息轉成 json 字串格式再轉回來:要多做這層處理,是因為預設留言 message 是物件,直接賦值的話會是傳參考,需要透過這種方式,創造一個全新的物件。
- 調整表情符號 bar 樣式:將表情工具的寬度改為 100%,加上透明背景。
<template lang="pug">
#app
...
.contentArea
input(v-model="message")
button(@click="addMessage") Add Comment
.comments(v-for="(comment, commentId) in comments", :key="commentId")
.head(:style="{ backgroundColor: comment.color }")
.content
.name {{ comment.name }}
.sentence {{ comment.content }}
</template>
<script>
import { TweenMax, TimelineMax, Power0, Power1, Power4 } from "gsap";
const emojiList = "👍,🎉,😂,😯,😢,😡";
let message = {
color: "#333",
name: "Lorem ipsum",
content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
};
export default {
name: "App",
data() {
return {
...
comments: [],
message: "",
};
},
computed: {...},
mounted() {...},
methods: {
addMessage() {
const newMessage = JSON.parse(JSON.stringify(message)); // 創造一個全新的物件
if (this.message !== "") {
newMessage.content = this.message;
this.message = "";
}
this.comments.push(newMessage);
}
...
},
};
</script>
<style lang="scss">
#emojiToolBar {
position: absolute;
right: 0;
bottom: 0;
margin: 0;
padding: 5px;
display: flex;
justify-content: flex-end;
list-style: none;
width: 100%;
background-color: rgba(#fff, 0.8);
...
}
...
.contentArea {
position: relative;
list-style: none;
padding-left: 0px;
margin-left: 10px;
.comments {
display: flex;
list-style: none;
padding: 5px;
font-size: 15px;
.head {
width: 40px;
height: 40px;
border-radius: 50%;
margin-top: 10px;
margin-right: 20px;
margin-left: 10px;
flex-shrink: 0;
}
.content {
text-align: left;
.name {
font-weight: 900;
}
}
}
}
</style>

新增/刪除訊息
最後我們利用新增訊息功能,來練習 transition,首先因為每筆訊息都是用 v-for 跑出來,所以我們要用 transition-group。
- 使用 name 來幫訊息加上動畫:前面的表情符號我們是用 v-on:enter ,當元件被監聽到加入畫面中時,觸發 enter 函式。這邊改使用 name 來觸發(延伸閱讀了解Transition),動態加上 class , vue 總共提供六個時間點,讓使用者為他們加上進場或離場動畫,同學可以去觀察 vue 在 dom 上做了什麼事。
- 調整對應時間點的動畫樣式:大家可以觀察當我們使用 name 來製作動畫後,vue 會在特定時間幫我們在對應的元件上新增 class。利用這些 class 我們就可以來製作過場動畫。要注意動畫的權重如果太小,有些效果無法順利觸發。
- 刪除訊息:既然完成了新增訊息,刪除訊息也能快速完成,老闆希望保留訊息的完整性,所以這邊調整成,當使用者點擊移除訊息的按鈕,只會在這則訊息的物件上新增一個 delete: true 的值,搭配 v-if 就能將這則訊息隱藏。
<template lang="pug">
#app
...
.contentArea
input(v-model="message")
button(@click="addMessage") Add Comment
transition-group(name="fade") // 改使用 name 製作動畫
.comments(
v-for="(comment, commentId) in comments",
:key="commentId",
v-if="comment.delete != true" // 當delete 的值不為 true 時,隱藏訊息
)
.head(:style="{ backgroundColor: comment.color }")
.content
.name {{ comment.name }}
.sentence {{ comment.content }}
button(@click="removeComment(comment)") - // 點擊後觸發 removeComment 函式
ul.floatingEmojiList
transition-group(v-on:enter="enter") // 當元件進入時,觸發 enter 函式
li.floatingEmoji(
v-for="(emoji, emojiId) in currentEmojiList",
:key="emojiId",
:class="`emoji_${emojiId}`"
) {{ emoji }}
ul#emojiToolBar
li.emojiBtn(v-for="emoji in emojis", @click="addEmoji(emoji)") {{ emoji }}
</template>
<script>
import { TweenMax, TimelineMax, Power0, Power1, Power4 } from "gsap";
const emojiList = "👍,🎉,😂,😯,😢,😡";
let message = {
color: "#333",
name: "Lorem ipsum",
content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
};
export default {
name: "App",
data() {
return {
time: 0,
emojis: emojiList.split(","),
currentEmojiList: [],
comments: [],
message: "",
};
},
computed: {
timeLabel() {
let sec = this.time % 60;
let min = Math.floor(this.time / 60) % 60;
let hour = Math.floor(this.time / 3600) % 60;
let pd = (num) => (num + "").padStart(2, "0");
return `${pd(hour)}:${pd(min)}:${pd(sec)}`;
},
},
mounted() {...},
methods: {
removeComment(comment) {
comment.delete = true;
},
addMessage() {
console.log("hi");
const newMessage = JSON.parse(JSON.stringify(message));
if (this.message !== "") {
newMessage.content = this.message;
this.message = "";
}
this.comments.push(newMessage);
},
...
},
};
</script>
<style lang="scss">
...
.contentArea {
.comments {
...
&.fade-enter-active, // 利用 transition name 做進出場動畫
&.fade-leave-active {
transition: all 0.5s;
}
&.fade-enter,
&.fade-leave-to {
opacity: 0;
transform: translateY(10px);
}
...
}
}
</style>

老闆來結語
這邊再提供一次範例的成果,讓大家在實作時參考,也帶大家快速回顧一次製作流程:
- 使用 codeSandbox 來開發專案,安裝 vue-cli, gsap 後,整理預設提供的檔案。
- 結合 vue 的 ref, $refs 來取得元件。
- 透過 gsap 中的 tweemMax, timelineMax 來製作一段或多段式的動畫。
- 利用原生 js 的影片串流模擬直播畫面,並加上 live 與時間計數器的效果。
- 了解 vue 提供的 nextTick 能夠確保資料更新後才進行畫面渲染。
- 製作表情符號工具欄,在使用者點擊後,能使用 timelineMax 製作表情符號動畫,結合 random 的 api 讓表情動畫更加自然。
- 使用 vue transition-group 來做為表情符號與新增刪除留言的進出場動畫。
這次利用 fb 的直播畫面做為目標,帶大家練習 gsap 製作動畫的方式,大家也可以挑戰自己,看看線上有哪些產品或網站有使用到動畫,想辦法使用 gsap 來實現,做為刻意練習的目標。萬事起頭難,一個作品不可能一步到位,大家在開發時,可以先將最終目標拆分成不同階段任務,從一開始的雛型慢慢開發出每個區塊,最後組裝在一起,就會十分有成就感啦!
跟著老闆上課去 👉 動態互動網頁程式入門(HTML/CSS/JS) 👉 動畫互動網頁特效入門(JS/CANVAS)
此篇直播筆記由幫手 H 協助整理




