iT邦幫忙

2023 iThome 鐵人賽

DAY 15
0
SideProject30

毫無自信的開發Web遊戲接龍-成為創造龍的英雄之路!系列 第 15

Day15 接龍發牌區功能實作(二)拖曳功能及CSS發牌動畫

  • 分享至 

  • xImage
  •  

前言

今天要來補強昨日缺少的拖曳功能和將整個頁面封裝成提供整合至接龍遊戲的元件發牌區

移牌區改為可拖曳

首先將移牌區的部分調整成從原生HTML元素div替換成這幾天都在使用draggable元件,程式碼如下:

<!-- 移牌區 - 左邊水平疊牌最多三張 -->
<draggable :list="canTakeCards" group="pokers" itemKey="value" class="list-group" >
    <template #item="{ element, index }">
        <Card :value="element.value" :isOpen="element.isOpen" />
    </template>
</draggable>

發牌區調整動畫

雖然移牌區感覺在加牌的時候應該要套用動畫,但無奈vue.draggable.next的transition-group有Bug存在且已被官方棄養,在issue也有許多類似問題issue with transition-group in composition API就不細講。

不論是Vue2使用的vue.draggableVue3使用的vue.draggable.next都是對SortableJS進行包裝的容器,基本上不滿意的話只能自己重封裝實現只是CP值不高,所以我不考慮這麼做。

這不代表選擇放棄動畫,打算以CSS來實現以下動畫效果
發牌向左位置&amp;左右搖晃的GIF

實際調整

首先調整樣板程式碼,主要是針對有牌時添加對應的class"card-back animtion",也順便將點擊事件@click移到外層div:

<!-- 發牌堆 -->
<div class="card-box">
    <div class="card " style="visibility: hidden;">
        <div style="visibility: visible; width: 100%;height: 100%;" @click="clickCard">
            <Transition name="slide-left">
                <div v-if="deckState == 'empty'">無牌可用</div>
                <div v-else-if="deckState == 'full'" class="card">重新循環</div>
                <div v-else-if="deckState == 'normal'" class="card-back animation"></div>
            </Transition>
        </div>
    </div>
</div>

添加對應CSS實現點擊後往左快速位移一次的效果

@keyframes move-left {
    from {
        transform: translateX(0rem);
    }

    to {
        transform: translateX(-100rem);
    }
}

.card-back.animation:active {
    animation: move-left 0.55s ease;
    animation-iteration-count: 1;
}

添加對應CSS當滑鼠移到發牌堆上方使其左右搖晃的效果

@keyframes swing {
    0% {
        transform: rotate(-5deg);
    }

    50% {
        transform: rotate(5deg);
    }

    100% {
        transform: rotate(-5deg);
    }
}

.card-back:hover {
    cursor: pointer;
    animation: swing 1s ease infinite;
}

封裝成元件

在目錄 src/components 內新增檔案DealerArea.vue,並將DealerAreaView.vue的程式碼完全複製到新檔案DealerArea.vue
但需要調整卡牌陣列deck從外部取得,所以DealerArea.vue程式碼需將變數deck改寫成以下形式:

// DealerArea.vue
const { deck } = defineProps(
    {
        deck: {
            type: Array,
            default: () => []
        }
    }
)

實際在外部引用

因為卡牌是在onMounted時才會初始化cardStacks內的每個陣列,
但不清楚為什麼初始化結束後,<DealerArea />仍顯示無牌可用。

/** DragDemo.vue */
<DealerArea :deck="cardStacks.delaerStacks" />

查詢網路資料後,才知道原來子組件props如果會整個異動的話需要用watch去監控變化才會重新觸發渲染,
所以再次調整DealerArea.vue的程式碼:

// DealerArea.vue
const props = defineProps(
    {
        deck: {
            type: Array,
            default: () => []
        },
        moveCard: {
            type: Function,
            default: () => { return false; }
        }
    }
)
const deck = ref([]);
watch(() => props.deck, (newVal) => {
    deck.value = newVal;
});

添加上方程式碼後,這裡<DealerArea>已可以根據props.deck的變動去做相對應的畫面更新。

但可以注意到defineProps突然多出moveCard屬性,實際上這是實作元件<DealerArea>忘記考慮被拖曳到其他區塊的行為。

所以再次調整DealerArea.vue的樣板,將<draggable的屬性:move對應到函數props.moveCard製作額外判斷是否可以被移出的函數,目前預設為回傳false的函數可以阻止元件內部<draggable>內的元素被移入到別的<draggable元件。

在樣板中使用props中的屬性moveCard為空,則會自動套用props屬性定義的預設值default

// DealerArea.vue
<!-- 移牌區 - 左邊水平疊牌最多三張 -->
<draggable :list="canTakeCards" group="pokers" itemKey="value" class="list-group" :move="moveCard">
    <template #item="{ element, index }">
        <Card :value="element.value" :isOpen="element.isOpen" />
    </template>
</draggable>

小結

今天將昨天的頁面轉成元件DealerArea並添加一些CSS動畫,也學到子元件需要監聽外部props變動重新渲染的前端做法。
明日將實作讓<DealerArea>內移牌區內的牌有辦法實際拖曳至中間的7個牌堆上並一樣遵守拖曳的遊戲規則。

程式碼: https://github.com/kabuto412rock/ithelp-pokergame/tree/day15
Yes

參考


上一篇
Day14 接龍發牌區功能實作(一)發牌循環
下一篇
Day16 接龍發牌區功能實作(三)拖曳與疊牌區整合
系列文
毫無自信的開發Web遊戲接龍-成為創造龍的英雄之路!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言