iT邦幫忙

2021 iThome 鐵人賽

DAY 29
1
Modern Web

排版神器 Tailwind CSS~和兔兔一起快速上手漂亮的元件開發!系列 第 30

Day 29:「欸!你填一下這張表!」- 彈跳視窗、Modal

Day29-Banner

「注意,您的表單尚未填寫完成」

好,我知道了。 (按下確定)

欸? 為什麼關不掉!?
不是按下確定就要關掉了嗎?

這是誰做的爛東西啊,看我怎麼整死你!


 

(開發者人員工具打開後,兔兔驚呆。)

哦 ... 什麼嘛~原來是我之前的未完成品啊~ 其實也不是做得很爛啦! 只是那時候比較趕時間所以 ...

「兔兔,我們都聽到囉~」

齁,不是這樣的,就,呃 ... 好啦!

(咳咳!)
那看來今天的任務就是把它做完整,來雪恥了!!!

carrotPoint 建立元件

首先,在專案裡的 ./src/components 資料夾中新增一個 Modal.vue 的元件:

然後:

<template>
  
</template>

<script>
export default {
  name: "Modal",
}
</script>

為了成品的美觀,修改一下 App.vue 內的樣式,接著把元件新增到畫面中:

<template>
  <div :class="[
    'w-screen h-screen',
    'p-5'
  ]">
    <Modal />
  </div>
</template>

<script>
import Modal from './components/Modal.vue'

export default {
  data() {
    return {

    }
  },
  components: {
    Modal,
  }
}
</script>

OK,下一步!
 

carrotPoint 互動視窗 (Modal)

今天要參考的範本是 ...

有沒有覺得很面熟啦~

沒錯,就是我們的 Creator
今天要做出來的成品和 Creator 的 Modal 本身沒有什麼差異哦~

首先,來分析一下要素:

主要類別就可以分成後面的覆蓋層,和前面的視窗兩類。 但視窗其實也還能再簡易的切一下:

這樣推理線索都具備後,就可以開始名偵探兔兔的神還原了!

 

carrotPoint 還原現場

依據各個線索來推斷,可以得到下面的結論:

  • Modal 覆蓋層: 壓在後面,看起來是用了半透明滿版
  • Modal 視窗: 從外觀看起來是完全置中於畫面,壓在覆蓋層之上,然後 ... 那完美弧度,應該是傳說中的小米定律導致的
    • 視窗標題列: 裡面的左、右各有一個元素,看來是和手風琴選單相似的犯罪手法
    • 視窗內容區: 通常 Modal 都會一直變換其中的內容 ... 可能是因為用了 slot 的原因

「我應該沒有漏說的吧?因為 ... 真相永遠只有一個!」

當然 ... 因為前一次造下這犯行的人就是我自己啊 QQ

為了雪恥當然得更認真點!

來吧! 第一個先完成覆蓋層和視窗的基底:

<div class="fixed top-0 left-0 w-screen h-screen p-5 flex justify-center items-center">
  <!-- Modal-Overlay -->
  <div class="absolute top-0 left-0 w-full h-full bg-black/50" />

  <!-- Modal-Window -->
  <div class="w-full max-w-sm bg-white rounded-md overflow-hidden z-10">
    這是 Modal 視窗
  </div>
</div>

對了,有注意到上面覆蓋層的 bg-black/50 嗎? 這就是之前在 Day 16 說過的便捷語法,快速就可以上完顏色和不透明度了。

還有,因為覆蓋層的位置是 absolute,所以會蓋住視窗,記得加上 z-10 來調整 z 軸位置,把視窗的顯示層級往上拉。

那上面都完成了,就可以準備來刻視窗內部了。

其實視窗的結構和 Day 27 的手風琴選單極為類似,大部分東西都是一樣的,而視窗內容區塊的設計也與手風琴選單內容區的設計相同,移植過來再修改一下,就會像這樣子:

<!-- Modal-Window -->
<div class="w-full max-w-sm bg-white rounded-md overflow-hidden z-10">
  <div class="border-b-2 p-3 flex justify-between items-center">
    <div class="font-bold text-gray-700">
      注意
    </div>
    <div class="h-7 w-7 p-1 hover:bg-gray-200 active:scale-90 rounded-md cursor-pointer transition-all">
      <svg xmlns="http://www.w3.org/2000/svg" class="h-full w-full" fill="none" viewBox="0 0 24 24" stroke="currentColor">
        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
      </svg>
    </div>
  </div>

  <div>
    <slot name="itemContent">
      <div class="p-3">
        <slot name="itemText">
          這是 Modal 視窗
        </slot>
      </div>
    </slot>
  </div>
</div>

順便把視窗標題的文字變成 Props,這樣們就可以從外部修改了:

<!-- 應用時 -->
<Modal title="嗨">
  <template v-slot:itemText>
    你好
  </template>
</Modal>

完美至極!

接下來就要來完成我們最需要雪恥的 Modal 視窗開關部份了!

 

carrotPoint 一雪前恥?

為了還要可以再度開啟視窗,我們會用上 Day 26 做過的按鈕哦~

我們就,開始吧!

首先,先來完成關閉的功能。 能夠用來點擊後關閉視窗的不外乎就是覆蓋層和叉叉兩處,但為了儲存狀態,我們在 data 中要增加一個變數:

export default {
  name: "Modal",
  props: {
    title: {
      default: "注意"
    }
  },
  data() {
    return {
      showed: true,
    }
  },
}

先設為 true 哦!
不然等等一開始 Modal 就消失了 XD

然後寫一個 methods 的函數 closing() 來處理關閉事件:

export default {
  name: "Modal",
  props: {
    title: {
      default: "注意"
    }
  },
  data() {
    return {
      showed: true,
    }
  },
+ methods: {
+   closing() {
+     this.showed = false
+   }
+ }
}

然後把我們所寫的函數應用上 template 中的覆蓋層和叉叉,做為它們 onclick 事件所觸發的動作:

<div class="fixed top-0 left-0 w-screen h-screen p-5 flex justify-center items-center">
  <!-- Modal-Overlay -->
  <div
    class="absolute top-0 left-0 w-full h-full bg-black/50"
    @click="closing()"
  />

  <!-- Modal-Window -->
  <div class="w-full max-w-sm bg-white rounded-md overflow-hidden z-10">
    <div class="border-b-2 p-3 flex justify-between items-center">
      <div class="font-bold text-gray-700">
        {{ title }}
      </div>
      <div
        class="h-7 w-7 p-1 hover:bg-gray-200 active:scale-90 rounded-md cursor-pointer transition-all"
        @click="closing()"
      >
        <svg xmlns="http://www.w3.org/2000/svg" class="h-full w-full" fill="none" viewBox="0 0 24 24" stroke="currentColor">
          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
        </svg>
      </div>
    </div>

    <div>
      <slot name="itemContent">
        <div class="p-3">
          <slot name="itemText">
            這是 Modal 視窗
          </slot>
        </div>
      </slot>
    </div>
  </div>
</div>

但現在按了仍然還不會有效果,因為我們還沒有讓 Modal 有相對應的樣式變化。

不過因為兔兔的做法和外面一般的實現方法有些許不同,所以在這裡先介紹一個會用到的特殊 CSS 屬性: pointer-events

pointer-events

在 CSS 中,若想要讓點擊事件穿透到元素背後,使這個元素不會觸發點擊事件的話,pointer-events 就是你要找的功能。

在 Tailwind 中也有相對應的功能性 class,若要讓點擊穿透,就在該元素中使用 pointer-events-none,若要恢復,則是 pointer-events-auto

所以在這裡,兔兔決定若是不顯示 Modal 時,Modal 整體會變成完全透明且可點擊穿透顯示 Modal 時則完全不透明且不可點擊穿透。我們用三元來實現:

<div
  :class="[
    'fixed top-0 left-0',
    'w-screen h-screen',
    'p-5',
    'flex justify-center items-center',
    'transition-all duration-300',
    showed? 'opacity-100': 'opacity-0 pointer-events-none'
  ]"
>
    <!-- Modal-Overlay -->
    <div
      class="absolute top-0 left-0 w-full h-full bg-black/50"
      @click="closing()"
    />
    
    <!-- Modal-Window -->
  ...

做到這裡,去點擊 Modal 的叉叉或覆蓋層應該馬上就有效果了,Modal 會淡出。

不過這樣還不夠華麗,我們順便追加一下 Modal 視窗的動畫。

Modal 視窗的部份我們讓它在**不顯示 Modal **時,會縮放到 0;相反的,顯示 Modal 時,縮放程度會回到 100,也就是不縮放。這個我們也用三元運算子來做:

<!-- Modal-Window -->
<div
  :class="[
    'w-full max-w-sm bg-white rounded-md overflow-hidden z-10',
    showed? 'scale-100': 'scale-0',
    'transition-all duration-300'
  ]"
>
  <div class="border-b-2 p-3 flex justify-between items-center">
    <div class="font-bold text-gray-700">
      {{ title }}
    </div>
    <div
      class="h-7 w-7 p-1 hover:bg-gray-200 active:scale-90 rounded-md cursor-pointer transition-all"
      @click="closing()"
    >
      <svg xmlns="http://www.w3.org/2000/svg" class="h-full w-full" fill="none" viewBox="0 0 24 24" stroke="currentColor">
        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
      </svg>
    </div>
  </div>
  ...

哈! 這樣是不是很好看呢?

現在可以關起來了,可是卻打不開 ...

不過也不用擔心啦,把我們之前做的按鈕拿出來用,讓我們按下按鈕來打開 Modal 吧!

但是要可以從外部開啟,我們還必須在內部動一點手腳。

要可以從外部收取是否展開 Modal 的訊號,我們就要加上一個 Props 參數:

export default {
  name: "Modal",
  props: {
    showModal: {},
    title: {
      default: "注意"
    }
  },
  ...
}

加好之後,別忘了做進度條時的事情,要監控外部傳來的變數內容來更新內部的變數,還有在元件掛載時也要做一次:

export default {
  name: "Modal",
  props: {
    showModal: {},
    title: {
      default: "注意"
    }
  },
  data() {
    return {
      showed: true,
    }
  },
  mounted() {
    this.showed = this.showModal
  },
  watch: {
    showModal(newVal, oldVal) {
      this.showed = newVal
    }
  },
  methods: {
    closing() {
      this.showed = false
    }
  }
}

雖然這樣看似完整了,不過還有一個小 bug。 我們在關閉事件 closing() 觸發時一直都是改變內部的變數,但外部的狀態並不會被我們同步到,所以我們必須用 emit 傳遞出去給父層:

methods: {
  closing() {
    this.showed = false
    this.$emit("closing")
  }
}

OK,這樣 Modal 元件應該是完成囉!

接著來測試吧~
 

carrotPoint 測試時間

我們用之前做過的按鈕快速來建立個測試環境。

我們必須現在 App.vue 中定義一個儲存 Modal 開啟狀態的變數,按鈕按下時會把變數變為 true,接收到來自 Modal 關閉事件觸發時,會把變數設為 false。

那實作起來大概就是這樣:

<template>
  <div :class="[
    'w-screen h-screen',
    'p-5'
  ]">
    <RabbitButton
      class="w-40"
      text="開啟 Modal"
      color="yellow"
      @click="showed=true"
    />

    <Modal
      title="嗨"
      :showModal="showed"
      @closing="showed=false"
    >
      <template v-slot:itemText>
        兔兔你好
      </template>
    </Modal>
  </div>
</template>

<script>
import RabbitButton from './components/RabbitButton.vue'
import Modal from './components/Modal.vue'

export default {
  data() {
    return {
      showed: true,
    }
  },
  components: {
    RabbitButton,
    Modal,
  }
}
</script>

OK~ 這樣算是雪恥成功了吧?
 

還行嗎? 會不會太難呢?

其實你應該會發現寫久了,複雜度好像就那樣而已,更多的是樣式上的變化,還有怎麼做才會讓自己未來可以更加的方便,其實人家說好的程式,是要能一直往上擴充,而不是去反覆修改原有的內容。

希望大家都能做順利的自己造輪子嘿!
(如果開輪胎廠了,記得要讓兔兔當股東。)

carrotPoint 給你們的回家作業:


關於兔兔們:


 


( # 兔兔小聲說 )

明天就是最後一天了呢~
說實在一路走來心裡是很掙扎的,
因為根本就沒想過可以堅持到這裡,
而且,我這才發現自己在文章內容上的坑越挖越大了 XDDD

加油!


上一篇
Day 28:「今天幾月幾號啊?」- 簡易日曆
下一篇
Day 30:「很刺眼,這樣太亮了啦!」- 深色模式切換開關
系列文
排版神器 Tailwind CSS~和兔兔一起快速上手漂亮的元件開發!32

1 則留言

0
孤獨一隻雞
iT邦新手 3 級 ‧ 2021-10-01 00:02:53

太扯 有夠壓秒

搋兔 iT邦新手 5 級 ‧ 2021-10-01 00:27:21 檢舉

就是這麼玩才刺激,不是嗎?

我要留言

立即登入留言