【互動網頁程式教學】用 GSAP 製作直播互動動態效果

用 GSAP 製作直播互動動態效果成品
用 GSAP 製作直播互動動態效果成品

本文翻自【互動網頁程式教學】用 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 並選擇安裝,就大功告成。

在Code Sandbox裡加入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 有許多的動畫值可以操作,建議同學們不用死背,需要時去查文件即可。

<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做出旋轉下滑再倒帶的動畫

完成一段式的動畫後,會發現 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>
使用TimelineMax做出旋轉下滑再放大的兩段動畫
使用TimelineMax做出旋轉下滑再放大的兩段動畫

直播畫面與 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>
增加刪除留言的功能
增加刪除留言的功能

老闆來結語

這邊再提供一次範例的成果,讓大家在實作時參考,也帶大家快速回顧一次製作流程:

  1. 使用 codeSandbox 來開發專案,安裝 vue-cli, gsap 後,整理預設提供的檔案。
  2. 結合 vue 的 ref, $refs 來取得元件。
  3. 透過 gsap 中的 tweemMax, timelineMax 來製作一段或多段式的動畫。
  4. 利用原生 js 的影片串流模擬直播畫面,並加上 live 與時間計數器的效果。
  5. 了解 vue 提供的 nextTick 能夠確保資料更新後才進行畫面渲染。
  6. 製作表情符號工具欄,在使用者點擊後,能使用 timelineMax 製作表情符號動畫,結合 random 的 api 讓表情動畫更加自然。
  7. 使用 vue transition-group 來做為表情符號與新增刪除留言的進出場動畫。

這次利用 fb 的直播畫面做為目標,帶大家練習 gsap 製作動畫的方式,大家也可以挑戰自己,看看線上有哪些產品或網站有使用到動畫,想辦法使用 gsap 來實現,做為刻意練習的目標。萬事起頭難,一個作品不可能一步到位,大家在開發時,可以先將最終目標拆分成不同階段任務,從一開始的雛型慢慢開發出每個區塊,最後組裝在一起,就會十分有成就感啦!

跟著老闆上課去 👉 動態互動網頁程式入門(HTML/CSS/JS) 👉 動畫互動網頁特效入門(JS/CANVAS)

此篇直播筆記由幫手 H 協助整理

墨雨設計banner
PHP Code Snippets Powered By : XYZScripts.com