iT邦幫忙

2022 iThome 鐵人賽

DAY 3
0
自我挑戰組

在30天利用HTML & CSS & JavaScript完成Side Project實作系列 第 3

Day 3 Side Project : Progress Steps 進度條

  • 分享至 

  • xImage
  •  

今天要來實作的是進度條,我們可能在各種不同類型的網站上看過它的蹤影,像是註冊表單、結帳流程等,那今天就來試著來自己做吧ヽ(✿゚▽゚)ノ


運用知識點羅列

一個作品是由一個個知識點堆疊而成,以下是本篇有使用到的知識點,應該是有全部列到啦

  • CSS
知識點 使用說明
z-index 控制順序
cursor disabled的按鈕
transition 進度線條的延展
2D transform 預設進度線條的位子調整
variable 變數存放顏色的變化
  • JS
知識點 使用說明
getElementById() / querySelectorAll() 獲取HTML元素
addEventListener() 監聽上一步和下一步按鈕
forEach() 迭代每個步驟
.classList.remove() / add() 新增或移除class="active"

流程講解

類似上一篇在做Expanding cards的配置,一樣是一個div容器內裝了子div,正在進行的那個步驟會加上class="active"去操控進度顯示
https://ithelp.ithome.com.tw/upload/images/20220909/201493628mK0DZmEkU.png

  • html
    提醒一下,上一步的按鈕記得加上disabled!因為最一開始是不會有上一步的選擇搭
  <div class="container">
        <div class="progress-container">
            <div id="progress" class="progress"></div>  //進度「線條」
            <div class="circle active">1</div>
            <div class="circle">2</div>
            <div class="circle">3</div>
            <div class="circle">4</div>
        </div>

        <!-- 上一步 -->
        <button id="prev" type="button" class="btn" disabled>上一步</button>
        <!-- 下一步 -->
        <button id="next" type="button" class="btn">下一步</button>
    </div>
  • CSS
    大局配置(這部分跟Day1:擴展卡片是一樣的,可以作為一個公版,基本上之後的project也都會用到)
* {
  box-sizing: border-box;
}

body {
  margin: 0;
  padding: 0;
  display: flex; /*讓內容在viewport的中間*/
  justify-content: center;
  align-items: center;
  height: 100vh;
  overflow: hidden;
}

若以上都設置好了,呈現會如下圖,接下來我們要把它變成橫向的排版
https://ithelp.ithome.com.tw/upload/images/20220909/20149362QC2mlWWGcs.png

接下來的程式碼 ↓ ,主要是在針對進度條的容器去做美化、排版,值得注意的是我們在progress-container內設了 max-width: 100%width:350px,為甚麼要雙重設定呢??? 我們可以視它為一個防護措施,max-width設定為百分比,是為了讓它不受限於父容器的寬度,而又有一個最少為350PX的寬度作為較安全的替代

外部和內部容器

/* 主容器 */
.container {
  text-align: center;
  font-size: 20px;
  font-weight: bold;
}
/* 進度條容器 */
.progress-container {
  display: flex;
  justify-content: space-between;
  position: relative;  /*可以針對progress-containe內部物件的位子去做調整*/
  margin-bottom: 30px;
  max-width: 100%;
  width: 350px;
}

變數設置
CSS3才出現的變數,主要是用來避免在不同的選擇器中重複定義相同的值,而變數名稱的開頭都是雙槓 --,要使用的時候搭配var(--變數名稱)就可以了。範例中我們把變數定義在:root,讓它成為全域變數

--line-border-fill: #f33252; /*填滿狀態*/
  --line-border-empty: #f4b9b9c6; /*未填滿狀態*/
  --btn-hover: #ec5b73;

進度條容器的內部
var(--變數名稱)來取用背景顏色,transform: translateY(-50%)是讓線條往Y軸的方向向上移動,百分比參考的是被定義transform的元件,而不是父元素,可以參考此連結

/* 預設進度「線條」 */
.progress-container::before {
  content: "";
  background-color: var(--line-border-empty);
  position: absolute;
  top: 50%;
  left: 0;
  width: 100%; 
  height: 3px;
  z-index: -1;
  transform: translateY(-50%); /*線條往上移動*/
}

/* 實際進度「線條」 */
div.progress {
  background-color: var(--line-border-fill);
  position: absolute;
  top: 50%;
  left: 0;
  width: 0%; /*一開始不會有進度,所以設0%*/
  height: 3px;
  z-index: -1;
  transform: translateY(-50%); /*線條往上移動*/
  transition: width 0.5s linear;
}
/* 步驟1、2、3、4 */
div.circle {
  color: white;
  background-color: #f34764;
  border: 3px solid var(--line-border-empty);
  border-radius: 50%;
  width: 30px;
  height: 30px;
  /* 讓數字置中對齊 */
  display: flex;
  justify-content: center;
  align-items: center;
  transition: border 0.5s linear;
}
/* 正在進行的步驟 */
div.circle.active {
  border: 3px solid var(--line-border-fill);
}

「上一步」、「下一步」按鈕
outline 是一個shorthand縮寫屬性,跟border很類似,不過一般是在表單輸入框被 focus 時,在外面那一圈變色的框線(Outline),這部分可以參考CSScoke大神寫的文章以及MDN

.btn {
  color: white;
  background-color: var(--line-border-fill);
  border: 0;
  border-radius: 10px;
  cursor: pointer;
  padding: 8px 30px;
  font-size: 14px;
  margin: 5px;
}
.btn:hover {
  background-color: var(--btn-hover);
}
.btn:focus {
  outline: 0; /*或none*/
}
.btn:disabled {
  cursor: not-allow;
  background-color: var(--line-border-empty);
}

  • JS
    終於來到本篇的重頭戲了,JS真是賦予了這個進度條生命,不過在開始看下去之前,先給自己一個掌聲,因為接下來比較複雜

建議可以寫一點就測一點,這樣要debug或是看某些設置的變化就就比較清楚! 如果有不知道甚麼就console.log()印出來看一下,有時候真的就豁然開朗

抓取HTML元素

let progress = document.getElementById("progress");
let prev = document.getElementById("prev");
let next = document.getElementById("next");
let circles = document.querySelectorAll(".circle");

功能的實現底家
因為進度條是跟著按鈕(next、prev)動態更新的,所以我們要加上addEventListener去監聽事件的狀態,然後透過if判斷式來決定是否可以再進行下一步/上一步,如果到最後一部,進度條就不會再往右方繼續延伸了;相反的,如果在第一步,進度條就不會再往左方繼續延伸了,所以我們要判斷目前是在第幾步驟,以便操控進度條的進展

let currentStep = 1; //目前步驟

// 下一步
next.addEventListener("click", () => {
  currentStep++;
  // console.log(currentStep);
  if (currentStep > circles.length) {
    currentStep = circles.length;
  }
  // console.log(currentStep);
  update();
});

// 上一步
prev.addEventListener("click", () => {
  currentStep--;
  // console.log(currentStep);
  if (currentStep < 1) {
    currentStep = 1;
  }
  console.log(currentStep);
  update();
});

DOM的更新

function update() {
  // 圓圈
  circles.forEach((circleItem, index) => {
    if (index < currentStep) {
      circleItem.classList.add("active");
    } else {
      circleItem.classList.remove("active");
    }
  });

當前進度的線條
這裡有一個很重要的點,一定要搞懂!
就是progress.style.width =(actives.length - 1) / (circles.length - 1)) * 100 + "%"

  let actives = document.querySelectorAll(".active");
  console.log((actives.length / circles.length) * 100);
  // progress.style.width = (actives.length / circles.length) * 100 + "%";
  progress.style.width =
    ((actives.length - 1) / (circles.length - 1)) * 100 + "%";  
  if (currentStep === 1) {
    prev.disabled = true;
  } else if (currentStep === 4) {
    next.disabled = true;
  } else {
    prev.disabled = false;
    next.disabled = false;
  }
}

我整理了自己當時在做的時候出現的疑問,或許也有可能是其他人的疑問

Q1 為什麼要用除法?
Q2 為什麼要-1?
Q3 為甚麼要加上%在最後面?

A1 把它轉換成分數的概念
A2 我們來比較看看有-1和沒-1的差別
https://ithelp.ithome.com.tw/upload/images/20220909/20149362WcvgpKY4rV.png
沒-1的版本,進度分成了1/2,換句話說,就是你每按下一步,進度就會前進1/2(約50%),這樣是不對的
https://ithelp.ithome.com.tw/upload/images/20220909/2014936213mDTJqeKY.png
有-1的版本,進度條就被分成1/3,換句話說,你每按下一步,進度就會前進1/3(約33%),這才是正確的
actives.length - 1 = 2 - 1 = 1
circles.length - 1 = 4 -1 = 3
A3 加上百分比是因為progress的width我們預設單位是%,所以才要用%

附上進度條的Codepen連結https://codepen.io/hangineer/pen/mdLVREN

summary 總結

可以順一下進度條的邏輯是怎麼運作的,會用到簡單的數學概念,並且一段一段來,如果還是有疑問,可以底下留言,我會盡我所能地回答你。

這篇的篇幅較長,還在想要用甚麼樣的方式呈現會比較好ಥ_ಥ 如果邦友們有任何想法可以留言讓我知道~ 所學不精,若有解說不夠詳盡或是錯誤歡迎指教,感激不盡!那明天見囉/images/emoticon/emoticon34.gif

參考資料

50 Projects In 50 Days - HTML, CSS & JavaScript
CSS視覺辭典 Greg Sidelnikov者


上一篇
Day 2 Side Project : Expanding Cards 擴展卡片
下一篇
Day 4 Side Project : Rotating Navigation 旋轉的導覽列
系列文
在30天利用HTML & CSS & JavaScript完成Side Project實作30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言