iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 5
1

任務目標

盤踞在大台南市區的四大天王「火爆浪子」最近在經營網路形象, 他們需要一個頁面來一段魅力的展現, 要求如下:

  1. 四名天王都必須有等寬的展示空間
  2. 滑鼠點擊該天王的展示區域, 會出現額外的資訊
    成品如同此連結.

這次任務若滿足「火爆浪子」, 以後在大台南地區就能橫著走了! 但如果失敗, 以後就只能橫屍街頭. 該怎麼化解危機呢? 讓我們看下去...

作法

下面是HTML框架.

<div class="panels">
  <div class="panel panel1">
    <p>玩</p>
    <p>火</p>
    <p>人</p>
  </div>
  <div class="panel panel2">
    <p>鬍</p>
    <p>爆</p>
    <p>哥</p>
  </div>
  <div class="panel panel3">
    <p>衝</p>
    <p>浪</p>
    <p>孩</p>
  </div>
  <div class="panel panel4">
    <p>種</p>
    <p>子</p>
    <p>男</p>
  </div>
</div>

四大天王的照片存在各個panel的背景中

.panel1 { background-image:url(https://source.unsplash.com/asytdeogTDE/1500x1500); }
.panel2 { background-image:url(https://source.unsplash.com/XhMSz5I1kn8/1500x1500); }
.panel3 { background-image:url(https://source.unsplash.com/i4OHxtxiMtk/1500x1500); }
.panel4 { background-image:url(https://source.unsplash.com/kPZwI56RbkY/1500x1500); }  

第一步, 必須讓包著四大天王照片的panel從左至右排列, 並且平均分配欄寬, 這時候可以用到Flex Box Module!

Flex Box

Flex Box顧名思義為具有彈性的盒子模型.

只要將元素的display屬性設為flex, 該元素的子元素就會具有適應容器空間的能力. 此時該元素成為Flex Box的作用容器, 稱做flex-container, 而容器內的子元素們稱作flex-item.

借用MDN文件上這張精美的圖, Flex Box內有兩個軸, 一個是主軸(main-axis), 一個是和主軸垂直的交叉軸(cross-axis). 預設情況下, 主軸都是左右橫向, 交叉軸是上下直向. 成為flex-item的元素們會依主軸方向, 從起點(main-start)往終點(main-end)排列.

因此要處理四大天王的照片, 我們可以在panels類別將display設成flex.

  .panels {
    /* 前略 */
    display: flex;
  }

照片們的確依照主軸方向由左到右排列了. 但是卻沒有塞滿整個畫面.
此時就要說到flex屬性了.

flex屬性

flex屬性是作用在容器內的元素, 也就是flex-item的屬性. flex屬性是flex-grow, flex-shrink, flex-basis三個屬性的依序簡寫, 用來定義各個flex-item的預設寬度與容器空間的關係. 下面個別說明.

flex-basis屬性

可以想成是flex-item的預設寬度, flex-basis預設值為auto, 意思就是去參考元素本身的widthheight值.

flex-grow屬性

flex-grow屬性值不為0時, 該屬性啟動, 表示當容器有多餘空間時, flex-item會自動填滿剩餘空間, flex-grow的數字代表分到多餘空間的比例.
舉例說明:

<div id="outer">
  <div id="bar1"></div>
  <div id="bar2"></div>
</div>
#outer {
  width: 300px;
  height: 50px;
  display: flex;
  border: 1px solid blue;
}

#bar1 {
  flex-basis: 100px;
  flex-grow: 1;
  background: gold;
}

#bar2 {
  flex-basis: 100px;
  flex-grow: 2;
  background: lightblue;
}
bar1 = document.getElementById('bar1').offsetWidth;
bar2 = document.getElementById('bar2').offsetWidth;
console.log(bar1, bar2) // 133 , 167

容器outer扣掉bar1, bar2的寬度後, 還有100px的剩餘空間, 因為bar1bar2flex-grow為1: 2, 因此剩下的100px, bar1只能分到三分之一, bar2能分到三分之二, 所以最後寬度分別為133px, 和167px

flex-shrink屬性

flex-shrinkflex-grow相反, 屬性值不為0時, 該屬性啟動, 表示當容器空間不足時, flex-item會依照屬性值的比例自動縮減寬度.

大概如上述, 當一個元素變成flex-item時, flex屬性會有基本預設值flex: 0 1 auto, 也就是不會自動填滿多餘空間, 空間不足會自動縮減寬度, 基本寬度值參考width屬性.

說了這麼多, 要讓四大天王的照片填滿版面, 只需要將子元素panelflex屬性更改即可:

.panel {
  flex: 1; /* 意思同等於 flex-grow: 1 */
}

四大天王們的名字由panel內的三個子元素p所組成, 我們希望三個子元素能夠

  1. 垂直排列
  2. 等寬度填充

看起來又可以用到Flex Box的特性了!
沒錯, Flex Box是可以嵌套的, 一個元素可以是flex-item, 同時又是別人的flex-container.
因此可以讓.panel成為pflex-container

.panel {
  flex: 1; /* 意思同等於 flex-grow: 1 */
  display: flex;
}

有提到flex-item預設沿著主軸, 也就是左右向排列, 該怎麼實現垂直排列呢? 可以透過flex-direction屬性改變主軸方向, 預設flex-direction: row, 只要flex-direction: column, 主軸會變成上下方向, 交叉軸變成左右方向. flex-item自然由上到下排列. 別忘了設定panel內子元素們的flex, 讓他們自動擴張.

.panel {
  flex: 1; /* 意思同等於 flex-grow: 1 */
  display: flex;
  flex-direction: column;
}

.panel > * {
  flex: 1 0 auto;
}

p元素內的字此時尚未置中, 這可是四大天王的名字, 這對天王們是大不敬. 得趕快修正. 在flex-container上設定align-items: center可以讓子元素們上下置中, justify-content: center可以左右置中.

.panel > * {
  flex: 1 0 auto;
  display: flex;
  justify-content: center;
  align-items: center;
}

p來說, 該子元素為裡面的字, 於是字就對齊了.

接下來我們希望四大天王的名字最上面跟最下面的字一開始是在畫面外, 點擊後會滑進來. 中間的字則會放大. 然後畫面會擴張.

利用transform: translate()將字先移到畫面外待命, 當給panel加上open-active類別時會滑進來.

.panel > *:first-child {
  transform: translateY(-100%);
}
.panel.open-active > *:first-child {
  transform: translateY(0);
}      
.panel > *:last-child {
  transform: translateY(100%);
}
.panel.open-active > *:last-child {
  transform: translateY(0);
}

panel 加上open類別時, flex-grow會擴張比較多.

.panel.open {
  font-size:40px;
  flex: 5;
}

接下來加上JS程式碼, 讓點擊時可以讓open-activeopen類別被切換, 利用style.classList.toggle()可以做到這點. 程式碼如下:

const panels = document.querySelectorAll('.panel');
panels.forEach(panel => panel.addEventListener('click', toggleOpen));
panels.forEach(panel => panel.addEventListener('transitionend', toggleActive));

function toggleOpen() {
  this.classList.toggle('open');
}

function toggleActive(e) {
  console.log(e.propertyName)
  if(e.propertyName.includes('flex')) {
    this.classList.toggle('open-active');
  }
}

這裡要注意的是toggleActive()函數的內容, 程式預設在transitionend事件觸發時呼叫toggleActive函式來回應, 由於Safari瀏覽器和Chrome/Firefox瀏覽器對flex-grow屬性儲存在e.propertyName的名稱並不相同, 一個為flex, 一個為flex-grow, 為了支援全部瀏覽器, 程式內寫只要屬性名稱"包含"flex四個字, 才執行切換類別的動作.

大台南四大天王非常滿意.

以上就是JS30 第五篇!

Reference

Flex Box
Flex
練習的完整程式碼

大台南四大天王, 友情贊助.
「玩火人」戴著紅帽, 擅使火技
「鬍爆哥」脾氣暴躁, 滿臉鬍渣
「衝浪孩」渾身濕透, 性喜衝浪
「種子男」一雙妙手, 四處播種


上一篇
Day 4 - Array Cardio Part I
下一篇
Day 6 - Type Ahead
系列文
JS30 錄30

2 則留言

0
Andy Tsai
iT邦新手 5 級 ‧ 2017-12-24 21:05:49

好文分享

0
JasonYang
iT邦新手 5 級 ‧ 2018-01-06 23:34:44

要命,才剛寫完flexbox,
結果你這邊又多了許多我還沒研究的屬性,
看來沒有看完整!

我要留言

立即登入留言