Day 10 拖曳紙牌的效果(二)限制內部拖曳

今天來研究兩個列表的拖曳和如何限制列表的拖曳,學習內容主要來自vue.draggable文件的內容和親身實作進行分析。 先實現兩個列表的拖曳 昨天已學過如何製作單一列表的拖曳,先將相同的<draggable>內容複製往下貼, 複製的另一個<draggable>只需要調整屬性:list成想要對應的另一個陣列,並且兩個<draggable>添加相同的屬性group則自動實現兩個列表拖曳的關聯。 <template> <div> <draggable :list="firstCardStack" group="pokers" itemKey="value"> <template #item="{ element, index }"> <Card :value="element.value" :isOpen="element.isOpen" /> </template> </draggable> </div> <div> <draggable :list="secondCardStack" group="pokers" itemKey="value"> <template #item="{ element, index }"> <Card :value="element.value" :isOpen="element.isOpen" /> </template> </draggable> </div> </template> 如何限制列表的拖曳 前面已先實現了兩列表的拖曳,但為求理解仍保留著@change="console.log",在限制拖曳之前,我必須先觀察拖曳完成前能得到的資訊,所以我必須先觀察變動後@change實際印了什麼有用的資訊。 因為如果只是要單純限制一個<draggable>不能拖曳移入移出,只需要設定屬性:move="() => false",但我想做的不僅僅如此。 不同牌堆的移動 將牌堆1的梅花A移動到牌堆2後,可觀察到開發人員工具的Console依序印出兩個物件: 內含added屬性,且added物件本身包含陣列移動的元素element和目標陣列內的新位置newIndex 內含removed屬性,且removed物件本身包含陣列移動的元素element和原陣列內的舊位置oldIndex 這兩個@change是由各自受影響的Draggable所觸發,但內含的元素是相同的 同牌堆的移動 將同牌堆的第三張牌(梅花3)移動到第六張牌(梅花6)的位置,則只印出一個包含屬性moved的物件, 且moved物件本身包含陣列移動的元素本身element和新位置newIndex和舊位置oldIndex的資訊。 從此可推測,如果要禁止同牌堆內的移動,只需要禁止moved的行為。 禁止同牌堆的內部拖曳 從官方文件中可查到屬性:move對應的函數只要回傳false即可取消此次的拖曳。 function onMoveCallback(evt, originalEvent){ ... // return false; — for cancel } 這邊我只印出參數evt得到的資料,內容跟@change不一樣的超乎想像多資訊😫,而且跟@change不同的是,這個:move對應函數只要拖曳卡牌到某一張卡的上面就會觸發一次 但好險…如果只需要判斷是否在同個牌堆只需要看evt內的屬性from, to是否為相同DOM元素即可, 因為from、to分別代表拖曳的元素是 來自哪一個draggable元件 和 即將放置在哪一個draggable元件。...

September 19, 2023 · 1 min · 宗嘉

Day 09 拖曳紙牌的效果(一)

玩紙牌接龍最重要的就是卡牌會移來移去,之前都是用點的移動定點, 今天來試試看如何撰寫拖曳紙牌的功能。 安裝依賴 因為重頭學習理解拖曳,對我來說太麻煩也太無聊, 乾脆就使用現成的套件Vue.Draggable吧! 因為使用的是Vue3專案,所以必須安裝有標註next的版本。 npm i -S vuedraggable@next 單一列表的拖曳使用 我也還在理解該套件中,接下來的過程會盡可能去蕪存菁,但不失細節。 首先一定要先引入vuedraggable,如下所示: <script setup> import draggable from 'vuedraggable' // ...other template </script> 樣板的部分則如下: <template> <draggable :list="firstCardStack" itemKey="value" @change="console.log" style="display: grid; grid-template-columns: repeat(13, 3rem);"> <template #item="{ element, index }"> <Card :value="element.value" :isOpen="element.isOpen" /> </template> </draggable> </template> 這樣的寫法在通常就已可運用,接下來讓我們來逐一理解<draggable>每個欄位的意義和預設行為… :list 首先:list內設定的是一個參考到陣列的ref或reactive變數,只要裡面參考到的是Array即可, 這可以讓<draggable>明白當列表項目被拖曳移動時自動修改的對象。 // 此處genearateDeck(20, true)會回傳20個物件的Array物件 const firstCardStack = ref(geneateDeck(20, true)); // 為求簡單也能寫成底下這樣 // const firstCardStack = ref([]); itemKey屬性 第二個重要的屬性是itemKey,代表前面陣列中每一個元素的唯一值,基本上就跟v-for內用到的:key有相同作用,可讓元件從itemKey明白內部元素的差異進而去做列表更新。 此處會設定itemKey="value",是因為參考的陣列firstCardStack每一個元素的構造如下: { value: number, // 撲克牌值,Ex: 0 對應 ♣A isOpen: boolean // 開牌狀態,Ex: false 對應 蓋牌樣式 } 嘗試過不添加itemKey仍可拖曳且參考的陣列有更新,但會發生UI不會刷新的窘境🤣...

September 18, 2023 · 1 min · 宗嘉

Day 08 牌堆的卡牌移動動畫

重點提醒: 沒有真的成功實作卡牌無中斷的移動 在實作的過程中遇到了一些問題,找到替代方案先記錄下來。 最初的思路 在前一篇文章中,我們已經完成了牌堆的製作,接下來我們要來製作牌堆的卡牌移動動畫。 在開始實作之前,我必須決定要用CSS或是JS實現? 如果牌堆的位置永遠是固定的,或許可以用CSS來實現,但是我們要移動的是牌堆位置不一定永遠在那, 而且牌堆的卡牌數量是不固定的,更加深了預設的座標位置,所以我想這必須用JS來實現, 至少需要JS取得動態元素的位置。 動畫的實作 第一個問題對我來說是,我要怎麼知道卡牌的位置? 起始和結束的位置都是不固定的,所以我必須要先取得卡牌的位置,才能夠進行動畫的實作。 取得當前牌堆的位置的方法有很多種,我在這邊使用的是getBoundingClientRect(), 這個方法可以取得當前元素的相對位置+寬高,但是這個方法是在DOM上的,所以我利用const cardBox = ref(null)設定一個參考, Vue3會自動找到template中使用ref="cardBox"的元素,並且將其綁定到cardBox上,這樣我就可以在<script setup>中使用cardBox.value來取得HTML元素本身。 因為元件渲染掛載畫面上便會觸發onMounted事件,所以我在onMounted中取得元素的位置,並且透過emit傳遞卡片的絕對位置給父元件。 至於為什麼是絕對位置,因為我們要移動的是卡片,而不是牌堆,所以我們必須要知道卡片的絕對位置,才能夠進行移動。 雖然後來我失敗了,但是我還是想記錄一下這個方法,因為我覺得這個方法還是很有用的。 // CardColumn.vue <script setup> // ... const cardBox = ref(null); onMounted(() => { const rect = cardBox.value.getBoundingClientRect(); const x = window.scrollX + rect.left; const y = window.scrollY + rect.top; emit('position', { x, y }); }) </script> <template > <div class="card-box" :class="{ 'empty-card-box': isEmpty }"> <div class="card" style="visibility: hidden;"></div> <div style="visibility: visible; position: absolute; z-index: 5;" ref="cardBox"> <div style="; display: grid; grid-template-rows: repeat(13, 2rem);"> <Card v-for="(card, index) in cards" @click="(event) => onClick(event....

September 16, 2023 · 2 min · 宗嘉

Day 07 卡牌垂直重疊

今日完成目標 多張卡牌實現垂直重疊,但露出非交疊的部分 垂直重疊 為了產生重疊的效果,個人覺得最酷的方式應該是使用CSS的grid排版, 所以在這之前我利用一個GRID GARDEN的網站練習了一下。 原本問ChatGPT是得知用每一張卡堆疊都還要套用不同的CSS,如果要多一張牌就要多一個class,或是用sass的寫法達成,但以上這些我都不想要,一來太麻煩、二來sass還要額外引入新依賴,畢竟我只是想堆疊卡牌而已。 接著說明實際我達成的方式是靠display: grid;要求格狀排列,然後設定grid-template-rows為 repeat(13, 3rem);,這樣就可以讓每一張牌所在的格子都只有3rem的高度,設定13是因為我認為這樣垂直重疊排列卡牌最多只有13張,畢竟紙牌接龍不同花色暫時串再一起也就13張,在現實還是虛擬我的認知都是這樣,當然在設定比13高一點也不會影響,但如果出現第14、15張就會有卡牌不重疊,這點還請注意。 那為什麼達成重疊呢?我在容器內裝13個元件,每一個元件都只裝一張牌,且元件高度限制都在3rem,但實際元件 牌的高度是超過3rem,所以當我裝入第二張牌就會擋住第一張牌,讓第一張牌只露出3rem的高度,以此類推,最後一張牌則會露出全部。 // CardColumn.vue <template > <div class="card-box" :class="{ 'empty-card-box': isEmpty }"> <div class="card" style="visibility: hidden;"></div> <div style="visibility: visible; position: absolute; z-index: 1;"> <div v-if="isEmpty">沒牌</div> <div v-else style="; display: grid; grid-template-rows: repeat(13, 3rem);"> <Card v-for="(card, index) in cards" @click="onClick" :key="card.value" :value="card.value" :isOpen="card.isOpen" /> </div> </div> </div> </template> 額外補充可以注意到card-box內第一個元素是用來稱基本的寬高, 所以第二個元素我則讓他設定position: absolute這樣可以讓其中的格狀排列不會受到第一個元素的影響也不會影響到外部元素。 若少掉position: absolute的話,會變成底下這樣: 本日小結 原本今天是要用水平堆疊,但看了撲克才發現接龍平常都是玩垂直的… 至於卡牌移動則留在明天吧,因為堆疊卡牌的關係讓我意識到沒有我想像動畫的只是水平移動那麼簡單。 程式碼: https://github.com/kabuto412rock/ithelp-pokergame/tree/day07 參考資料 GRID GARDEN CSS Grid Layout

September 15, 2023 · 1 min · 宗嘉

Day 06 實現自訂義牌堆

今日目標 研究如何製作牌堆 製作牌堆 製作CardBox元件負責用來擺放卡片的容器,所以結構上就只是比原本卡片元件的稍寬,所以設計成可放子元件進去的樣板,然後當沒放牌時會產生跟牌一個寬高的隱藏區塊到slot裡面。 // CardBox.vue <template > <div class="card-box"> <slot> <div class="card" style="visibility: hidden;"></div> </slot> </div> </template> 所以目前首頁是長這樣,放了兩個空的CardBox佔位置。 // HomeView.vue <div style="display: grid; grid-template-columns: 1fr 1fr;"> <CardBox></CardBox> <CardBox></CardBox> </div> 接著思考如何實作卡牌從A點發到B點,看著畫面思考發現, 不如今天就來實作兩邊卡堆點擊後會移動到對方卡堆的功能。 這邊為了簡單驗證想法,定義函數geneateDeck(5, true)生成五張卡牌依序是梅花A~5(設為開牌), 我預計實驗兩個卡堆,所以函數也就設計以下兩個非常相似的函數, 功用正是將卡堆的最後一張卡彈出,並推到另一個牌堆。 // HomeView.vue const fstCards = ref(geneateDeck(5, true)); const secondCards = ref([]); const moveCardFromAToB = () => { const card = fstCards.value.pop(); if (card === undefined) return; secondCards.value.push(card); }; const moveCardFromBToA = () => { const card = secondCards....

September 15, 2023 · 1 min · 宗嘉