iT邦幫忙

2021 iThome 鐵人賽

DAY 25
1
Modern Web

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

Day 25:「好慢喔,下載多少了?」- 進度條

Day25-Banner

終於到了我們的元件篇啦!!!

今天是第一個元件,所以稍微簡單一點。
我們要來做下載的進度條~

carrotPoint 前置作業

我們這次要使用的專案環境是 Vue 的,所以之前建立過的那個專案就不沿用囉!

而 Tailwind + Vue 的專案要快速建立就得用到 Creator 啦!

Creator 連結在這邊: 點我

進到 Creator 後,我這邊專案名稱是叫做 beautiful-components-libs,然後 template 選擇 Vite 分類下的 Vue。

都 OK 之後,大力按下複製然後貼到你的 Terminal (cmd) 中,讓他開始建立專案並開啟 dev server。

開啟後,我們瀏覽網站就會看到這個畫面:

那這樣前置作業就完成啦~
接著開啟 vs code 準備開發!

(如果對這些步驟或是對 Creator 還不熟悉的人,建議你回去複習兔兔的 Day 18:「極速開發」- Vitawind)
 

carrotPoint 建立空白元件

首先,我們要先建立空白元件,
這樣畫面上就可以同步看到我們的修改內容。

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

完成後,增添以下內容:

<template>
  
</template>

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

<style>

</style>

接著我們就可以把這個元件新增到畫面中,打開 App.vue 然後改成以下內容:

<template>
  <div :class="[
    'w-screen h-screen',
    'flex flex-col',
    'justify-center items-center',
    'gap-5',
  ]">
    <ProgressBar />
  </div>
</template>

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

export default {
  components: {
    ProgressBar
  }
}
</script>

這時候畫面上 ... 是空白的!

對,因為我們把 HelloWorld.vue 從畫面上去掉了,加入了我們建立的 ProgressBar 元件,然後因為元件內容都還沒有做,所以畫面是空白的。

再來,就要來開始做我們的進度條囉!
 

carrotPoint 進度條

我們先來看一下,進度條可能會有的樣子!

所以很明顯,這就是要分成三層,
既然知道了,就 div 大法直接建 3 層

像這樣:

<template>
  <div>
    <div>
      <div />
    </div>
  </div>
</template>

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

建好之後,都先填上背景顏色:

<div :class="[
  'bg-gray-200'
]">
  <div :class="[
    'bg-green-800'
  ]">
    <div :class="[
      'bg-green-500'
    ]" />
  </div>
</div>

因為現在都還沒有寬度高度,所以畫面上還是沒有東西。 我們幫最外層設定高度 w-80 並增加一點內距 p-5

<div :class="[
  'w-80 p-5',
  'bg-gray-200'
]">
  <div :class="[
    'bg-green-800'
  ]">
    <div :class="[
      'bg-green-500'
    ]" />
  </div>
</div>

看起來有點 ... 樣子?

還沒,我們再來幫進度條背景和進度條本體設定寬度高度,進度條背景的寬高設為 w-full h-2,然後進度條本體的寬高設定為 w-1/3 h-full

<div :class="[
  'w-80 p-5',
  'bg-gray-200'
]">
  <div :class="[
    'w-full h-2',
    'bg-green-800'
  ]">
    <div :class="[
      'w-1/3 h-full',
      'bg-green-500'
    ]" />
  </div>
</div>

這次是不是真的有點 fu 了?
對啦~一定有啦~我知道啦~

可是根據小米定律,要好看的話肯定要加上什麼?

大聲點,我聽不到! 梁靜茹也聽不到!

嘿啦~就是圓角啦!

我們幫最外層加上大量級的圓角 rounded-lg,然後幫進度條背景和進度條本體都加上 rounded-full

<div :class="[
  'w-80 p-5',
  'bg-gray-200',
  'rounded-lg'
]">
  <div :class="[
    'w-full h-2',
    'bg-green-800',
    'rounded-full'
  ]">
    <div :class="[
      'w-1/3 h-full',
      'bg-green-500',
      'rounded-full'
    ]" />
  </div>
</div>

嗯~~~很可以。

是不是! 我就說小米定律超有效的啦!
質感整個大提升了。

但現在只是固定的畫面,所以我們要讓它可以實際使用!
要讓它動起來了!
 

carrotPoint 動起來

接下來就是要寫 Vue 的部分了。

這時候可以仔細的思考一下,
進度條會需要什麼東西?

答案就是:最小值最大值現在值

所以我們就幫元件加上 props,分別是 minValmaxValcurrentVal

<script>
export default {
  name: "ProgressBar",
  props: ["minVal","maxVal","currentVal"],
}
</script>

加好之後,我們需要算進度是多少 % 。

在 data 中建立一個變數 percent 來儲存算完的結果,預設值是 0。

但是光是建立好變數,還沒建立算式呢! 這時候我們要使用之前沒說過的東西:watch 來完成。

之前在 Day 20 有談過類似的概念,而 Vue 中的 watch 呢就是可以用來監看某個變數或物件是否有變動,然後觸發你所指定的方法。

因為我們這裡需要在 currentVal 變動時重新計算進度是多少 % ,所以我們寫在 watch 之中:

<script>
export default {
  name: "ProgressBar",
  props: ["minVal","maxVal","currentVal"],
  data() {
    return {
      percent: 0,
    }
  },
  watch: {
    currentVal(newVal, oldVal) {
      this.percent = newVal <= this.minVal ? 0 : (newVal-this.minVal)*100/(this.maxVal-this.minVal)
    }
  }
}
</script>

最後,因為元件建立時載入初始值的當下不算是變數內容的變動,所以我們要在元件掛載時重新計算一次:

<script>
export default {
  name: "ProgressBar",
  props: ["minVal","maxVal","currentVal"],
  data() {
    return {
      percent: 0,
    }
  },
  mounted() {
    this.percent = this.currentVal <= this.minVal ? 0 : (this.currentVal-this.minVal)*100/(this.maxVal-this.minVal)
  },
  watch: {
    currentVal(newVal, oldVal) {
      this.percent = newVal <= this.minVal ? 0 : (newVal-this.minVal)*100/(this.maxVal-this.minVal)
    }
  }
}
</script>

那麼我們就 ... 啊!

修但幾咧,差點就忘了最重要最重要的點
我們的 % 算完還沒讓它應用到高度上啊 XD

所以,我們貼心的幫進度條本體綁定 style,用 percent 來動態改變寬度:

<div :class="[
  'w-80 p-5',
  'bg-gray-200',
  'rounded-lg'
]">
  <div :class="[
    'w-full h-2',
    'bg-green-800',
    'rounded-full'
  ]">
    <div 
      :class="[
        'w-1/3 h-full',
        'bg-green-500',
        'rounded-full'
      ]"
      :style="{
        width: percent + '%'
      }"
    />
  </div>
</div>

這樣就真的完成了!

最後元件的完整內容會是這樣:

<template>
  <div :class="[
    'w-80 p-5',
    'bg-gray-200',
    'rounded-lg'
  ]">
    <div :class="[
      'w-full h-2',
      'bg-green-800',
      'rounded-full'
    ]">
      <div 
        :class="[
          'w-1/3 h-full',
          'bg-green-500',
          'rounded-full'
        ]"
        :style="{
          width: percent + '%'
        }"
      />
    </div>
  </div>
</template>

<script>
export default {
  name: "ProgressBar",
  props: ["minVal","maxVal","currentVal"],
  data() {
    return {
      percent: 0,
    }
  },
  mounted() {
    this.percent = this.currentVal <= this.minVal ? 0 : (this.currentVal-this.minVal)*100/(this.maxVal-this.minVal)
  },
  watch: {
    currentVal(newVal, oldVal) {
      this.percent = newVal <= this.minVal ? 0 : (newVal-this.minVal)*100/(this.maxVal-this.minVal)
    }
  }
}
</script>

元件完成之後,我們就可以來測試啦!
 

carrotPoint 測試

再測試之前,我們要先來完善一下我們的測試環境

回到 App.vue,我們來增加一個拉桿的 input 元素,不然一直改來改去很累,不如用拉的來改變數值!

<div :class="[
  'w-screen h-screen',
  'flex flex-col',
  'justify-center items-center',
  'gap-5',
]">
  <input type="range" />
  <ProgressBar />
</div>

加好之後,我們也需要從外部幫他們兩個元件設定最小值、最大值,然後讓進度條元件的 currentVal 與拉桿的當前值同步,最後就會是這個樣子:

<template>
  <div :class="[
      'w-screen h-screen',
      'flex flex-col',
      'justify-center items-center',
      'gap-5',
  ]">
    <input type="range" v-model="val" :min="min" :max="max" />
    <ProgressBar :currentVal="val" :minVal="min" :maxVal="max" />
  </div>
</template>

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

export default {
  data() {
    return {
      min: 0,
      max: 200,
      val: 100,
    }
  },
  components: {
    ProgressBar,
  }
}
</script>

那麼可以測試了!
我們回到畫面上玩玩~

耶~大成功!
 

carrotPoint 進階修改

雖然像是做完了,不過還有一些可以再加強的地方!

畢竟這樣還是不夠靈活、不夠華麗呀~

所以最簡單的,我們先加上個過渡效果,且 duration 設為 0.5 s:

<div :class="[
  'w-80 p-5',
  'bg-gray-200',
  'rounded-lg'
]">
  <div :class="[
    'w-full h-2',
    'bg-green-800',
    'rounded-full'
  ]">
    <div 
      :class="[
        'w-1/3 h-full',
        'bg-green-500',
        'rounded-full',
        'transition-all duration-500'
      ]"
      :style="{
        width: percent + '%'
      }"
    />
  </div>
</div>

加完之後,應該覺得順暢多了吧?

「是沒錯啦,華麗的部份解決了 ... 但兔兔你說的靈活度呢?」

靈活度嗎,好!
這就要靠 slot 啦~

我們可以運用 slot 的 props,把資料從內部拉出來外部使用,像是:

<div :class="[
  'w-80 p-5',
  'bg-gray-200',
  'rounded-lg',
  'flex flex-col',
  'justify-center items-center',
  'gap-3'
]">
  <slot
    :minVal="minVal"
    :maxVal="maxVal"
    :currentVal="currentVal"
    :percent="percent"
  />
  <div :class="[
    'w-full h-2',
    'bg-green-800',
    'rounded-full'
  ]">
    <div 
      :class="[
        'w-1/3 h-full',
        'bg-green-500',
        'rounded-full',
        'transition-all duration-500'
      ]"
      :style="{
        width: percent + '%'
      }"
    />
  </div>
</div>

然後為了讓文字和進度條可以直向排列,我還另外在外框多下了 flex flex-col 的樣式,然後記得給一點空隙,才不會看起來太擠。

 
這樣做之後,我們就可以弄出像這樣子的效果:

<ProgressBar
  :currentVal="val" :minVal="min" :maxVal="max"
  v-slot="{ percent }"
>
  已下載 {{ percent }} %
</ProgressBar>

不僅如此,
也可以輕鬆修改出像是遊戲載入資源的進度條:

<ProgressBar
  :currentVal="val" :minVal="min" :maxVal="max"
  v-slot="{ maxVal, currentVal }"
>
  正在準備資源 ({{currentVal}} / {{maxVal}}) 
</ProgressBar>

那就會看起來像是這樣~

這麼一來就完全做完啦!
 

好的,那麼今天的進度條就是這麼輕鬆簡單~

明天的也很簡單喔,
我們要來完成各種按鈕的變化!

是不是躍躍欲試啦?
那我就不說這麼多了,趕快留時間給你去做做看!
 

carrotPoint 給你們的回家作業:


關於兔兔們:


 


( # 兔兔小聲說 )

今天兔兔喉嚨燒聲,說不出話。
但是可以唱歌!

(欸你這明明就是選擇性的!)


上一篇
Day 24:「Switch 也要換遊戲片啦~」- Slot 插槽
下一篇
Day 26:「按鈕博物館」- 輕鬆變化各種按鈕元件
系列文
排版神器 Tailwind CSS~和兔兔一起快速上手漂亮的元件開發!32

1 則留言

1
jason06286
iT邦新手 5 級 ‧ 2021-09-28 14:17:04

Hi 兔兔
今天跟著做進度條,發現有個 bug

// 如果今天最小值給的不是 0,帶入公式
// min=10 ,max=100
// current=10  會是 11% 沒有歸零
// current=90 就會是 100%
 this.percent = Number(this.currentVal*100/(this.maxVal-this.minVal))

是否優化成這樣會更好

this.percent = +this.currentVal <= this.minVal ? 0 : +this.currentVal-this.minVal/(this.maxVal-this.minVal)
搋兔 iT邦新手 5 級 ‧ 2021-09-29 10:32:37 檢舉

對,我沒注意到這個小細節 /images/emoticon/emoticon20.gif

會,你優化成這樣是可以的!
我更新上去,謝謝~

不過你的公式還要再乘 100 哦
但還是謝謝你!

我要留言

立即登入留言