iT邦幫忙

2022 iThome 鐵人賽

DAY 5
1
Modern Web

派對動物嗨起來!系列 第 5

D05 - 飄吧!多邊形!

  • 分享至 

  • xImage
  •  

本系列文已改編成書「甚麼?網頁也可以做派對遊戲?使用 Vue 和 babylon.js 打造 3D 派對遊戲吧!」

書中不只重構了程式架構、改善了介面設計,還新增了 2 個新遊戲呦!ˋ( ° ▽、° )

新遊戲分別使用了陀螺儀與震動回饋,趕快買書來研究研究吧!ლ(╹∀╹ლ)

在此感謝深智數位的協助,歡迎大家前往購書,鱈魚感謝大家 (。・∀・)。

助教:「所以到底差在哪啊?沒圖沒真相,被你坑了都不知道。(´。_。`)」

鱈魚:「你對我是不是有甚麼很深的偏見啊 (っ °Д °;)っ,來人啊,上連結!」

Yes


要讓很多個多邊形出現,第一步就是建立一個產生多邊形的 function(這一句好像廢話 ¯_( ͡° ͜ʖ ͡°)_/¯)

先來定義一下多邊形需要的參數。

import PolygonBase, { FillType, ShapeType } from './polygon-base.vue';

...

interface PolygonParams {
  left: string;
  top: string;
  size: string;
  rotate: string;
  opacity: string | number;
  shape: `${ShapeType}`;
  fill: `${FillType}`;
  color: string;
  animationDuration: string;
}

可以注意到有一個項目是 animationDuration,這是因為我們預期使用 CSS 之 animation 讓多邊形動起來,透過讓每個多邊形之 animationDuration 都不同,就可以有交錯漂浮的效果,讓畫面更多元。

接著新增名為 createPolygonParams 的 function,負責產生多邊形參數。

import { random, sample } from 'lodash-es';

...

function createPolygonParams() {
  // 變暗、偏移色相、提升飽和度,作為 polygon 候選顏色
  const darkColor = lighten(props.mainColor, -10);
  const hsvColor = rgbToHsv(textToRgb(darkColor));
  hsvColor.s += 20;

  const hsvColor01 = cloneDeep(hsvColor);
  hsvColor01.h -= 10;

  const hsvColor02 = cloneDeep(hsvColor);
  hsvColor02.h += 10;

  const colors = [
    rgbToHex(hsvToRgb(hsvColor)),
    rgbToHex(hsvToRgb(hsvColor01)),
    rgbToHex(hsvToRgb(hsvColor02))
  ];

  const params: PolygonParams = {
    left: `${random(0, 100)}%`,
    top: `${random(0, 100)}%`,
    size: `${random(5, 20)}rem`,
    rotate: `${random(0, 90)}deg`,
    opacity: random(0.1, 0.2, true),
    color: sample(colors) ?? '',
    shape: sample(Object.values(ShapeType)) ?? 'round',
    fill: sample(Object.values(FillType)) ?? 'solid',
    animationDuration: `${random(5, 20)}s`,
  }
  return params;
}

這裡引入了 lodash 的 random、sample 與 cloneDeep,功能分別為:

  • random:產生指定的隨機數值
  • sample:隨機取出舉鎮內某元素
  • cloneDeep:深度複製物件

接著就是實際產生多邊形的時候了,一般來說最常見的方式就是用一個矩陣將 PolygonParams 裝起來,並在 template 透過 v-for 產生即可,不過這樣有個小問題。

當多邊形動畫結束時,要移除矩陣中指定的多邊形會稍微有點麻煩,這裡用一個更簡單的方式實作,也就是 JavaScript 的 Map 物件。

想要深入了解的讀者可以參考此連結:JavaScript - Map

其實使用 object 也可以,只是 Map 提供更簡便直覺的 Method,用來新增、刪除內容。

首先定義一個儲存多邊形用的 Map 物件。

const polygonsMap = ref<Map<string, PolygonParams>>(new Map());

並新增用來新增、刪除多邊形的 function。

import { nanoid } from 'nanoid';

...

function addPolygon(params: PolygonParams) {
  polygonsMap.value.set(nanoid(), params);
}
function removePolygon(id: string) {
  polygonsMap.value.delete(id);
}

這裡透過 nanoid 產生 key。

nanoid 是一種尺寸小、速度快的唯一字串生成套件,簡單好用。

接著來實作持續產生多邊形效果,有以下需求:

  • 依據 generateInterval 之時間間距,產生多邊形。
  • 依據 initialQuantity 產生初始數量多邊形。
  • 組件被解除時,需自動銷毀計時器。
let timer: ReturnType<typeof setInterval>;

/** 若 generateInterval 參數有變化,則自動重新建立 timer */
watch(() => props.generateInterval, (generateInterval) => {
  clearInterval(timer);

  timer = setInterval(async () => {
    // 到達最大數量後,停止生成
    if (polygonsMap.value.size >= props.maxQuantity) return;

    // 刻意延遲隨機時間,讓多邊形生成不會過度固定而顯得死板
    await promiseTimeout(random(300, 1000));
    addPolygon(createPolygonParams());
  }, generateInterval);
}, {
  immediate: true
});

function init() {
  // 預先建立多邊錫
  for (let i = 0; i < props.initialQuantity; i++) {
    addPolygon(createPolygonParams());
  }
}
init();

onBeforeUnmount(() => {
  clearInterval(timer);
});

現在讓我們打開 Vue DevTools。

Untitled

可以看到 polygonsMap 內應該會有多邊形了。

讓我們將 polygonsMap 加到 template 裡面吧!將原本測試用的多邊形刪除,改為以下程式。

<template>
  <div
    class="overflow-hidden"
    :style="backgroundStyle"
  >
    <slot />

    <polygon-base
      v-for="[key, polygon] in polygonsMap"
      :key="key"
      class="absolute"
      :style="`left: ${polygon.left}; top: ${polygon.top}; animation-duration: ${polygon.animationDuration}`"
      :color="polygon.color"
      :rotate="polygon.rotate"
      :shape="polygon.shape"
      :fill="polygon.fill"
      :size="polygon.size"
      :opacity="polygon.opacity"
      @animationend="removePolygon(key)"
    />
  </div>
</template>

讀者可能會注意到在此註冊了 **@**animationend 事件,這個事件用於 CSS 之 animation 播放結束時觸發,很適合用來刪除元素,所以將 removePolygon 綁定在此事件。

同時也加入 slot,讓父元件額外加入內容的彈性。

現在可以看到多邊形出現了。(・∀・)ノ

Untitled

最後我們讓多邊形動起來吧,實作相當簡單,建立動畫用的 class 就行惹。

<style scoped lang="sass">
.polygon-move
  animation: polygon-move 10s forwards linear
@keyframes polygon-move
  0%
    transform: translate(0px, 0px) rotate(0deg)
    opacity: 0
  10%
    opacity: 1
  90%
    opacity: 1
  100%
    transform: translate(-10rem, 10rem) rotate(6deg)
    opacity: 0
</style>

把 class 加入 template 中。

<template>
  <div
    ...
  >
    ...
    <polygon-base
      ...
      class="absolute polygon-move"
      ...
    />
  </div>
</template>

可以看到多邊形飄起來了!(。^▽^)

undefined

不過這樣畫面看起來稍嫌單調,讓我們新增幾個背景用的多邊形,這個時候剛剛預留的 slot 就派上用場了!

在 the-home 中的 background-polygons-floating 加入兩個多邊形並新增對應樣式。

<template>
  <background-polygons-floating class="absolute inset-0">
    <polygon-base
      class="bg-polygon-lt"
      size="50rem"
      fill="fence"
      shape="square"
      rotate="30deg"
      opacity="0.1"
      color="#e09c48"
    />

    <polygon-base
      class="bg-polygon-rb"
      size="80rem"
      fill="spot"
      shape="round"
      rotate="30deg"
      opacity="0.1"
      color="#f0a53c"
    />
  </background-polygons-floating>
</template>

<script setup lang="ts">
import { ref } from 'vue';

import BackgroundPolygonsFloating from '../components/background-polygons-floating.vue';
import PolygonBase from '../components/polygon-base.vue';
</script>

<style scoped lang="sass">
.bg-polygon-lt
  position: absolute
  left: -25rem
  top: -25rem
  animation: polygon-swing 40s infinite ease-in-out
.bg-polygon-rb
  position: absolute
  right: -25rem
  bottom: -50rem
  transform: translate(30%, 30%)
  animation: polygon-swing 28s infinite ease-in-out

@keyframes polygon-swing
  0%, 100%
    transform: rotate(0deg)
  50%
    transform: rotate(20deg)
</style>

現在左上角和右下角各有一個固定班底的多邊形了!( •̀ ω •́ )y

Untitled

總結

  • 使用 Map 物件儲存多邊形
  • 建立多邊形產生器
  • 動畫結束後自動刪除多邊形
  • 完成多邊形漂浮動畫

下一章讓我們加上按鈕,讓使用者「建立派對」或「加入遊戲」吧。( •̀ ω •́ )✧

以上程式碼已同步至 GitLab,大家可以前往下載:

GitLab - D05


上一篇
D04 - 門面怎麼可以沒有背景
下一篇
D06 - 打造遊戲選單按鈕:利用 SVG 產生文字外框
系列文
派對動物嗨起來!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言